I finally found some time over the holidays to wrap up some improvements I was working on for ScummVM last year. There are still a few unfinished features waiting, but I thought it was a good moment to actually collect some improvements into a pull request—it has been more than 18 months since the last one (see also part 1 and part 2 of this project).
As always, you can try out all the changes at scummvm.kuendig.io
Log File Download
The ScummVM log file can now be opened (as a download in the browser) from the integrated console. The majority of this is adding the capability download files. As the file system in Emscripten isn’t user-accessible, there’s not much point in saving data to it unless the app also provides a way to access it. To overcome this, I save the files in a temporary directory and then pass them over to the browser’s Javascript context where we can trigger a download with URL.createObjectURL1.
Screenshots Fixes
Once there was a method in place for downloading files, a natural next step was adding support for screenshots. This was using the same method as for the log file, but initially there were only images with black pixels exported as soon as rendering was not happening in software (i.e. in OpenGL).
There were two WebGL-specific fixes required for that:
-
We can’t access pixels on the screen after compositing happens.
By default, after compositing the contents of the drawing buffer shall be cleared to their default values […] Techniques like synchronous drawing buffer access (e.g., calling readPixels or toDataURL in the same function that renders to the drawing buffer) can be used to get the contents of the drawing buffer.
This means we need to capture the screenshot right after rendering (before yielding back to the browser), which is not the time when the keystroke event to capture a screenshot is triggered (right after yielding). I had to hack this a bit so a screenshot is queued on this event and taken when the next frame is rendered. Not great, but it works nicely in practice.
-
WebGL doesn’t support
GL_RGB, but ScummVM unfortunately switched to capturingGL_RGBscreenshots recently2.Only two combinations of format and type are accepted. The first is format RGBA and type UNSIGNED_BYTE. The second is an implementation-chosen format.
As it turns out, the implementation-chosen formats are buggy (), so I switched back to
GL_RGBAfor Emscripten.
Once that was fixed, when pressing alt+s, screenshots are opened the same way as log files:
Fixing an OpenGL performance regression
For some reason new builds were running Grim Fandango only at 3-5 FPS instead of 50-60fps - which wasn’t the case in 2022 when I submitted my last PR. Finding the root cause of this was quite some work to track down.
I initially ignored the fact that this was a regression and tried debugging it with the profiler - but couldn’t figure it out since all time was spent in WebGLRenderingContext function. As it later turned out, the root cause was super obvious from the profile alone…3

So I went on figuring out which of the over 18'000 commits between my last PR in June 2022 and the end of December 2023 was causing this regression. For this I manually went picking random commits in between to check the performance trying to slowly narrow down where the issue might have been caused:
This was so frustrating that I actually decided to stop banging my head against a wall and figure out how to troubleshoot such issues properly. Turns out there’s git-bisect which made things much much easier 😊 and eventually I could track it down to a specific commit: .
At this point it became clear that I should not have given up on the profiler, the bug was indeed that since adding the debugging wrapper in that commit, every frame was calling an expensive GL function as not everything worked properly on WebGL (so errors are caught on every frame).
Eventually this was a super easy fix, I just decided to skip this debug wrapper for Emscripten as the browser anyway pushes out the same debugging messages in the console.
Shaders
Adding shaders actually required no adjustments - since ScummVM 2.7.0 users can select shader-based scalers to simulate a CRT. It was very satisfying to see this work without any changes (and I teased this already earlier on discord).
It’s not super ergonomic to set up, and only a subset of the available shaders work in WebGL, so ideally in a future update these will be filtered out from the settings.
Screenshot taken within the browser with the above mentioned fix
Engines not compiling with OpenGL ES
When building ScummVM with all engines enabled I encountered problems in two engines that used OpenGL symbols that aren’t part of OpenGL ES:
- Freescape does not wrap its
gfx_opengl.cppwith#ifdef USE_OPENGL_GAMEas other engines, as well as callsglDisable(GL_LIGHTING)in it’sgfx_opengl_shaders.cppwhich isn’t something that is done in OpenGL ES (which doesn’t have fixed pipeline lighting). Fixing this was easy by adding the relevant#ifdefs. Unfortunately this doesn’t yet make the Shader-based OpenGL renderer work, as the shaders themselves aren’t compatible with WebGL/GLSL, so now this fails at runtime:This is something that has to be fixed in the relevant shaders4. But it at least compiles now, and given TinyGL works just fine for this super old engine, I didn’t add any other workarounds.Shader: createCompatShader(): Could not compile shader freescape_triangle.fragment: ERROR: 0:33: '[]' : Index expression must be constant.
- HPL1 fails compiling in
CGProgram.cpp:engines/hpl1/engine/impl/CGProgram.cpp:149:14: error: use of undeclared identifier 'GL_PROJECTION_MATRIX' 149 | glGetFloatv(GL_PROJECTION_MATRIX, projection.getData()); | ^ engines/hpl1/engine/impl/CGProgram.cpp:150:14: error: use of undeclared identifier 'GL_MODELVIEW_MATRIX' 150 | glGetFloatv(GL_MODELVIEW_MATRIX, modelView.getData()); | ^
I didn’t find any workaround and, given that I found many other references to classic OpenGL in this engine, I decided to flag HPL1 as requiring classic OpenGL in its configure.engine so the engine is automatically disabled when building against OpenGL ES.
Other Improvements
There were a bunch of improvements that aren’t related to user visible features or minor bugfixes:
- Disabled GLAD and fixed a build issue related to that - GLAD is not needed on Emscripten as we target Emscripten’s emulated OpenGL ES built on top of WebGL which is a stable and static environment without any need for adjustments at runtime. Also, the current GLAD implementation is slow (see ).
- Updated the
build.shandconfigurescripts for better handling of libraries. Third-party libraries are now auto-detected, no longer need external patches, and we use emscripten-ports for giflib (which needed a small fix in emscripten: ) and libvorbis. - Adjusted game downloader to the new ScummVM data spreadsheet.
- Removed workarounds in
scummvm.iniand launch parameters by making OpenGL the default backend and adjusting the user settings path to use the home directory. - Remove gamepad API workaround which isn’t needed anymore since .
- Loading Icons directly without having to fetch a large ZIP file to simplify things.
Conclusion
It took me a long time to get this out, much longer than intended initially (I aimed at pushing something at least once a year). This was mostly because I decided to fix too many things and add new features at the same time.
There’s quite a bit of (half-working) code for proper filesystem handling in C++ and cloud synchronization still waiting to be wrapped up and I look forward to release that later this year.
-
Trevor Sundberg’s tutorial at motley-coder.com had a super useful sample for this (since taken offline, but available via the Internet Archive). ↩︎
-
Apparently to fix some other issues - see - hopefully this won’t be an issue in Emscripten. Otherwise the PR has some discarded code that works by capturing RGBA and converting it later (). ↩︎
-
You can inspect the profile yourself at https://share.firefox.dev/3RGTBd4 - I just saw that all time seems to be spent in GL functions (namely WebGLRenderingContext.getError() which didn’t make any sense to me as the regression was in our code not in the browser. ↩︎
-
This issue is well documented on Stack Overflow: Index expression must be constant - WebGL/GLSL error ↩︎