├── 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 | ![assets](assets/wasm-sdk.png) 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 | ![Diagram](assets/diagram.png) 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 | ![Value loading returning null](assets/demo-react-hooks-loading.png) 120 | 121 | ###### Instance loaded (wasm export object as value) 122 | 123 | ![Value loading returning instance object](assets/demo-react-hooks-loaded.png) 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 | { 178 | if (filter >= FILTER_LIMIT) { 179 | setFilter(0); 180 | return; 181 | } 182 | 183 | setFilter(filter + 1); 184 | }} /> 185 |
186 | raphamorim 8m 187 |
188 |
189 |

isWasmEnabled: {String(isWasmEnabled())}

190 |

_doubler: {String(instance && instance._doubler(2))}

191 |
192 |
193 | ); 194 | } 195 | 196 | render(, document.querySelector('#root')); 197 | --------------------------------------------------------------------------------