Umfeld × WebAssembly

while listening to an interesting interview about The Future of Processing – with Raphaël and Stef by tim rodenbröker, i heard raphaël ask “we are looking at ways that Processing can get back into the browser … in different ways …”
well, here i present one way of doing it with WebAssembly ( Wasm ). i remembered that i read somewhere that SDL3 ( <- which a lot of Umfeld is based upon ) support this via Emscripten ( a toolchain that compiles C/C++ to Wasm ).
click to run an example♥ right here the browser.
since i had already implemented an OpenGL ES 3.0 renderer it was VERY easy to get the workflow up and running. i plan to test it a bit before merging it into the main branch.
if you are interested in testing right away check out the development branch dev-web-assembly,; only prerequisit is the installation of Emscripten SDK ( on macOS with brew install emscripten or manually from https://emscripten.org/docs/getting_started/downloads.html ).
compilation is rather easy, but here is a convenient shell script and a base HTML container; both should live next to the CMake script:
.
├── application.cpp
├── build-wasm.sh
├── CMakeLists.txt
├── data
│ └── ...
└── shell.html
shell script build-wasm.sh:
#!/usr/bin/env bash
#
# build script for Umfeld WebAssembly application
#
# usage:
# ./build-wasm.sh # configure + build
# ./build-wasm.sh clean # remove build directory
# ./build-wasm.sh serve # start a local web server on port 8000
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${SCRIPT_DIR}/build-wasm"
# -------------------------------------------------------------------------
# Locate Emscripten toolchain
# -------------------------------------------------------------------------
# Option 1: emsdk-based (EMSDK env var set by `source emsdk_env.sh`)
# Option 2: homebrew-installed emscripten
if command -v emcmake &>/dev/null; then
echo "✓ Found emcmake: $(which emcmake)"
elif [ -n "${EMSDK:-}" ]; then
echo "Sourcing emsdk_env.sh from EMSDK=$EMSDK"
source "$EMSDK/emsdk_env.sh"
else
echo "ERROR: Emscripten not found."
echo "install via homebrew: brew install emscripten"
echo "or install emsdk: https://emscripten.org/docs/getting_started/downloads.html"
exit 1
fi
# -------------------------------------------------------------------------
# sub-commands
# -------------------------------------------------------------------------
case "${1:-build}" in
clean)
echo "Removing $BUILD_DIR ..."
rm -rf "$BUILD_DIR"
echo "Done."
exit 0
;;
serve)
if [ ! -d "$BUILD_DIR" ]; then
echo "ERROR: build directory not found. run './build-wasm.sh' first."
exit 1
fi
echo "starting web server at http://localhost:8000"
echo "open http://localhost:8000/web-assembly.html in your browser."
cd "$BUILD_DIR"
python3 -m http.server 8000
exit 0
;;
build)
;;
*)
echo "Usage: $0 [build|clean|serve]"
exit 1
;;
esac
# -------------------------------------------------------------------------
# Configure + Build
# -------------------------------------------------------------------------
echo "configuring WebAssembly build ..."
emcmake cmake -B "$BUILD_DIR" -S "$SCRIPT_DIR" \
-DCMAKE_BUILD_TYPE=Release
echo ""
echo "building ..."
cmake --build "$BUILD_DIR" --parallel
echo ""
echo "============================================="
echo " build complete!"
echo " output: $BUILD_DIR/web-assembly.html"
echo ""
echo " test locally with:"
echo " ./build-wasm.sh serve"
echo " open http://localhost:8000/web-assembly.html"
echo "============================================="
HTML file shell.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Umfeld</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #202020;
color: #e0e0e0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
}
h1 { margin-bottom: 0.5em; font-size: 1.2em; color: #a0a0a0; }
#canvas {
border: 1px solid #333;
cursor: crosshair;
}
#status {
margin-top: 0.5em;
font-size: 0.85em;
color: #888;
}
#toggle-console {
margin-top: 0.5em;
padding: 0.25em 1em;
background: #222;
color: #888;
border: 1px solid #444;
cursor: pointer;
font-size: 0.75em;
font-family: monospace;
border-radius: 3px;
}
#toggle-console:hover { color: #ccc; border-color: #666; }
#output {
width: 1024px;
max-height: 200px;
overflow-y: auto;
margin-top: 0.5em;
padding: 0.5em;
background: #111;
font-family: "SF Mono", "Menlo", "Monaco", "Consolas", monospace;
font-size: 0.75em;
line-height: 1.4;
border: 1px solid #333;
border-radius: 3px;
display: none;
}
#output .msg-error { color: #ff6b6b; }
#output .msg-warning { color: #ffa726; }
#output .msg-console { color: #66bb6a; }
#output .msg-info { color: #aaa; }
</style>
</head>
<body>
<h1>Umfeld / WebAssembly</h1>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<div id="status">Loading…</div>
<button id="toggle-console">Show Console</button>
<pre id="output"></pre>
<script>
var outputEl = document.getElementById('output');
var toggleBtn = document.getElementById('toggle-console');
function appendMessage(text, className) {
var span = document.createElement('span');
span.className = className;
span.textContent = text + '\n';
outputEl.appendChild(span);
outputEl.scrollTop = outputEl.scrollHeight;
}
function classifyMessage(text) {
if (text.indexOf('ERROR') !== -1) return 'msg-error';
if (text.indexOf('WARNING') !== -1) return 'msg-warning';
if (text.indexOf('CONSOLE') !== -1) return 'msg-console';
return 'msg-info';
}
toggleBtn.addEventListener('click', function() {
var hidden = outputEl.style.display === 'none';
outputEl.style.display = hidden ? 'block' : 'none';
toggleBtn.textContent = hidden ? 'Hide Console' : 'Show Console';
});
var Module = {
canvas: (function() { return document.getElementById('canvas'); })(),
setStatus: function(text) {
document.getElementById('status').textContent = text;
},
print: function(text) {
console.log(text);
appendMessage(text, classifyMessage(text));
},
printErr: function(text) {
console.warn(text);
appendMessage(text, classifyMessage(text));
},
};
</script>
{{{ SCRIPT }}}
</body>
</html>