WebAssembly (Wasm)简介

最近做动画渲染的时候有个诉求是需要把Wasm能力集成至 IOS 客户端,于是决定写篇博客学习下 WebAssembly 相关的知识。

WebAssembly(简称 Wasm)是一种平台无关的二进制指令格式,被设计为在现代浏览器中高效、安全地运行接近本地性能的代码。Wasm 的出现为 Web 开发带来了新的可能性,尤其是在性能要求高的应用场景中。本文将详细介绍 WebAssembly 的应用方向、工作原理,并为每个方向提供实例。

WebAssembly 的主要应用方向

  1. 计算密集型任务
  2. 游戏开发
  3. 应用移植
  4. 多媒体处理
  5. 安全和加密

1. 计算密集型任务

应用场景:需要大量计算的任务,例如图像处理、复杂数学计算、科学计算和数据分析等。

原理:传统的 JavaScript 在处理大量计算时性能可能不尽如人意。而通过 WebAssembly,可以将这些计算密集型任务交给 C/C++ 等高性能语言编写的代码来执行,从而提高性能。

实例:用 WebAssembly 进行图像灰度处理。

编写 C 代码(image_grayscale.c)

c 复制代码
// image_grayscale.c
#include <stdint.h>

void grayscale(uint8_t* image, int32_t width, int32_t height) {
    for (int32_t i = 0; i < width * height * 4; i += 4) {
        uint8_t r = image[i];
        uint8_t g = image[i + 1];
        uint8_t b = image[i + 2];
        uint8_t gray = (r + g + b) / 3;
        image[i] = gray;
        image[i + 1] = gray;
        image[i + 2] = gray;
    }
}

编译为 WebAssembly

使用 Emscripten 编译:

shell 复制代码
emcc image_grayscale.c -s WASM=1 -o image_grayscale.js

HTML 和 JavaScript

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly Image Grayscale Example</title>
</head>
<body>
    <input type="file" id="upload" />
    <canvas id="canvas"></canvas>
    <script>
        let Module = {
            onRuntimeInitialized: function() {
                const upload = document.getElementById('upload');
                const canvas = document.getElementById('canvas');
                const ctx = canvas.getContext('2d');

                upload.addEventListener('change', (e) => {
                    const file = e.target.files[0];
                    const reader = new FileReader();
                    reader.onload = (event) => {
                        const img = new Image();
                        img.onload = () => {
                            canvas.width = img.width;
                            canvas.height = img.height;
                            ctx.drawImage(img, 0, 0);
                            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

                            const ptr = Module._malloc(imageData.data.length);
                            Module.HEAPU8.set(imageData.data, ptr);
                            Module._grayscale(ptr, canvas.width, canvas.height);
                            imageData.data.set(Module.HEAPU8.subarray(ptr, ptr + imageData.data.length));
                            Module._free(ptr);

                            ctx.putImageData(imageData, 0, 0);
                        };
                        img.src = event.target.result;
                    };
                    reader.readAsDataURL(file);
                });
            }
        };

        fetch('image_grayscale.wasm').then(response =>
            response.arrayBuffer()
        ).then(bytes =>
            WebAssembly.instantiate(bytes, {
                env: {
                    memoryBase: 0,
                    tableBase: 0,
                    memory: new WebAssembly.Memory({ initial: 256 }),
                    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
                }
            })
        ).then(results => {
            Module.wasmModule = results.module;
            Module.wasmInstance = results.instance;
            Module._grayscale = Module.wasmInstance.exports.grayscale;
            Module._malloc = Module.wasmInstance.exports.malloc;
            Module._free = Module.wasmInstance.exports.free;
            Module.HEAPU8 = new Uint8Array(Module.wasmInstance.exports.memory.buffer);
            Module.onRuntimeInitialized();
        });
    </script>
</body>
</html>

2. 游戏开发

应用场景:需要高性能和高响应速度的复杂 3D 游戏和引擎。

原理:通过将游戏引擎核心部分用 C/C++ 等语言编写并编译为 WebAssembly,在浏览器中可以实现接近本地性能的游戏引擎,从而提高游戏性能和用户体验。

实例:用 WebAssembly 驱动简单的 3D 物理引擎。

编写 C++ 代码(physics_engine.cpp)

cpp 复制代码
// physics_engine.cpp
extern "C" {
    void update_positions(float* positions, float* velocities, int num_objects, float dt) {
        for (int i = 0; i < num_objects * 3; i++) {
            positions[i] += velocities[i] * dt;
        }
    }
}

编译为 WebAssembly

使用 Emscripten 编译:

shell 复制代码
emcc physics_engine.cpp -s WASM=1 -o physics_engine.js

HTML 和 JavaScript

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly Physics Engine Example</title>
</head>
<body>
    <script>
        let Module = {
            onRuntimeInitialized: function() {
                const num_objects = 10;
                const positions = new Float32Array(num_objects * 3);
                const velocities = new Float32Array(num_objects * 3);
                for (let i = 0; i < num_objects * 3; i++) {
                    positions[i] = Math.random() * 100;
                    velocities[i] = Math.random() * 10;
                }

                const positionsPtr = Module._malloc(positions.length * positions.BYTES_PER_ELEMENT);
                const velocitiesPtr = Module._malloc(velocities.length * velocities.BYTES_PER_ELEMENT);
                Module.HEAPF32.set(positions, positionsPtr / positions.BYTES_PER_ELEMENT);
                Module.HEAPF32.set(velocities, velocitiesPtr / velocities.BYTES_PER_ELEMENT);

                function update() {
                    Module._update_positions(positionsPtr, velocitiesPtr, num_objects, 0.016);
                    positions.set(Module.HEAPF32.subarray(positionsPtr / positions.BYTES_PER_ELEMENT, positionsPtr / positions.BYTES_PER_ELEMENT + positions.length));

                    // 这里可以将 positions 传递给 3D 渲染引擎进行渲染

                    requestAnimationFrame(update);
                }

                update();
            }
        };

        fetch('physics_engine.wasm').then(response =>
            response.arrayBuffer()
        ).then(bytes =>
            WebAssembly.instantiate(bytes, {
                env: {
                    memoryBase: 0,
                    tableBase: 0,
                    memory: new WebAssembly.Memory({ initial: 256 }),
                    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
                }
            })
        ).then(results => {
            Module.wasmModule = results.module;
            Module.wasmInstance = results.instance;
            Module._update_positions = Module.wasmInstance.exports.update_positions;
            Module._malloc = Module.wasmInstance.exports.malloc;
            Module._free = Module.wasmInstance.exports.free;
            Module.HEAPF32 = new Float32Array(Module.wasmInstance.exports.memory.buffer);
            Module.onRuntimeInitialized();
        });
    </script>
</body>
</html>

3. 应用移植

应用场景:将现有的桌面应用程序通过 WebAssembly 移植到 Web 平台,实现跨平台运行。

原理:利用 WebAssembly,可以将用 C/C++ 等语言编写的桌面应用程序编译为 WebAssembly 模块,这样可以在浏览器中运行这些应用程序。

实例:将一个简单的 C++ 屏幕画图程序移植到 Web。

编写 C++ 代码(drawing_program.cpp)

cpp 复制代码
// drawing_program.cpp
#include <stdint.h>

extern "C" {
    void draw(uint8_t* canvas, int width, int height) {
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                canvas[(y * width + x) * 4 + 0] = x % 255; // R
                canvas[(y * width + x) * 4 + 1] = y % 255; // G
                canvas[(y * width + x) * 4 + 2] = (x + y) % 255; // B
                canvas[(y * width + x) * 4 + 3] = 255; // A
            }
        }
    }
}

编译为 WebAssembly

使用 Emscripten 编译:

shell 复制代码
emcc drawing_program.cpp -s WASM=1 -o drawing_program.js

