About

WebAssembly WebGL OpenGL ES Android AAudio PulseAudio Win32 X11 WebAudio GLSL

Cross-platform portability layer for multimedia applications. Deploy to Windows, Linux, Android and the Web via a single codebase. Includes a Javascript-superset DSL for writing WASM-to-JS bindings and a bytecode-based realtime audio rendering engine that implements a subset of the WebAudio spec. Currently under heavy development.

bpat in Action

bpat-demos

An assortment of demos showcasing bpat's features.

Try it out online!

Bouncy Boar 3

Bouncy Boar 3 was successfully ported to the web and to Android using bpat.

Try it out online!

DSL for Visual Novels in Scala

DSL for Visual Novels in Scala was successfully ported to the web and to Android using bpat.

Try it out online!

Highlights

A "web-first" API Design

Out of all platforms which bpat supports, the web platform is the most restrictive. In particular, the application has to periodically yield to the web browser's event loop, and most web APIs are async.

As such, bpat adopts a "web first" API design --- just like the web platform, bpat runs a central event loop, and most of its APIs are asynchronous callback-based. This means that most of bpat's APIs map directly onto web platform APIs, which minimizes the amount of binding code and allows binding JS code to be much simpler.

Javascript-like DSL for writing WASM-to-JS bindings

bpat defines a Javascript-like DSL to make writing WASM-to-JS bindings a pleasant experience. This DSL allows developers to write Javascript modules which can selectively export functions to WASM and import functions from WASM.

An example Javascript module written in the DSL is:

import { getValueFromWasm } from '@wasm-exports';
import { console } from '@js-globals';

export("wasm") function printValueToConsole() {
  console.log(getValueFromWasm());
}

As we can see, the DSL allows using WASM functions by importing them from the special @wasm-exports modules. Any global, such as console, must also be explicitly imported from @js-globals to allow the DSL to perform correct dead code elimination.

The DSL compiler will compile the above source file into:

let bpat=(function(){ let a0;let a1;let a2;let a3=function(){console.log(a1());};return{env:{printValueToConsole:a3},run:function(b0){let b1=b0.instance.exports;let b2=function(c0){if(!b1[c0])throw new Error("Export not found");return b1[c0];};a0=b2("main");a1=b2("getValueFromWasm");a2=b2("btDumpStack");try{a0();}catch(c0){a2();throw c0;}}};})()

As we can see, the compiler takes care of generating the necessary boilerplate for giving the WASM the table of exported JS functions and importing required WASM functions from the WASM module. Furthermore, it also tries performs minification so that the generated JS file is small (although it can certainly be smaller, more work is required in this area!).

An example C application that can use the exported JS function is:

void printValueToConsole(void);

__attribute__((visibility("default")))
float getValueFromWasm(void) {
  return 42.0f;
}

int main(void) {
  printValueToConsole();
  return 0;
}

The DSL compiler also utilizes the wasm imports table to perform dead-code elimination, such that the generated JS file will only contain the JS code that the wasm file requires.