最近做动画渲染的时候有个诉求是需要把Wasm能力集成至 IOS 客户端,于是决定写篇博客学习下 WebAssembly 相关的知识。
WebAssembly(简称 Wasm)是一种平台无关的二进制指令格式,被设计为在现代浏览器中高效、安全地运行接近本地性能的代码。Wasm 的出现为 Web 开发带来了新的可能性,尤其是在性能要求高的应用场景中。本文将详细介绍 WebAssembly 的应用方向、工作原理,并为每个方向提供实例。
WebAssembly 的主要应用方向
- 计算密集型任务
- 游戏开发
- 应用移植
- 多媒体处理
- 安全和加密
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 进行交互。