HTML 和 JavaScript

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly Drawing Program Example</title>
</head>
<body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script>
        let Module = {
            onRuntimeInitialized: function() {
                const canvas = document.getElementById('canvas');
                const ctx = canvas.getContext('2d');
                const imageData = ctx.createImageData(canvas.width, canvas.height);

                const ptr = Module._malloc(imageData.data.length);
                
                Module._draw(ptr, canvas.width, canvas.height);
                imageData.data.set(Module.HEAPU8.subarray(ptr, ptr + imageData.data.length));
                Module._free(ptr);

                ctx.putImageData(imageData, 0, 0);
            }
        };

        fetch('drawing_program.wasm').then(response => 
            response.arrayBuffer()
        ).then(bytes =>
            WebAssembly.instantiate(bytes, {
                env: {
                    memoryBase: 0,
                    tableBase: 0,
                    memory: newAssembly.Memory({ initial: 256 }),
                   : new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
                }
            })
        ).then(results => {
            Module.wasmModule = results.module;
            Module.wasmInstance = results.instance;
            Module._draw = Module.wasmInstance.exports.draw;
            Module._malloc = Module.wasmInstance.exports.malloc;
            Module._free = Module.wasmInstance.exports.free;
            Module.HEAPU8 = new Uint8Array(Module.wasmInstance.exports.memory.buffer);
            Module.onRuntimeInitialized();
        });
    </script>
</body>
</html>

4. 多媒体处理

应用场景:需要对音频、视频进行实时处理,例如视频滤镜、音频分析等。

原理:通过 WebAssembly,可以将性能密集型的音视频处理任务交给 C/C++ 等高性能语言实现,提高处理效率。

实例:用 WebAssembly 实现简单的视频滤镜(灰度滤镜)。

编写 C 代码(video_filter.c)

c 复制代码
// video_filter.c
#include <stdint.h>

void apply_grayscale_filter(uint8_t* frame, int32_t width, int32_t height) {
    for (int32_t i = 0; i < width * height * 4; i += 4) {
        uint8_t r = frame[i];
        uint8_t g = frame[i + 1];
        uint8_t b = frame[i + 2];
        uint8_t gray = (r + g + b) / 3;
        frame[i] = gray;
        frame[i + 1] = gray;
        frame[i + 2] = gray;
    }
}

编译为 WebAssembly

使用 Emscripten 编译:

shell 复制代码
emcc video_filter.c -s WASM=1 -o video_filter.js

HTML 和 JavaScript

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly Video Filter Example</title>
</head>
<body>
    <video id="video" autoplay></video>
    <canvas id="canvas"></canvas>
    <script>
        let Module = {
            onRuntimeInitialized: function() {
                const video = document.getElementById('video');
                const canvas = document.getElementById('canvas');
                const ctx = canvas.getContext('2d');

                // 获取用户媒体流,显示在 video 标签中
                navigator.mediaDevices.getUserMedia({ video: true })
                    .then(stream => {
                        video.srcObject = stream;
                        video.play();
                    }).catch(err => {
                        console.error('Error accessing webcam:', err);
                    });

                function processFrame() {
                    canvas.width = video.videoWidth;
                    canvas.height = video.videoHeight;
                    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
                    const frame = ctx.getImageData(0, 0, canvas.width, canvas.height);

                    const ptr = Module._malloc(frame.data.length);
                    Module.HEAPU8.set(frame.data, ptr);
                    Module._apply_grayscale_filter(ptr, canvas.width, canvas.height);
                    frame.data.set(Module.HEAPU8.subarray(ptr, ptr + frame.data.length));
                    Module._free(ptr);

                    ctx.putImageData(frame, 0, 0);
                    requestAnimationFrame(processFrame);
                }

                video.addEventListener('play', processFrame);
            }
        };

        fetch('video_filter.wasm').then(response =>
            response.arrayBuffer()
        ).then(bytes =>
            WebAssembly.instantiate(bytes, {
                env: {
                    memoryBase: 0,
                    tableBase: 0,
                    memory: new WebAssembly.Memory({ initial: 256 }),
                    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
                }
            })
        ).then(results => {
            Module.wasmModule = results.module;
            Module.wasmInstance = results.instance;
            Module._apply_grayscale_filter = Module.wasmInstance.exports.apply_grayscale_filter;
            Module._malloc = Module.wasmInstance.exports.malloc;
            Module._free = Module.wasmInstance.exports.free;
            Module.HEAPU8 = new Uint8Array(Module.wasmInstance.exports.memory.buffer);
            Module.onRuntimeInitialized();
        });
    </script>
</body>
</html>

5. 安全和加密

应用场景:需要处理安全性高、计算复杂的加密解密任务,比如敏感数据加解密、验签等。

