Skip to content

Browser WebAssembly Port - #5106

Draft
ading2210 wants to merge 29 commits into
supertuxkart:masterfrom
ading2210:wasm
Draft

Browser WebAssembly Port#5106
ading2210 wants to merge 29 commits into
supertuxkart:masterfrom
ading2210:wasm

Conversation

@ading2210

@ading2210 ading2210 commented May 30, 2024

Copy link
Copy Markdown

Agreement

By creating a pull request in stk-code, you hereby agree to dual-license your contribution as
GNU General Public License version 3 or any later version and
Mozilla Public License version 2 or any later version.

This includes your previous contribution(s) under the same name of contributor.

Keep the above statement in the pull request comment for agreement.

I ported SuperTuxKart to the web using Emscripten, and I think it would be worthwhile to have this merged upstream.

Here's a working demo: https://supertuxkart.pages.dev/

I tested this on Chrome 125 and Firefox ESR 115.

8mb.video-Q5u-I6rcrEhf.mp4

Working features:

  • The game launches
  • OpenGL ES 2 graphics (GLES 3 doesn't work)
  • Audio
  • Persistent user data
  • Caching data and asset files in IndexedDB

TODO:

  • Fix GLES 3 (maybe someone with more experience in webgl could help here)
  • Webpage theming
  • Better compression for assets
  • Lazy load assets during gameplay
  • Networking

Caveats:

  • The RAM usage is rather high because the assets are always loaded in memory
  • The performance isn't great, probably because the legacy renderer is still being used.
  • There is a large ~700MB download required immediately
  • Some options, like anything related to online multiplayer, may hang the game

@ading2210
ading2210 marked this pull request as draft May 30, 2024 14:45
@CodingJellyfish

Copy link
Copy Markdown
Contributor

This is so cool!

@CodingJellyfish

CodingJellyfish commented May 30, 2024

Copy link
Copy Markdown
Contributor

You don't need to worry about the graphics API thing. If I complete the Vulkan engine I will probably write a WGPU engine.
Also, there's a compressed asset for Android to load faster, or the whole WASM version should probably use the same code as Android. I don't know much about this, you could ask @deveee for help.

@CodingJellyfish

Copy link
Copy Markdown
Contributor

If the commits don't create any regressions, I guess I could merge it as quickly as possible. The todos can be done in later PRs or by other contributors. But I would like a clear instruction on how to compile this in INSTALL.md.

@ading2210

Copy link
Copy Markdown
Author

Also, there's a compressed asset for Android to load faster, or the whole WASM version should probably use the same code as Android.

Personally I don't think using the same code as the Android version would be a good idea here, since Android is vastly different from the Emscripten environment. For example in WASM the threading implementation is very broken, which means a large amount of the networking logic has to be rewritten to use async APIs for it to work. I did borrow the audio patches from the Switch port though, since those allowed the audio processing to run in the main thread (because web audio cannot be run from a worker at all).

Regarding the graphics issues, when I tried using the non-legacy renderer, I kept getting WebGL errors which I wasn't able to debug. You can try removing the #ifndef here to see how far you can get with that:

#ifndef __EMSCRIPTEN__
if (CreationParams.ForceLegacyDevice)
#endif

image

Also, I did put compile instructions inside the README for the wasm directory, but I guess this got buried in the PR diff.

@CodingJellyfish

Copy link
Copy Markdown
Contributor

Thanks for mentioning. Actually, I meant the Android code could handle the option of installing the full uncompressed game asset instead of forcing you to load that initially. I don't compile Android code so I don't know a lot tbh.

@CodingJellyfish

Copy link
Copy Markdown
Contributor

And yes I found the instructions now, very thanks. Then I would approve the merge of this PR at any time when you feel free.

@ading2210

Copy link
Copy Markdown
Author

I do think that I can incorporate what android/generate_assets.sh does to compress the assets further, which would be great for decreasing the initial download size. However loading all of the assets in memory is an Emscripten limitation, rather than something in STK's code. All of the C++ APIs to read files are synchronous, but Javascript has no synchronous API for reading files from the disk. This means that Emscripten's virtual filesystem has to be loaded in memory at all times. Even if it could be backed up something like an IndexedDB, Emscripten still has to be able to read any file from it instantly. The Emscripten docs have some more info on this.

If I can get the assets compressed down to under ~200MB or so, then that amount of extra memory usage is more acceptable and we won't need to worry about the Emscripten virtual filesystem stuff.

@thiccaxe

Copy link
Copy Markdown

another task is to either strip out or parse the ANSI escape sequences

@ading2210

ading2210 commented May 31, 2024

Copy link
Copy Markdown
Author
stk_wasm_2.mp4

I just made some improvements to the web interface. The most significant changes are that the game now fills the entire browser window, and the progress for the asset download is displayed.

@Alayan-stk-2

Copy link
Copy Markdown
Member

It's cool to have STK running in a web browser.

But I have some concerns. The OpenGL ES 2 graphics mean that the game looks really crappy, and at the same time I get really terrible performance. Running the benchmark I got a steady FPS of 6 and the game was running at 50% of real-time-speed (it took 1m13s to complete instead of normally about 36s). I get something like 250FPS with a normal compiled version using Graphics 1.

If someone is curious about STK, I'd prefer them to not play the web version as a way to test the game...

I had a cursory look at the changes, most of it seem to be compiling logic and define guards, plus the webpage code. It seems to be relatively simple overall, but the maintenance burden still appears to be an open question.

Especially as additional code would arguably be needed to disable the parts and options that don't work on the web version.

@searinminecraft

searinminecraft commented Jun 14, 2024

Copy link
Copy Markdown
Contributor

to anyone that wants to try this you might wanna disable ublock origin as it blocks webassembly

if youre using a hardened by default browser (e.g. librewolf, mulch, ungoogled-chromium, etc.) you may need to find a way to enable jit, webgl and wasm

@ading2210

ading2210 commented Jun 26, 2024

Copy link
Copy Markdown
Author

I managed to get the assets compressed down to 120MB, which is a large improvement over the 700MB download that was required before. The memory usage is also significantly lower since less asset data gets loaded into memory.

The highest levels of compression cause the textures to be noticeably blurry sometimes, so I added an option to use higher resolution assets at the cost of a larger download.

@CodingJellyfish

Copy link
Copy Markdown
Contributor

Hello, is there any progress on this?

@ading2210

Copy link
Copy Markdown
Author

@CodingJellyfish I just merged the latest commits on the main branch with my changes. I'll probably be able to fix the networking problems by switching from the blocking libcurl APIs to the async ones. Once the networking is fixed I'll likely be fine with merging this PR. I don't have any experience in graphics programming so I can't fix the GLES 3 support, so someone else will probably have to do it later.

@vlouvet

vlouvet commented May 23, 2026

Copy link
Copy Markdown

I am happy to announce that STK in the browser with GL ES 3.0 is now working successfully, on my fork/branch.

The demo is available up here https://stk.linuxcolorado.com/

I need more people to test playing the game in the browser, and online matches, then provide feedback on bugs, etc.

my fork/branch is here https://github.com/vlouvet/stk-code/tree/wasm-gles3 and I will be making a separate PR for it soon, in order to not cross-request on this PR.

I wanted to add this note for the sake of reference - and to thank @ading2210 for the hard work, without which my work would not have been possible.

@ading2210

Copy link
Copy Markdown
Author

Thank you @vlouvet! This is the furthest we've gotten so far with GLES3 support. However, I had some issues with your demo. If you run the in-game benchmark, the kart isn't rendered correctly, and the game crashes with this error:

image

If you start a new race manually, it's fine though.

@ading2210

ading2210 commented May 23, 2026

Copy link
Copy Markdown
Author

@vlouvet Once everything is fixed I can add your changes to my PR and hopefully get it merged soon. There's a ton of AI generated changes in there and I can manually pick out the ones that are needed to make it easier to review.

vlouvet added a commit to vlouvet/stk-code that referenced this pull request May 23, 2026
The in-game benchmark crashes on Firefox WebGL2 the moment the race
countdown hits "GO":

  Uncaught TypeError: GLctx.disjointTimerQueryExt.createQueryEXT is not a function
      at _emscripten_glGenQueriesEXT (supertuxkart.js:20:3666)
      ...

Reproduction reported by @ading2210 in PR supertuxkart#5106 review:
  supertuxkart#5106 (comment)

What triggers it: WorldStatus::onPhaseChange() activates the profiler
when RaceManager::isBenchmarking() is true; the very first ScopedGPUTimer
then calls glGenQueries, which Emscripten's library_webgl.js dispatches
to GLctx.disjointTimerQueryExt.createQueryEXT. Firefox often refuses to
expose EXT_disjoint_timer_query_webgl2 (fingerprinting concern), so
disjointTimerQueryExt is undefined and the page tears down.

Guard ScopedGPUTimer construction/destruction and GPUTimer::elapsedTimeus
behind !defined(__EMSCRIPTEN__). CPU-side profiling still runs and the
benchmark completes; GPU-time breakdown is just unavailable on wasm,
which matches the broader story that Emscripten doesn't expose reliable
GPU timer queries anyway.

Also adds wasm/test_benchmark.sh as a regression check:
  1. Parses the wasm IMPORT section and fails if any glGenQueries /
     glBeginQuery / glGetQuery / TimerQuery symbols are imported.
  2. Verifies the public deploy returns 200 with application/wasm.
  3. Documents the manual browser repro.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vlouvet

vlouvet commented May 23, 2026

Copy link
Copy Markdown

@ading2210 thanks for the catch — fixed in 8c345939df.

Root cause: WorldStatus::onPhaseChange() activates the profiler the moment the race countdown hits "GO" when RaceManager::isBenchmarking() is true. The very first ScopedGPUTimer after that calls glGenQueriesEXT, which Emscripten dispatches to GLctx.disjointTimerQueryExt.createQueryEXT. Firefox refuses to expose EXT_disjoint_timer_query_webgl2 (fingerprinting concern), so disjointTimerQueryExt is undefined and the page tears down with the TypeError you saw. A normal race start doesn't go through that path, which is why only the benchmark hit it.

Fix: the three GPU-timer call sites in src/graphics/glwrap.cpp (ScopedGPUTimer ctor/dtor and GPUTimer::elapsedTimeus) are now guarded by #if defined(GL_TIME_ELAPSED) && !defined(EMSCRIPTEN). CPU profiling still runs; the benchmark just doesn't report a GPU-time breakdown on wasm — which matches the broader reality that Emscripten can't reliably expose GPU timer queries anyway.

Verified at https://stk.linuxcolorado.com/ — benchmark replay completes cleanly now:

[info] Profiler: Frame count '6123', Time (ms) '20335',
Steady FPS '139', Mostly stable FPS '250', Typical FPS '319'
Also added wasm/test_benchmark.sh as a regression check — it parses the wasm IMPORT section and fails if any glGenQueries/glBeginQuery/glGetQuery/TimerQuery symbols are re-imported, so the next time we touch this code path we won't silently regress.

Re-test whenever you have a minute. Hard-reload first since the old wasm is in browser cache.

@Alayan-stk-2

Copy link
Copy Markdown
Member

@vlouvet Once everything is fixed I can add your changes to my PR and hopefully get it merged soon. There's a ton of AI generated changes in there and I can manually pick out the ones that are needed to make it easier to review.

@vlouvet @ading2210 Please see https://supertuxkart.net/How_to_contribute_code#supertuxkarts-policy-on-ai-generated-code

@vlouvet

vlouvet commented May 31, 2026

Copy link
Copy Markdown

@Alayan-stk-2

I understand the position of the project on A.I. generated code - and I did not mean to waste anyone's time, my apologies.

I do hope that the work done so far on bringing STK to the browser recently can be of some use to someone in the project in the future.

Additionally, I am happy to incorporating upstream changes into my fork/branch so that playing through the browser remains possible - even if it is through an unofficial port/fork.

Thank you for the wonderful project.

@searinminecraft

Copy link
Copy Markdown
Contributor

So if theres AI generated code on this PR now, doesn't that mean this PR has to be immediately closed and rejected?

@vlouvet

vlouvet commented Jun 1, 2026

Copy link
Copy Markdown

This PR remains untouched by AI.

My original comment states that the work was on a separate for and branch and it not incorporated into this PR from what I can tell.

Hope this helps clear this up @searinminecraft

@searinminecraft

Copy link
Copy Markdown
Contributor

My original comment states that the work was on a separate for and branch and it not incorporated into this PR from what I can tell

My bad, sorry for the misunderstanding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants