├── CHANGELOG.md
├── docs
├── image.jpg
├── doubler.wasm
├── profile.jpeg
├── babel.config.js
├── xwasm.config.js
├── doubler.cpp
├── index.html
├── package.json
├── webpack.config.babel.js
├── wa.c
├── styles.css
└── App.js
├── xwasm
├── README.md
├── test_emcc.sh
├── package.json
├── build.js
├── scripts
│ └── install_emscripten.sh
├── install.js
└── index.js
├── assets
├── diagram.png
├── wasm-sdk.png
├── wasm-sdk-logo.png
├── demo-react-hooks-loaded.png
└── demo-react-hooks-loading.png
├── use-wasm
├── README.md
├── package.json
├── webpack.config.babel.js
├── index.js
└── use-wasm.min.js
├── .gitignore
├── lerna.json
├── scripts
└── create-project.sh
├── .editorconfig
├── package.json
├── LICENSE.md
├── .circleci
└── config.yml
└── README.md
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raphamorim/xwasm/HEAD/docs/image.jpg
--------------------------------------------------------------------------------
/xwasm/README.md:
--------------------------------------------------------------------------------
1 | # Emscripten
2 |
3 | Docs: https://github.com/raphamorim/xwasm
4 |
--------------------------------------------------------------------------------
/assets/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raphamorim/xwasm/HEAD/assets/diagram.png
--------------------------------------------------------------------------------
/docs/doubler.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raphamorim/xwasm/HEAD/docs/doubler.wasm
--------------------------------------------------------------------------------
/docs/profile.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raphamorim/xwasm/HEAD/docs/profile.jpeg
--------------------------------------------------------------------------------
/use-wasm/README.md:
--------------------------------------------------------------------------------
1 | # Emscripten
2 |
3 | Docs: https://github.com/raphamorim/xwasm
4 |
--------------------------------------------------------------------------------
/assets/wasm-sdk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raphamorim/xwasm/HEAD/assets/wasm-sdk.png
--------------------------------------------------------------------------------
/assets/wasm-sdk-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raphamorim/xwasm/HEAD/assets/wasm-sdk-logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | *.log
4 | *.map
5 | .vscode
6 |
7 | # debug purposes
8 | emsdk
9 |
--------------------------------------------------------------------------------
/docs/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-env', '@babel/preset-react']
3 | };
4 |
--------------------------------------------------------------------------------
/assets/demo-react-hooks-loaded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raphamorim/xwasm/HEAD/assets/demo-react-hooks-loaded.png
--------------------------------------------------------------------------------
/assets/demo-react-hooks-loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raphamorim/xwasm/HEAD/assets/demo-react-hooks-loading.png
--------------------------------------------------------------------------------
/xwasm/test_emcc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | node ./index.js install
4 |
5 | emcc --version
6 | if [ $? -eq 0 ]; then
7 | exit 0
8 | else
9 | exit 1
10 | fi
11 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "3.10.8",
3 | "packages": [
4 | "babel-plugin-wasm",
5 | "use-wasm",
6 | "emscripten"
7 | ],
8 | "version": "0.0.2"
9 | }
10 |
--------------------------------------------------------------------------------
/scripts/create-project.sh:
--------------------------------------------------------------------------------
1 | git clone --depth 1 https://github.com/raphamorim/xwasm.git
2 | mv ./xwasm/docs ./wasm-app
3 | rm -rf ./xwasm
4 | cd ./wasm-app && yarn && yarn start
5 |
--------------------------------------------------------------------------------
/docs/xwasm.config.js:
--------------------------------------------------------------------------------
1 | const filesToProcess = [
2 | {
3 | input: 'doubler.c',
4 | output: 'doubler.wasm',
5 | functions: '["_doubler"]'
6 | }
7 | ]
8 |
9 | module.exports = filesToProcess;
10 |
--------------------------------------------------------------------------------
/use-wasm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-wasm",
3 | "version": "0.0.3",
4 | "description": "WASM React Hook",
5 | "main": "useWasm.min.js",
6 | "scripts": {
7 | "build": "webpack"
8 | },
9 | "keywords": ["hook", "use", "emscripten", "emcc", "sdk", "wasm", "webassembly", "react"],
10 | "author": "Raphael Amorim",
11 | "license": "MIT"
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | max_line_length = 80
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | max_line_length = 0
15 | trim_trailing_whitespace = false
16 |
17 | [COMMIT_EDITMSG]
18 | max_line_length = 0
19 |
--------------------------------------------------------------------------------
/docs/doubler.cpp:
--------------------------------------------------------------------------------
1 | int doubler(int x) {
2 | return 2 * x;
3 | }
4 |
5 | // float doubler(float *buffer) {
6 | // for (int i = 0; i < pixels.length; i += 4) {
7 | // int red = pixels[i];
8 | // int green = pixels[i + 1];
9 | // int blue = pixels[i + 2];
10 | // int alpha = pixels[i + 3];
11 |
12 | // pixels[i] = 0.2 * red;
13 | // pixels[i + 1] = 1 * green;
14 | // pixels[i + 2] = 1 * blue;
15 | // pixels[i + 3] = 1 * alpha;
16 | // }
17 |
18 | // return pixels;
19 | // }
20 |
21 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WASM
8 |
9 |
10 |
11 |
12 |
13 |

14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/xwasm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "preferGlobal": true,
3 | "name": "xwasm",
4 | "emscripten": "1.38.34",
5 | "version": "0.0.2-beta",
6 | "description": "Packager for WASM",
7 | "keywords": ["node", "xwasm", "emscripten", "emcc", "sdk", "wasm", "webassembly"],
8 | "author": "Raphael Amorim ",
9 | "scripts": {
10 | },
11 | "bin": {
12 | "xwasm": "index.js"
13 | },
14 | "files": [
15 | "scripts/",
16 | "install.js",
17 | "build.js",
18 | "index.js"
19 | ],
20 | "license": "MIT"
21 | }
22 |
--------------------------------------------------------------------------------
/xwasm/build.js:
--------------------------------------------------------------------------------
1 | function Build(rootPath, filePath = '', exportedFunctions, output) {
2 | const { spawn } = require('child_process');
3 |
4 | // exported_functions: `['_doubler']`
5 | const child = spawn(`emcc ${filePath} -Os -s WASM=1 -s SIDE_MODULE=1 -s EXPORTED_FUNCTIONS="${exportedFunctions}" -o ${ output || filePath.split('.')[0] + '.wasm' }`, {
6 | shell: true,
7 | cwd: rootPath
8 | });
9 |
10 | child.stdout.setEncoding('utf8');
11 |
12 | child.stdout.pipe(process.stdout);
13 | child.stderr.pipe(process.stderr);
14 | }
15 |
16 | module.exports = Build;
17 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground",
3 | "version": "0.0.2",
4 | "license": "MIT",
5 | "scripts": {
6 | "build": "webpack",
7 | "start": "webpack-dev-server"
8 | },
9 | "devDependencies": {
10 | "webpack": "^4.29.3",
11 | "webpack-cli": "^3.2.3",
12 | "webpack-dev-server": ">=3.1.11",
13 | "use-wasm": "^0.0.3",
14 | "xwasm": "^0.0.2-beta",
15 | "@babel/core": "^7.0.0",
16 | "babel-loader": "^8.0.5",
17 | "@babel/preset-env": "^7.1.3",
18 | "@babel/preset-react": "^7.0.0",
19 | "react": "^16.8.6",
20 | "react-dom": "^16.8.6"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "use-wasm",
5 | "docs",
6 | "babel-wasm",
7 | "emscripten"
8 | ],
9 | "author": "Raphael Amorim",
10 | "license": "MIT",
11 | "main": "index.js",
12 | "scripts": {
13 | "run-docs": "cd docs && yarn start",
14 | "build-use-wasm": "cd use-wasm && yarn build",
15 | "install-sdk": "node ./emscripten install",
16 | "test": "echo 'done'"
17 | },
18 | "devDependencies": {
19 | "@babel/core": "^7.0.0",
20 | "lerna": "^3.10.8",
21 | "babel-loader": "^8.0.5",
22 | "@babel/preset-env": "^7.1.3",
23 | "@babel/preset-react": "^7.0.0",
24 | "react": "^16.8.6",
25 | "react-dom": "^16.8.6"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/xwasm/scripts/install_emscripten.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | osx_install_emcc () {
4 | git clone https://github.com/emscripten-core/emsdk.git ./emsdk
5 |
6 | chmod +x ./emsdk
7 |
8 | # Enter that directory
9 | # Fetch the latest version of the emsdk
10 | # Use 1.38.34 (https://github.com/emscripten-core/emsdk/commit/188c3edb9b7edafeaef4f4658a19dbcd99e81a37)
11 | cd emsdk && git pull && git checkout 188c3edb9b7edafeaef4f4658a19dbcd99e81a37
12 |
13 | # Download and install the latest SDK tools.
14 | ./emsdk install 1.38.34 # ./emsdk install latest
15 |
16 | # Make the "latest" SDK "active" for the current user. (writes ~/.emscripten file)
17 | ./emsdk activate 1.38.34 # ./emsdk install latest
18 |
19 | # Activate PATH and other environment variables in the current terminal
20 | source ./emsdk_env.sh
21 | }
22 |
23 | emcc --version
24 | if [ $? -eq 0 ]; then
25 | echo 'emcc: checked'
26 | else
27 | osx_install_emcc
28 | fi
29 |
--------------------------------------------------------------------------------
/xwasm/install.js:
--------------------------------------------------------------------------------
1 | function InstallEmscripten(rootPath) {
2 | const path = require('path');
3 | const fs = require('fs');
4 | const { spawn } = require('child_process');
5 |
6 | console.log('Installing SDK in:', process.cwd())
7 |
8 | // emscripten/scripts/install_emscripten.sh (DEBUG/DEV cases only)
9 | const scriptPath = fs.existsSync(
10 | path.resolve(rootPath, './scripts/install_emscripten.sh')
11 | ) ? path.resolve(rootPath, './scripts/install_emscripten.sh') :
12 | path.resolve(rootPath, './emscripten/scripts/install_emscripten.sh')
13 |
14 | const child = spawn(`sh ${scriptPath}`, {
15 | shell: true,
16 | });
17 |
18 | child.stdout.setEncoding('utf8');
19 |
20 | child.stdout.pipe(process.stdout);
21 | child.stderr.pipe(process.stderr);
22 |
23 | child.on('close', (code) => {
24 | console.log(`[emscripten] process exited with code ${code}`);
25 | });
26 | }
27 |
28 | module.exports = InstallEmscripten;
29 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-present, Raphael Amorim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/docs/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const webpack = require('webpack');
4 |
5 | const config = {
6 | mode: 'development',
7 | entry: path.join(__dirname, 'App.js'),
8 | output: {
9 | path: __dirname,
10 | filename: 'bundle.js',
11 | publicPath: '/',
12 | },
13 | resolve: {
14 | extensions: ['.js', '.jsx'],
15 | modules: [
16 | path.resolve(__dirname, 'node_modules')
17 | ],
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.(js|jsx)$/,
23 | exclude: /node_modules/,
24 | use: ['babel-loader'],
25 | },
26 | ],
27 | },
28 | plugins: [new webpack.NamedModulesPlugin()],
29 | devServer: {
30 | compress: false,
31 | host: '0.0.0.0',
32 | open: true,
33 | port: 9000,
34 | historyApiFallback: {
35 | index: path.join(__dirname, 'index.html'),
36 | },
37 | },
38 | };
39 |
40 | // [optional] yarn-workspaces if needed
41 | const workpacesPath = path.resolve(__dirname, '../node_modules');
42 | if (fs.existsSync(workpacesPath)) {
43 | config.resolve.modules.push(workpacesPath);
44 | }
45 |
46 | module.exports = config;
47 |
--------------------------------------------------------------------------------
/xwasm/index.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | const argv = process.argv;
4 | const path = require('path');
5 |
6 | function guide() {
7 | console.log('\nAvailable commands: install, build, version\n Docs: https://github.com/raphamorim/wasm')
8 | }
9 |
10 | if (argv.length < 2) {
11 | guide();
12 | }
13 |
14 | const command = argv[2];
15 | const pck = require('./package.json');
16 |
17 | switch (command) {
18 | case 'install':
19 | require('./install')(process.cwd());
20 | break;
21 |
22 | case 'build':
23 | if (!argv[3] || !argv[4] || !argv[5]) {
24 | const pkgPath = path.resolve(process.cwd(), 'xwasm.config.js');
25 | console.log('loading from ' + pkgPath);
26 | const build = require('./build');
27 | const files = require(pkgPath).forEach(file => {
28 | build(process.cwd(), file.input, file.functions, file.output)
29 | })
30 |
31 | return 0;
32 | }
33 |
34 | require('./build')(process.cwd(), argv[3], argv[4], argv[5])
35 | break;
36 |
37 | case 'check':
38 | console.log('Emscripten Version: ' + pck.emscripten);
39 | break;
40 |
41 | case 'version':
42 | console.log('CLI Version: ' + pck.version);
43 | break;
44 |
45 | default:
46 | guide();
47 | }
48 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | aliases:
3 | build_config: &build_config
4 | environment:
5 | BASH_ENV: ~/.bashrc
6 |
7 | steps:
8 | - checkout
9 |
10 | # Versions Check
11 | - run:
12 | name: Node version
13 | command: node -v
14 |
15 | - run:
16 | name: EMCC version
17 | command: emcc --version
18 |
19 | - run:
20 | name: Install Yarn
21 | command: curl -o- -L https://yarnpkg.com/install.sh | bash
22 |
23 | - run:
24 | name: Yarn version
25 | command: yarn --version
26 |
27 | - restore_cache:
28 | keys:
29 | - v1-dependencies-{{ checksum "package.json" }}
30 | - v1-dependencies-
31 |
32 | - run:
33 | name: Install base packages
34 | command: yarn install
35 |
36 | - save_cache:
37 | paths:
38 | - node_modules
39 | key: v1-dependencies-{{ checksum "package.json" }}
40 |
41 | - run:
42 | type: shell
43 | name: Test Emscripten-CLI and Install SDK
44 | command: cd emscripten && source ./test_emcc.sh
45 |
46 | - run:
47 | name: Lint and Test packages
48 | command: yarn test
49 |
50 | jobs:
51 | build:
52 | docker:
53 | - image: trzeci/emscripten:sdk-tag-1.38.32-64bit
54 | <<: *build_config
55 |
--------------------------------------------------------------------------------
/use-wasm/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | const sourcePath = path.join(__dirname, 'index.js');
5 |
6 | const config = {
7 | target: 'web',
8 | mode: 'production',
9 | entry: sourcePath,
10 | output: {
11 | path: __dirname,
12 | library: 'use-wasm',
13 | libraryTarget: 'umd',
14 | umdNamedDefine: true,
15 | filename: 'use-wasm.min.js',
16 | },
17 | externals: {
18 | // Use external version of React
19 | react: {
20 | root: 'React',
21 | commonjs2: 'react',
22 | commonjs: 'react',
23 | amd: 'react'
24 | }
25 | },
26 | resolve: {
27 | extensions: ['.js', '.jsx'],
28 | modules: [
29 | sourcePath,
30 | path.resolve(__dirname, 'node_modules'),
31 | path.resolve(__dirname, '../node_modules')
32 | ],
33 | },
34 | module: {
35 | rules: [
36 | {
37 | test: /\.(js|jsx)$/,
38 | exclude: /node_modules/,
39 | use: ['babel-loader'],
40 | include: [sourcePath, path.join(__dirname, 'src')],
41 | },
42 | ],
43 | },
44 | plugins: [],
45 | optimization: {
46 | minimize: true
47 | }
48 | };
49 |
50 | config.plugins.push(
51 | new webpack.DefinePlugin({
52 | 'process.env.NODE_ENV': JSON.stringify('production'),
53 | })
54 | );
55 | config.plugins.push(new webpack.optimize.ModuleConcatenationPlugin());
56 | config.plugins.push(new webpack.HashedModuleIdsPlugin());
57 |
58 | module.exports = config;
59 |
--------------------------------------------------------------------------------
/use-wasm/index.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | function useWasm(filename) {
4 | const filepath = filename + '.wasm';
5 | const [ instance, setInstance ] = useState(null);
6 |
7 | useEffect(() => {
8 | if (instance) {
9 | return;
10 | }
11 |
12 | const memory = new WebAssembly.Memory({
13 | initial: 256
14 | });
15 | const importObj = {
16 | env: {
17 | abortStackOverflow: () => {
18 | throw new Error('overflow');
19 | },
20 | table: new WebAssembly.Table({
21 | initial: 0,
22 | element: 'anyfunc'
23 | }),
24 | __table_base: 0,
25 | memory: memory,
26 | __memory_base: 1024,
27 | STACKTOP: 0,
28 | STACK_MAX: memory.buffer.byteLength,
29 | }
30 | };
31 |
32 | if (typeof WebAssembly.instantiateStreaming === 'function') {
33 | WebAssembly.instantiateStreaming(fetch(filepath), importObj)
34 | .then(results => {
35 | setInstance(results.instance.exports)
36 | });
37 | } else {
38 | fetch(filepath).then(response =>
39 | response.arrayBuffer()
40 | ).then(bytes =>
41 | WebAssembly.instantiate(bytes, importObj)
42 | ).then(results => {
43 | setInstance(results.instance.exports)
44 | });
45 | }
46 | }, []);
47 |
48 | return {
49 | isWasmEnabled: () => {
50 | return 'WebAssembly' in window
51 | },
52 | instance
53 | }
54 | }
55 |
56 | export default useWasm;
57 |
--------------------------------------------------------------------------------
/use-wasm/use-wasm.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define("use-wasm",["react"],t):"object"==typeof exports?exports["use-wasm"]=t(require("react")):e["use-wasm"]=t(e.React)}(window,function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s="QfWi")}({QfWi:function(e,t,n){"use strict";n.r(t);var r=n("cDcd");t.default=function(e){const t=e+".wasm",[n,o]=Object(r.useState)(null);return Object(r.useEffect)(()=>{if(n)return;const e=new WebAssembly.Memory({initial:256}),r={env:{abortStackOverflow:()=>{throw new Error("overflow")},table:new WebAssembly.Table({initial:0,element:"anyfunc"}),__table_base:0,memory:e,__memory_base:1024,STACKTOP:0,STACK_MAX:e.buffer.byteLength}};"function"==typeof WebAssembly.instantiateStreaming?WebAssembly.instantiateStreaming(fetch(t),r).then(e=>{o(e.instance.exports)}):fetch(t).then(e=>e.arrayBuffer()).then(e=>WebAssembly.instantiate(e,r)).then(e=>{o(e.instance.exports)})},[]),{isWasmEnabled:()=>"WebAssembly"in window,instance:n}}},cDcd:function(t,n){t.exports=e}})});
--------------------------------------------------------------------------------
/docs/wa.c:
--------------------------------------------------------------------------------
1 | #define PI 3.14159265358979323846
2 | #define RAD 6.283185307179586
3 | #define COEFF_1 0.7853981633974483
4 | #define COEFF_2 2.356194490192345
5 | #define BLADES 3
6 | #define CYCLE_WIDTH 100
7 | #define BLADES_T_CYCLE_WIDTH 300
8 | #include
9 | #include
10 | #include
11 |
12 | int height;
13 | int width;
14 | int pixelCount;
15 | int ch;
16 | int cw;
17 | double maxDistance;
18 |
19 | int data[2000000];
20 |
21 | int* EMSCRIPTEN_KEEPALIVE init(int cWidth, int cHeight) {
22 | width = cWidth;
23 | height = cHeight;
24 | pixelCount = width * height;
25 | ch = height >> 1;
26 | cw = width >> 1;
27 | maxDistance = sqrt(ch * ch + cw * cw);
28 | return &data[0];
29 | }
30 |
31 | double customAtan2(int y, int x) {
32 | double abs_y = abs(y) + 1e-10;
33 | double angle;
34 | if (x >= 0) {
35 | double r = (x - abs_y) / (x + abs_y);
36 | angle = 0.1963 * r * r * r - 0.9817 * r + COEFF_1;
37 | } else {
38 | double r = (x + abs_y) / (abs_y - x);
39 | angle = 0.1963 * r * r * r - 0.9817 * r + COEFF_2;
40 | }
41 | return y < 0 ? -angle : angle;
42 | }
43 |
44 | double customFmod(double a, double b)
45 | {
46 | return (a - b * floor(a / b));
47 | }
48 |
49 | void EMSCRIPTEN_KEEPALIVE render(double timestamp) {
50 | int scaledTimestamp = floor(timestamp / 10.0 + 2000.0);
51 | for (int y = 0; y < height; y++) {
52 | int dy = ch - y;
53 | int dysq = dy * dy;
54 | int yw = y * width;
55 | for (int x = 0; x < width; x++) {
56 | int dx = cw - x;
57 | int dxsq = dx * dx;
58 | double angle = customAtan2(dx, dy) / RAD;
59 | int asbs = dxsq + dysq;
60 | double distanceFromCenter = sqrt(asbs);
61 | double scaledDistance = asbs / 400.0 + distanceFromCenter;
62 | double lerp = 1.0 - (customFmod(fabs(scaledDistance - scaledTimestamp + angle * BLADES_T_CYCLE_WIDTH), CYCLE_WIDTH)) / CYCLE_WIDTH;
63 | double absoluteDistanceRatioGB = 1.0 - distanceFromCenter / maxDistance;
64 | double absoluteDistanceRatioR = absoluteDistanceRatioGB * 0.8 + 0.2;
65 | int fadeB = round(50.0 * lerp * absoluteDistanceRatioGB);
66 | int fadeR = round(240.0 * lerp * absoluteDistanceRatioR * (1.0 + lerp) / 2.0);
67 | int fadeG = round(120.0 * lerp * lerp * lerp * absoluteDistanceRatioGB);
68 | data[yw + x] =
69 | (255 << 24) | // A
70 | (fadeB << 16) | // B
71 | (fadeG << 8) | // G
72 | fadeR; // R
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/docs/styles.css:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-size: 100%;
18 | font: inherit;
19 | vertical-align: baseline;
20 | }
21 | /* HTML5 display-role reset for older browsers */
22 | article, aside, details, figcaption, figure,
23 | footer, header, hgroup, menu, nav, section {
24 | display: block;
25 | }
26 |
27 | body {
28 | line-height: 1;
29 | overflow: hidden;
30 | width: 100%;
31 | height: 100%;
32 | background-color: black;
33 | display: flex;
34 | justify-content: center;
35 | }
36 |
37 | canvas {
38 | width: 100%;
39 | max-width: 600px;
40 | height: 100%;
41 | overflow: hidden;
42 | position: absolute;
43 | top: 0;
44 | left: calc(50% - (600px / 2));
45 | }
46 |
47 | @media screen and (max-width: 680px) {
48 | canvas {
49 | max-width: 100%;
50 | left: 0;
51 | }
52 | }
53 |
54 | .debug {
55 | position: absolute;
56 | left: 50%;
57 | bottom: 15px;
58 | background: black;
59 | opacity: 0.5;
60 | color: white;
61 | font-size: 30px;
62 | padding: 15px;
63 | }
64 |
65 | @media screen and (max-width: 680px) {
66 | .debug {
67 | left: 15px;
68 | }
69 | }
70 |
71 | .profile {
72 | position: absolute;
73 | top: 10px;
74 | left: calc(50% - (600px / 2));
75 | width: 100%;
76 | max-width: 600px;
77 | box-sizing: border-box;
78 | padding: 20px;
79 | color: white;
80 | font-family: helvetica;
81 | display: flex;
82 | font-size: 20 px;
83 | align-items: center;
84 | }
85 |
86 | @media screen and (max-width: 680px) {
87 | .profile {
88 | left: 0;
89 | }
90 | }
91 |
92 | .profile::before {
93 | position: absolute;
94 | content: '';
95 | background: grey;
96 | width: 100%;
97 | opacity: 0.5;
98 | height: 5px;
99 | top: -5px;
100 | left: 0;
101 | }
102 |
103 | .profile span {
104 | margin-left: 10px;
105 | opacity: 0.6;
106 | }
107 |
108 | .profile img {
109 | width: 40px;
110 | height: 40px;
111 | border-radius: 40%;
112 | margin-right: 10px;
113 | }
114 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | This repository contain tools for develop modern frontend with WebAssembly (React, Vue, Babel and etecetera).
4 |
5 | Please don't use it in production. It's not stable yet.
6 |
7 | 
8 |
9 | #### Create a project with WASM in less than 5 minutes (optional)
10 |
11 | ```
12 | curl -o- -L https://raw.githubusercontent.com/raphamorim/xwasm/master/scripts/create-project.sh | bash
13 | ```
14 |
15 | ## Supported Languages
16 |
17 | | Language | Status | Notes |
18 | | :--- | :--- | :--- |
19 | | C++ | Under development | Still very experimental |
20 | | Rust | Under development | Test phase |
21 | | Go | On Roadmap | - |
22 | | Kotlin | On Roadmap | - |
23 | | Lua | On Roadmap | - |
24 |
25 | #### Summary
26 |
27 | - [`xwasm` Packager to WebAssembly](#xwasm)
28 | - [`useWasm` React Hook for load WASM files](#usewasm)
29 | - [FAQ](#faq)
30 | - [TODO](#todo)
31 | - [References](#references)
32 |
33 | ## `xwasm`
34 |
35 | WebAssembly Packager (understand Rust/C/C++).
36 |
37 | It will install modules/environment on demand. However you can run separate commands to install enviroment:
38 |
39 | - `$ xwasm install cpp` (install C/C++ required dependencies [emcc])
40 |
41 | - `$ xwasm check cpp` (check C/C++ dependencies status)
42 |
43 | - `$ xwasm install rust` (install Rust required dependencies [cargo])
44 |
45 | - `$ xwasm check rust` (check C/C++ dependencies status)
46 |
47 | #### Building with
48 |
49 | 1. Create a file: `xwasm.config.js`
50 |
51 | ```jsx
52 | const filesToProcess = [
53 | {
54 | input: 'doubler.c',
55 | output: 'doubler.wasm',
56 | functions: ['doubler'] // functions that you want to export
57 | },
58 | {
59 | // by default output will follow input filename, in this case: "counter.wasm"
60 | input: 'counter.rs',
61 | functions: ['counter'] // functions that you want to export
62 | }
63 | ]
64 |
65 | module.exports = filesToProcess;
66 | ```
67 |
68 | 2. Now if you run `xwasm`, it's going to load the configuration above. If you want to, you can add it before any build task. For example:
69 |
70 | ```json
71 | "scripts": {
72 | "build": "xwasm && webpack",
73 | ```
74 |
75 | ## `useWasm`
76 |
77 | ### Installing
78 |
79 | ```bash
80 | $ npm install use-wasm
81 | ```
82 |
83 | ### Usage
84 |
85 | C++ code
86 |
87 | ```cpp
88 | int _doubler(int x) {
89 | return 2 * x;
90 | }
91 | ```
92 |
93 | JSX code with React
94 |
95 | ```jsx
96 |
97 | import React, { Fragment, Component } from 'react';
98 | import { render } from 'react-dom';
99 | import useWasm from 'use-wasm';
100 |
101 | function App() {
102 | // method will initialize null til load the "./doubler.wasm"
103 | const { isWasmEnabled, instance } = useWasm('doubler');
104 |
105 | return (
106 |
107 | isWasmEnabled: {String(isWasmEnabled())}
108 | _doubler: {String(instance && instance._doubler(2))}
109 |
110 | );
111 | }
112 |
113 | render(, document.querySelector('#root'));
114 |
115 | ```
116 |
117 | ###### Instance loading (`null` as initial value)
118 |
119 | 
120 |
121 | ###### Instance loaded (wasm export object as value)
122 |
123 | 
124 |
125 | ## TODO
126 |
127 | - [ ] useWasm: Cache logic for fetching WASM files
128 | - [ ] xwasm/emscripten: Cache for build
129 | - [ ] xwasm/emscripten: Add support for Windows
130 | - [ ] xwasm/emscripten: Add support for load different files into one export
131 | - [ ] Write examples using Rust
132 |
133 | ## References
134 |
135 | - https://webassembly.org/getting-started/developers-guide
136 | - https://emscripten.org/docs/compiling/WebAssembly.html
137 | - https://developer.mozilla.org/en-US/docs/WebAssembly
138 | - https://developer.mozilla.org/en-US/docs/WebAssembly/C_to_wasm
139 | - https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API
140 | - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory
141 | - https://github.com/emscripten-core/emscripten/issues/8126
142 |
--------------------------------------------------------------------------------
/docs/App.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from 'react';
2 | import { render } from 'react-dom';
3 | import useWasm from '../use-wasm/index.js';
4 |
5 | const FILTER_LIMIT = 4;
6 |
7 | export function scaleDPI(canvas, context, customWidth, customHeight) {
8 | const devicePixelRatio = window.devicePixelRatio || 1;
9 |
10 | const backingStorePixelRatio =
11 | context.webkitBackingStorePixelRatio ||
12 | context.mozBackingStorePixelRatio ||
13 | context.msBackingStorePixelRatio ||
14 | context.oBackingStorePixelRatio ||
15 | context.backingStorePixelRatio ||
16 | 1;
17 |
18 | const ratio = devicePixelRatio / backingStorePixelRatio;
19 |
20 | const width =
21 | customWidth || canvas.offsetWidth || canvas.width || canvas.clientWidth;
22 | const height =
23 | customHeight || canvas.offsetHeight || canvas.height || canvas.clientHeight;
24 | canvas.width = Math.round(width * ratio);
25 | canvas.height = Math.round(height * ratio);
26 |
27 | if (devicePixelRatio !== backingStorePixelRatio) {
28 | canvas.style.width = `${width}px`;
29 | canvas.style.height = `${height}px`;
30 | context.scale(ratio, ratio);
31 | }
32 |
33 | return ratio;
34 | }
35 |
36 | function setImage(filter, instance) {
37 | const canvas = document.getElementById('preview');
38 | const ctx = canvas.getContext('2d');
39 | const image = document.getElementById('source');
40 | scaleDPI(canvas, ctx);
41 | ctx.drawImage(image, 0, 0);
42 |
43 | switch(filter){
44 | case 0:
45 | // noop
46 | break;
47 | case 1:
48 | processBenjamin(canvas, ctx);
49 | break;
50 | case 2:
51 | processRaphael(canvas, ctx);
52 | break;
53 | case 3:
54 | processChris(canvas, ctx);
55 | break;
56 | case 4:
57 | newProcessWasm(canvas, ctx, instance);
58 | break;
59 | }
60 | }
61 |
62 | function newProcessWasm(canvas, context, instance) {
63 | const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
64 | const doubler = instance && instance._doubler;
65 | let data = imageData.data;
66 |
67 | const value = doubler(Math.floor(Math.random() * 40) + 120);
68 | for(let i = 0; i < data.length; i += 4) {
69 | data[i] = value - data[i];
70 | data[i + 1] = value - data[i + 1];
71 | data[i + 2] = value - data[i + 2];
72 | }
73 |
74 | context.putImageData(imageData, 0, 0);
75 | }
76 |
77 | function processWasm(canvas, ctx, instance) {
78 | let buffer;
79 | let error;
80 | let result;
81 |
82 | const Module = instance.module;
83 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
84 | const pixels = imageData.data;
85 | const typedArray = new Float32Array(pixels.length);
86 |
87 | for (let i=0; i> 2);
93 | result = Module.ccall("render", null, ["number", "number"], [buffer, arrayDataToPass.length]);
94 | }
95 |
96 | const sepia = (imageData, adj) => {
97 | let pixels = imageData.data;
98 | for (let i = 0; i < pixels.length; i += 4) {
99 | let r = pixels[i], g = pixels[i + 1], b = pixels[i + 2];
100 | pixels[i] = (r * (1 - (0.607 * adj))) + (g * .769 * adj) + (b * .189 * adj);
101 | pixels[i + 1] = (r * .349 * adj) + (g * (1 - (0.314 * adj))) + (b * .168 * adj);
102 | pixels[i + 2] = (r * .272 * adj) + (g * .534 * adj) + (b * (1 - (0.869 * adj)));
103 | }
104 | return imageData;
105 | }
106 |
107 | function processChris(canvas, context) {
108 | const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
109 | let pixels = imageData.data;
110 |
111 | for (var i = 0, n = pixels.length; i < n; i += 4) {
112 | const grayscale = pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
113 | pixels[i] = grayscale;
114 | pixels[i+1] = grayscale;
115 | pixels[i+2] = grayscale;
116 | }
117 |
118 | context.putImageData(imageData, 0, 0);
119 | };
120 |
121 | const contrast = (imageData, adj) => {
122 | let pixels = imageData.data;
123 | adj *= 255;
124 | let factor = (259 * (adj + 255)) / (255 * (259 - adj));
125 | for (let i = 0; i < pixels.length; i += 4) {
126 | pixels[i] = factor * (pixels[i] - 128) + 128;
127 | pixels[i + 1] = factor * (pixels[i + 1] - 128) + 128;
128 | pixels[i + 2] = factor * (pixels[i + 2] - 128) + 128;
129 | }
130 | return imageData;
131 | }
132 |
133 | function processRaphael(canvas, context) {
134 | const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
135 | const pixels = imageData.data;
136 |
137 | for (let i = 0; i < pixels.length; i += 4) {
138 | const red = pixels[i];
139 | const green = pixels[i + 1];
140 | const blue = pixels[i + 2];
141 | const alpha = pixels[i + 3];
142 |
143 | pixels[i] = 0.2 * red;
144 | pixels[i + 1] = 1 * green;
145 | pixels[i + 2] = 1 * blue;
146 | pixels[i + 3] = 1 * alpha;
147 | }
148 |
149 | context.putImageData(imageData, 0, 0);
150 | };
151 |
152 | function processBenjamin(canvas, context) {
153 | const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
154 | let newImageData = imageData;
155 |
156 | newImageData = sepia(newImageData, 0.5);
157 | newImageData = contrast(newImageData, -0.1);
158 |
159 | context.putImageData(newImageData, 0, 0);
160 | };
161 |
162 | // 0 default
163 | // 1 benjamin
164 | // 2 raphael
165 | // 3 wasm
166 |
167 | function App() {
168 | const { isWasmEnabled, instance } = useWasm('doubler');
169 | const [ filter, setFilter ] = useState(0);
170 |
171 | useEffect(() => {
172 | setImage(filter, instance);
173 | }, [filter]);
174 |
175 | return (
176 |
177 |
193 | );
194 | }
195 |
196 | render(, document.querySelector('#root'));
197 |
--------------------------------------------------------------------------------