原理:通过将加密算法用性能高的语言实现并编译为 WebAssembly,可以提高其在浏览器中的性能。

实例:用 WebAssembly 实现简单的 AES 加密。

编写 C 代码(aes_encryption.c)

c 复制代码
// aes_encryption.c
#include <stdint.h>
#include "aes.h" // 假设这里有一个已有的 AES 实现库

void aes_encrypt(uint8_t* plaintext, uint8_t* key, uint8_t* ciphertext) {
    AES128_ECB_encrypt(plaintext, key, ciphertext);
}

编译为 WebAssembly

使用 Emscripten 编译:

shell 复制代码
emcc aes_encryption.c -s WASM=1 -o aes_encryption.js

HTML 和 JavaScript

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly AES Encryption Example</title>
</head>
<body>
    <script>
        let Module = {
            onRuntimeInitialized: function() {
                const plaintext = new Uint8Array([/* 16 bytes of plaintext */]);
                const key = new Uint8Array([/* 16 bytes of key */]);
                const ciphertext = new Uint8Array(16);

                const plaintextPtr = Module._malloc(plaintext.length);
                const keyPtr = Module._malloc(key.length);
                const ciphertextPtr = Module._malloc(ciphertext.length);

                Module.HEAPU8.set(plaintext, plaintextPtr);
                Module.HEAPU8.set(key, keyPtr);

                Module._aes_encrypt(plaintextPtr, keyPtr, ciphertextPtr);
                ciphertext.set(Module.HEAPU8.subarray(ciphertextPtr, ciphertextPtr + ciphertext.length));

                Module._free(plaintextPtr);
                Module._free(keyPtr);
                Module._free(ciphertextPtr);

                console.log(`Ciphertext: ${ciphertext}`);
            }
        };

        fetch('aes_encryption.wasm').then(response =>
            response.arrayBuffer()
        ).then(bytes =>
            WebAssembly.instantiate(bytes, {
                env: {
                    memoryBase: 0,
                    tableBase: 0,
                    memory: new WebAssembly.Memory({ initial: 256 }),
                    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
                }
            })
        ).then(results => {
            Module.wasmModule = results.module;
            Module.wasmInstance = results.instance;
            Module._aes_encrypt = Module.wasmInstance.exports.aes_encrypt;
            Module._malloc = Module.wasmInstance.exports.malloc;
            Module._free = Module.wasmInstance.exports.free;
            Module.HEAPU8 = new Uint8Array(Module.wasmInstance.exports.memory.buffer);
            Module.onRuntimeInitialized();
        });
    </script>
</body>
</html>

结论

WebAssembly 为现代 Web 开发带来了巨大的潜力,特别是在需要高性能计算的应用场景中。通过 WebAssembly,可以将一些原本只能在本地运行的复杂应用程序搬到 Web 平台上,既无损性能,又提供了跨平台的兼容性和安全性。

以上实例展示了 WebAssembly 在不同应用场景中的使用,包括计算密集型任务、游戏开发、应用移植、多媒体处理和安全加密。每个实例都详细展示了如何编写 C/C++ 代码,如何编译为 WebAssembly,并通过 JavaScript 进行交互。

相关推荐
文城5212 小时前
HTML-day1(学习自用)
前端·学习·html
阿珊和她的猫2 小时前
Vue 和 React 的生态系统有哪些主要区别
前端·vue.js·react.js
偷光2 小时前
深度剖析 React 的 useReducer Hook:从基础到高级用法
前端·javascript·react.js
The_era_achievs_hero3 小时前
动态表格html
前端·javascript·html
Thomas_YXQ3 小时前
Unity3D Shader 简析:变体与缓存详解
开发语言·前端·缓存·unity3d·shader
傻小胖4 小时前
ES6 Proxy 用法总结以及 Object.defineProperty用法区别
前端·javascript·es6
Web极客码4 小时前
如何跟踪你WordPress网站的SEO变化
前端·搜索引擎·wordpress
横冲直撞de4 小时前
高版本electron使用iohook失败(使用uiohook-napi替代)
前端·javascript·electron
_Eden_4 小时前
认识Electron 开启新的探索世界一
前端·javascript·electron
~怎么回事啊~4 小时前
electron中调用C++
前端·javascript·electron