Начало работы с WebAssembly: как комбинировать с проектами C/C++
Что такое веб-сборка?
- Новый тип кода, который запускается в веб-браузере, предоставляет некоторые новые функции и ориентирован в первую очередь на высокую производительность.
- В первую очередь не для написания, а для компиляции таких языков, как C/C++, C#, Rust и т. д., поэтому вы можете воспользоваться этим, даже если не знаете, как писать код WebAssembly.
- Код, написанный на других языках, может работать почти на скорости, а клиентские приложения могут работать в Интернете.
- Модули WebAssembly можно импортировать в браузер или Node.js, а JS-фреймворки могут использовать WebAssembly, чтобы получить огромные преимущества в производительности и новые функции, будучи функционально простыми в использовании.
Цели WebAssembly
- Быстрый, эффективный, удобный — может выполняться на почти исходной скорости на разных платформах за счет использования некоторых общих аппаратных возможностей.
- Удобный для чтения и отладки — WebAssembly — это язык ассемблера низкого уровня, но он также имеет удобочитаемый текстовый формат, который позволяет людям писать код, просматривать код и отлаживать код.
- Обеспечьте безопасность — WebAssembly явно работает в безопасной изолированной среде выполнения, аналогично другому веб-коду, он применяет тот же источник и некоторые политики разрешений.
- Не ломает существующую сеть — WebAssembly разработан для совместимости с другими веб-технологиями и поддерживает обратную совместимость.
Как WebAssembly совместим с Интернетом?
Веб-платформу можно рассматривать как состоящую из двух частей:
- Виртуальная машина (ВМ) используется для запуска кода веб-приложения, такого как механизм JS для запуска кода JS.
- Набор веб-API, которые веб-приложения могут вызывать для управления функциональностью веб-браузеров/устройств для выполнения определенных действий (DOM, CSSOM, WebGL, IndexedDB, Web Audio API и т. д.).
Долгое время виртуальные машины могли загружать только JS для запуска, JS может быть достаточно для наших нужд, но теперь мы сталкиваемся с различными проблемами производительности, такими как 3D-игры, VR/AR, компьютерное зрение, редактирование изображений/видео и другие нужды Поле производительности.
В то же время очень сложно загружать, анализировать и компилировать крупномасштабные JS-приложения, а на некоторых платформах с более ограниченными ресурсами, таких как мобильные устройства, оно будет еще больше упираться в это узкое место производительности.
WebAssembly — это другой язык, чем JavaScript, он не был создан для замены JS, но был разработан для дополнения и работы с JS, позволяя веб-разработчикам повторно использовать преимущества обоих языков:
- JS — это язык высокого уровня, гибкий и выразительный, с динамической типизацией, не требующий этапов компиляции и обладающий мощной экосистемой, что позволяет очень легко писать веб-приложения.
- WebAssembly – это низкоуровневый язык, похожий на ассемблер, который использует компактный двоичный формат, может работать почти с исходной производительностью и обеспечивает низкоуровневую модель памяти. Это цель компиляции для таких языков, как C++ и Rust. создание кода, написанного на этих языках, для работы в Интернете (обратите внимание, что WebAssembly в будущем будет предоставлять высокоуровневые цели, такие как модель памяти со сборкой мусора).
С появлением WebAssembly вышеупомянутые виртуальные машины теперь могут загружать два типа выполнения кода: JavaScript и WebAssembly.
JavaScript и WebAssembly могут взаимодействовать, фактически код WebAssembly называется модулем, а модуль WebAssembly имеет много общего с модулями ES2015.
Ключевые концепции WebAssembly
Чтобы понять, как WebAssembly работает в Интернете, необходимо понять несколько ключевых понятий:
- Модуль: двоичный файл WebAssembly, скомпилированный браузером в исполняемый машинный код. Модуль не имеет состояния, как и Blob, и может передаваться между Window и Worker.
postMessage
Shared, модуль объявляет импорт и экспорт аналогично модулям ES2015. - Память: ArrayBuffer изменяемого размера, содержащий линейный массив байтов, считываемых и записываемых низкоуровневыми инструкциями доступа к памяти WebAssembly.
- Таблица: массив типизированных ссылок (например, функций) с изменяемым размером, однако его нельзя хранить в памяти в виде необработанных байтов по соображениям безопасности и переносимости.
- Экземпляр: модуль, который содержит все состояние, которое он использует во время выполнения, включая память, таблицу и ряд импортированных значений. Экземпляр подобен модулю ES2015, который загружается в определенную глобальную переменную с определенным набором импортируемых данных.
JavaScript API WebAssembly предоставляет разработчикам возможность создавать модуль, память, таблицу и экземпляр.Для экземпляра WebAssembly код JS может синхронно вызывать его экспорты, которые экспортируются как обычные функции JavaScript. Любая функция JavaScript может быть вызвана синхронно кодом WebAssembly путем передачи функции JavaScript в качестве импорта в экземпляр WebAssembly.
Поскольку JavaScript имеет полный контроль над загрузкой, компиляцией и запуском кода WebAssembly, разработчики JavaScript могут рассматривать WebAssembly как просто новую функцию JavaScript, эффективно генерирующую высокопроизводительные функции.
В будущем модули WebAssembly можно будет загружать в форме загрузки модулей ES2015, например<script type="module">
, что означает, что JS может извлекать, компилировать и импортировать модуль WebAssembly так же легко, как импортировать модуль ES2015.
Как использовать WebAssembly в приложении?
WebAssembly добавляет к веб-платформе две части: код двоичного формата и набор API-интерфейсов, которые можно использовать для загрузки и выполнения двоичного кода.
WebAssembly в настоящее время находится в зачаточном состоянии, и в будущем наверняка появятся многие инструменты, и в настоящее время есть четыре основных входа:
- Портирование приложений C/C++ с помощью EMScripten
- Пишите и генерируйте код WebAssembly непосредственно на уровне сборки
- Напишите приложение на Rust, выводящее WebAssembly.
- Используйте AssemblyScript, язык, похожий на TypeScript, который компилируется в двоичные файлы WebAssembly.
Портирование приложений C/C++
Хотя есть и другие инструменты, такие как:
Однако в этих инструментах отсутствует цепочка инструментов EMScripten и операции оптимизации.Конкретный рабочий процесс EMScripten выглядит следующим образом:
- EMScripten передает код C/C++ компилятору Clang (компилятору C/C++, основанному на архитектуре компиляции LLVM), который компилируется в LLVM IR.
- EMScripten преобразует LLVM IR в двоичный байт-код .wasm
- WebAssembly не может напрямую получить DOM, он может только вызывать JS и передавать примитивные типы данных, такие как целые числа или числа с плавающей запятой.Поэтому WebAssembly необходимо вызывать JS для получения веб-API и вызовов, а EMScripten достигает этого, создавая файлы HTML и JS. код клея Вышеупомянутый эффект
В будущем WebAssembly также можетВызов веб-API напрямую.
Приведенный выше JS-код не так прост, как можно было себе представить.Вначале EMScripten реализовывал некоторые популярные библиотеки C/C++, такие как SDL, OpenGL, OpenAL и некоторые библиотеки POSIX.Все эти библиотеки реализованы в соответствии с WebAPI.Так что JS связующий код необходим, чтобы помочь WebAssembly взаимодействовать с базовым веб-API.
Таким образом, часть связующего кода реализует функции соответствующей библиотеки, которую должен использовать код C/C++, а связующий код также включает логику для вызова вышеуказанного API WebAssembly JavaScript для получения, загрузки и запуска файла .wasm.
Сгенерированный HTML-документ загружает связующий код JS, а затем записывает вывод в<textarea>
Если приложение использует OpenGL, HTML также содержит<canvas>
Элементы используются в качестве целей рендеринга, и вы можете легко переписать вывод EMScripten, чтобы преобразовать его в форму, требуемую веб-приложением.
Пишите код WebAssembly напрямую
Если вы хотите создать свой собственный компилятор, набор инструментов или библиотеку JS, которые могут генерировать код WebAssembly во время выполнения, вы можете написать код WebAssembly вручную. Подобно физическому языку ассемблера, двоичный формат WebAssembly также имеет текстовое представление, которое вы можете написать или сгенерировать вручную, а также преобразовать текст в двоичный с помощью инструментов преобразования текста в двоичный файл WebAssembly.
Написать код Rust и скомпилировать в WebAssembly
Благодаря неустанной работе рабочей группы Rust WebAssembly теперь мы можем компилировать код Rust в код WebAssembly.
Вы можете обратиться по этой ссылке:developer.Mozilla.org/en-US/docs/…
Использование ассемблерного скрипта
Для веб-разработчиков, которые пытаются написать WebAssembly в форме, похожей на TypeScript, не изучая деталей C или Rust, AssemblyScript будет лучшим выбором. AssemblyScript компилирует варианты TypeScript в WebAssembly, позволяя веб-разработчикам использовать цепочки инструментов, совместимые с TypeScript, такие как Prettier, VSCode Intellisense, вы можете проверитьего документациячтобы научиться им пользоваться.
Как скомпилировать недавно написанный код C/C++ в WebAssembly?
С помощью инструмента EMScripten вновь написанный код C/C++ можно скомпилировать в WebAssembly для использования.
Условия приготовления
Чтобы иметь возможность использовать инструмент Emscripten, нам необходимо его установить. Сначала клонируйте соответствующий код:
git clone https: // github . com / emscripten-core / emsdk . git
cd emsdk
Затем выполните следующий скрипт для настройки emsdk:
# 如果之前 clone 过,那么这里更新最新的代码
git pull
# 下载和安装最新的 SDK 工具
./emsdk install latest
# 为当前的 user 激活最新的 SDK 工具,在 .emscripten 文件中写入当前用户
./emsdk activate latest
# 将 SDK 相关的命令加入到 PATH,以及激活其他环境变量
source ./emsdk_env.sh
С помощью вышеуказанных операций мы можем использовать связанные с Emscripten команды в командной строке.Как правило, когда мы используем Emscripten, есть два основных сценария:
- Скомпилируйте в WASM, а затем создайте HTML-документы для запуска кода в сочетании с связующим кодом JavaScript для запуска кода wasm в веб-среде.
- Скомпилирован в код wasm, создаются только файлы JavaScript
Создание HTML и JavaScript
первый вemsdk
Создайте папку на том же уровне, что и каталог:WebAssembly
, а затем создайте код C в папке:hello.c
следующим образом:
#include <stdio.h>
int main() {
printf("Hello World\n");
}
Затем в командной строке перейдите к этомуhello.c
каталоге, выполните следующую команду, чтобы вызвать Emscripten для компиляции:
emcc hello.c -s WASM=1 -o hello.html
Приведенная выше команда поясняется следующим образом:
-
emcc
Команда командной строки для Emscripten -
-s WASM=1
Затем скажите Emscripten вывести файл wasm.Если этот параметр не указан, он будет выводиться по умолчанию.asm.js
-
-o hello.html
затем говорит компилятору сгенерироватьhello.html
HTML-документация для запуска кода, а также модули wasm и соответствующий связующий код JavaScript для компиляции и создания экземпляров wasm, чтобы его можно было использовать в веб-среде.
После выполнения вышеуказанной команды вашWebAssembly
В каталоге должно быть еще три файла:
- Код бинарного модуля wasm:
hello.wasm
- Файл JavaScript, содержащий связующий код:
hello.js
, который переводит нативные функции C в код JavaScript/wasm. - HTML-файл:
hello.html
, который загружает, компилирует и создает экземпляр кода wasm, а также отображает вывод кода wasm в браузере.
запустить код
Все, что осталось на данный момент, это загрузка в браузерах с поддержкой WebAssembly.hello.html
бегать.
Поддерживается по умолчанию в Firefox 52+, Chrome 57+ и минимальных браузерах Opera, также доступно в Firefox 47+
about:config
включиjavascript.options.wasm
и в Chrome 51+, Opera 38+chrome://flags
чтобы разрешить экспериментальную поддержку эффекта WebAssembly.
Поскольку современные браузеры не поддерживаютfile://
сформировать запрос XHR, поэтому не может быть загружен в HTML.wasm
и другие связанные файлы, поэтому для того, чтобы увидеть эффект, требуется дополнительная поддержка локального сервера, вы можете запустить следующую команду:
npx serve .
npx — удобный инструмент для выполнения npm-команд, запущенных npm после 5.2.0+, таких как вышеприведенная подача, он сначала определяет, существует ли он локально во время выполнения, если он не существует, он загружает исходный соответствующий пакет и выполняет соответствующий команда, и это одноразовая операция, устраняющая необходимость устанавливать, а затем разрешать операцию и временно использовать локальную память.
существуетWebAssembly
запустите локальный веб-сервер в папке, затем откройтеhttp://localhost:5000/hello.htmlПроверьте эффект:
Вы можете увидеть печать, которую мы написали в коде CHello World
Код успешно выводится в браузер, вы также можете открыть консоль, чтобы увидеть соответствующий вывод:
поздравляю! Вы успешно скомпилировали модуль C в WebAssembly и запустили его в браузере!
Используйте собственный HTML-шаблон
В приведенном выше примере используется HTML-шаблон Emscripten по умолчанию, но во многих сценариях нам нужно использовать пользовательский HTML-шаблон. Например, при интеграции WebAssembly в существующий проект нам необходимо настроить HTML-шаблон. Далее мы узнаем, как использовать пользовательский HTML-шаблон. шаблоны.
первый вWebAssembly
новый каталогhello2.c
файл, напишите следующее:
#include <stdio.h>
int main() {
printf("Hello World\n");
}
Найден в предыдущем клоне кода локального репозитория emsdkshell_minimal.html
файл, скопируйте его вWebAssembly
подпапки в каталогеhtml_template
Далее (эта папка нуждается в новой), сейчасWebAssembly
Структура файлов в каталоге следующая:
.
├── hello.c
├── hello.html
├── hello.js
├── hello.wasm
├── hello2.c
└── html_template
└── shell_minimal.html
Перейдите в командную строку, чтобыWebAssembly
, выполните следующую команду:
emcc -o hello2.html hello2.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html
Как видите, в передаче параметров есть несколько изменений по сравнению с предыдущим:
- установив
-o hello2.html
, компилятор выведетhello2.js
Код клея JS, а такжеhello2.html
HTML-файл - Также установлено
--shell-file html_template/shell_minimal.html
, который предоставляет URL-адрес HTML-шаблона, который вы используете при создании HTML-файла.
Теперь давайте запустим этот HTML с помощью следующей команды:
npx serve .
Перейдите к:localhosthttp://localhost:5000/hello2.htmlдля доступа к результатам запуска вы можете наблюдать аналогичный эффект, как и раньше:
Видно, что предыдущий заголовок Emscripten просто отсутствует, остальные аналогичны предыдущим, проверьтеWebAssembly
файловый каталог, вы обнаружите, что генерируются похожие коды JS и Wasm:
.
├── hello.c
├── hello.html
├── hello.js
├── hello.wasm
├── hello2.c
├── hello2.html
├── hello2.js
├── hello2.wasm
└── html_template
└── shell_minimal.html
Примечание. Вы можете настроить вывод только связующего кода JavaScript вместо полного HTML-документа, введя
-o
После того, как метка указана как.js
файлы, такие какemcc -o hello2.js hello2.c -O3 -s WASM=1
, затем вы можете настроить файл HTML, а затем импортировать этот связующий код для использования, однако это более продвинутый метод, общая форма заключается в использовании предоставленного шаблона HTML:
-
Emscripten требует много связующего кода JavaScript для решения проблем с выделением памяти, утечками памяти и множеством других проблем.
Вызов пользовательской функции в C
Если вы определяете функцию в коде C, а затем хотите вызвать ее в JavaScript, вы можете использовать Emscripten.ccall
функция, иEMSCRIPTEN_KEEPALIVE
Объявление (это объявление добавляет вашу функцию C в список вывода функции, конкретный рабочий процесс выглядит следующим образом:
первый вWebAssembly
Создано в каталогеhello3.c
файл, добавьте следующее:
#include <stdio.h>
#include <emscripten/emscripten.h>
int main() {
printf("Hello World\n");
}
#ifdef __cplusplus
extern "C" {
#endif
EMSCRIPTEN_KEEPALIVE void myFunction(int argc, char ** argv) {
printf("MyFunction Called\n");
}
#ifdef __cplusplus
}
#endif
Emscripten Code Genered Code Challes толькоmain
функция, другие функции удаляются как «мертвый код». добавить перед именем функцииEMSCRIPTEN_KEEPALIVE
декларация предотвратит это «удаление», вам нужно импортироватьemscripten.h
заголовочный файл для использованияEMSCRIPTEN_KEEPALIVE
утверждение.
Обратите внимание, что мы добавили в код
#ifdef
блок, убедитесь, что импорт этого использования в код C++ также работает правильно, потому что могут быть некоторые запутанные правила об именовании C и C++, поэтому добавление вышеEMSCRIPTEN_KEEPALIVE
Объявленная функция может быть недействительной, поэтому добавьте функцию в среду C++.external
, относитесь к нему как кexternal
так, чтобы он корректно работал и в среде C++.
Затем для удобства демонстрации HTML-файл по-прежнему используется в том виде, в каком мы его поставили ранее.html_template
в каталогеshell_minimal.html
файл, а затем скомпилируйте код C с помощью следующей команды:
emcc -o hello3.html hello3.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']"
Обратите внимание, что в приведенной выше компиляции мы добавилиNO_EXIT_RUNTIME
параметр, потому что когдаmain
После того, как функция завершит работу, программа завершит работу, поэтому добавление этого параметра гарантирует, что другие функции по-прежнему будут работать должным образом.
и дополнительно добавленоEXTRA_EXPORTED_RUNTIME_METHODS
Затем он используется для экспорта модуля для WebAssembly.ccall
использование метода, позволяющее вызывать экспортированные функции C из JavaScript.
когда ты проходишьnpx serve .
При запуске вы все еще можете видеть такие же результаты, как и раньше:
Теперь мы можем попробовать использовать в JavaScriptmyFunction
функция, сначала откройте в редактореhello3.html
файл, затем добавьте<button>
элемент, а в<button>
Может вызываться при нажатии на элементmyFunction
функция:
<!-- 其他内容 --->
<button class="mybutton">Run myFunction</button>
<script type='text/javascript'>
// ... 其他生成的代码
// script 标签底部
document.querySelector('.mybutton')
.addEventListener('click', function() {
alert('check console');
var result = Module.ccall(
'myFunction', // name of C function
null, // return type
null, // argument types
null // arguments
);
});
</script>
<!-- 其他内容 --->
Сохраните приведенный выше контент и обновите браузер, чтобы увидеть следующие результаты:
Когда мы нажимаем кнопку на изображении выше, мы можем получить следующие результаты:
сначала получитalert
приглашение, а затем распечатать содержимое MyFunction Called в выходных данных, указав, чтоmyFunction
Вызывается, откройте консоль, и вы можете увидеть следующие результаты печати:
В приведенном выше примере показано, как вы можете сделать это в JavaScript с помощьюccall
для вызова экспортированной функции в коде C.
Как скомпилировать существующий модуль C в WebAssembly?
Основным вариантом использования WebAssembly является повторное использование библиотек, которые уже существуют в экосистеме C, и их компиляция для использования на веб-платформе без повторной реализации набора кода.
Эти библиотеки C обычно зависят от стандартной библиотеки C, операционной системы, файловой системы и т. д. Emscripten предоставляет большинство вышеупомянутых зависимостей, хотя все еще существуют некоторые ограничения.
Давайте скомпилируем кодек WebP библиотеки C в wasm, чтобы понять, как скомпилировать существующий модуль C. Исходный код кодека WebP реализован на C и может быть найден вGithubнайди его и узнай об этомДокументация API.
Сначала клонируйте исходный код кодировщика WebP на локальный иemsdk
,WebAssembly
Родной каталог:
git clone https://github.com/webmproject/libwebp
Чтобы быстро начать работу, мы можем сначала экспортироватьencode.h
в заголовочном файлеWebPGetEncoderVersion
Функция предоставляется JavaScript для использования, сначала вWebAssembly
создать папкуwebp.c
файл и добавьте следующее:
#include "emscripten.h"
#include "src/webp/encode.h"
EMSCRIPTEN_KEEPALIVE
int version() {
return WebPGetEncoderVersion();
}
Приведенный выше пример может быстро проверить, правильно ли скомпилирован исходный код libwebp и можно ли успешно использовать его функции, потому что приведенные выше функции могут быть успешно выполнены без различных сложных параметров и структур данных.
Чтобы скомпилировать вышеуказанные функции, мы сначала должны сообщить компилятору, как найти заголовочные файлы библиотеки libwebp, добавив флаг при компиляцииI
, а затем указать адрес файла заголовка libwep, чтобы сообщить компилятору адрес, и передать ему все файлы C в libwebp, которые нужны компилятору. Но иногда очень обременительно перечислять файлы C один за другим, поэтому эффективная стратегия состоит в том, чтобы передать компилятору все файлы C, а затем полагаться на то, что компилятор сам отфильтрует эти ненужные файлы.Описанные выше операции можно выполнить с помощью Напишите следующую команду в командной строке для достижения:
emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' \
-I libwebp \
WebAssembly/webp.c \
libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c
Примечание: описанная выше стратегия передачи параметров действует не во всех проектах C. Многие проекты полагаются на такие библиотеки, как autoconfig/automake, для создания системного кода перед компиляцией.
emconfigure
а такжеemmake
чтобы инкапсулировать эти команды и ввести соответствующие параметры, чтобы сгладить эти проекты с предварительными зависимостями.
После запуска вышеуказанной команды он выведетa.out.js
код клея иa.out.wasm
файл, то вам нужноa.out.js
Создайте файл HTML в каталоге, в котором файл выводится, и добавьте в него следующий код.
<script src="./a.out.js"></script>
<script>
Module.onRuntimeInitialized = async _ => {
const api = {
version: Module.cwrap('version', 'number', []),
};
console.log(api.version());
};
</script>
В приведенном выше коде мы сначала импортируем скомпилированный вывод компилятора.a.out.js
связующий код, а затем после инициализации модуля WebAssembly передатьcwrap
функция экспортирует функцию Cversion
использовать, запустив так же, как и раньшеnpx serve .
команду, а затем откройте браузер, чтобы увидеть следующий эффект:
libwebp через шестнадцатеричный код
0xabc
abc для указания текущей версииa.b.c
, например v0.6.1, он будет закодирован как шестнадцатеричный0x000601
, что соответствует 1537 в десятичном виде. А вот 66049 в десятичном виде, а при переводе в шестнадцатеричный получается0x010201
, что указывает на то, что текущая версия — v1.2.1.
Получите изображение в JavaScript и поместите его в wasm для запуска
просто позвонив кодировщикуWebPGetEncoderVersion
метод, чтобы получить номер версии, чтобы подтвердить, что библиотека libwebp была успешно скомпилирована в wasm, а затем может использоваться в JavaScript, тогда мы поймем более сложные операции, как использовать API кодирования libwebp для преобразования форматов изображений.
API кодирования libwebp должен получать массив байтов RGB, RGBA, BGR или BGRA, поэтому первый вопрос, на который нужно ответить, — как поместить изображение в wasm для запуска? К счастью, Canvas API имеетCanvasRenderingContext2D.getImageData
метод, который возвращаетUint8ClampedArray
, этот массив содержит данные изображения в формате RGBA.
Сначала нам нужно написать функцию для загрузки изображения на JavaScript и записать его в файл HTML, созданный на предыдущем шаге:
<script src="./a.out.js"></script>
<script>
Module.onRuntimeInitialized = async _ => {
const api = {
version: Module.cwrap('version', 'number', []),
};
console.log(api.version());
};
async function loadImage(src) {
// 加载图片
const imgBlob = await fetch(src).then(resp => resp.blob());
const img = await createImageBitmap(imgBlob);
// 设置 canvas 画布的大小与图片一致
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
// 将图片绘制到 canvas 上
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, img.width, img.height);
}
</script>
Теперь осталось скопировать данные изображения из JavaScript в wasm.Для этого необходимоwebp.c
Функция предоставляет дополнительные методы:
- Метод выделения памяти для изображений в wasm
- способ освободить память
Исправлятьwebp.c
следующим образом:
#include <stdlib.h> // 此头文件导入用于分配内存的 malloc 方法和释放内存的 free 方法
EMSCRIPTEN_KEEPALIVE
uint8_t* create_buffer(int width, int height) {
return malloc(width * height * 4 * sizeof(uint8_t));
}
EMSCRIPTEN_KEEPALIVE
void destroy_buffer(uint8_t* p) {
free(p);
}
create_buffer
Выделите память для изображений RGBA, изображения RGBA содержат 4 байта на пиксель, поэтому код необходимо добавить4 * sizeof(uint8_t)
,malloc
Указатель, возвращаемый функцией, указывает на адрес первого блока памяти выделенной памяти.Когда этот указатель возвращается в JavaScript, он будет рассматриваться как простое число. когда прошлоcwrap
Когда функция получает соответствующую функцию C, доступную для JavaScript, она может использовать этот номер указателя, чтобы найти начальное местоположение памяти скопированных данных изображения.
Мы добавляем дополнительный код в файл HTML следующим образом:
<script src="./a.out.js"></script>
<script>
Module.onRuntimeInitialized = async _ => {
const api = {
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
encode: Module.cwrap("encode", "", ["number","number","number","number",]),
free_result: Module.cwrap("free_result", "", ["number"]),
get_result_pointer: Module.cwrap("get_result_pointer", "number", []),
get_result_size: Module.cwrap("get_result_size", "number", []),
};
const image = await loadImage('./image.jpg');
const p = api.create_buffer(image.width, image.height);
Module.HEAP8.set(image.data, p);
// ... call encoder ...
api.destroy_buffer(p);
};
async function loadImage(src) {
// 加载图片
const imgBlob = await fetch(src).then(resp => resp.blob());
const img = await createImageBitmap(imgBlob);
// 设置 canvas 画布的大小与图片一致
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
// 将图片绘制到 canvas 上
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, img.width, img.height);
}
</script>
Вы можете видеть, что приведенный выше код добавляется перед импортомcreate_buffer
а такжеdestroy_buffer
Кроме того, есть много функций для кодирования файлов и т. д., которые мы объясним позже.Кроме того, код сначала загружаетimage.jpg
изображение, а затем вызовите функцию C, чтобы выделить память для данных этого изображения, получить возвращенный указатель и передать его в WebAssembly.Module.HEAP8
, в начале памяти p записать данные изображения и, наконец, освободить выделенную память.
кодировать картинки
Теперь, когда данные изображения загружены в память wasm, вы можете вызвать метод encoder libwebp для завершения процесса кодирования.Документация для WebP, обнаружил, что вы можете использоватьWebPEncodeRGBA
функция для выполнения работы. Эта функция получает указатель на данные изображения и его размеры, а также необязательный параметр качества в диапазоне 0-100. В процессе кодированияWebPEncodeRGBA
Выделит блок памяти для выходных данных, нам нужно вызвать после завершения кодированияWebPFree
освободить эту память.
мы открытыwebp.c
файл, добавьте следующий код для обработки кодировки:
int result[2];
EMSCRIPTEN_KEEPALIVE
void encode(uint8_t* img_in, int width, int height, float quality) {
uint8_t* img_out;
size_t size;
size = WebPEncodeRGBA(img_in, width, height, width * 4, quality, &img_out);
result[0] = (int)img_out;
result[1] = size;
}
EMSCRIPTEN_KEEPALIVE
void free_result(uint8_t* result) {
WebPFree(result);
}
EMSCRIPTEN_KEEPALIVE
int get_result_pointer() {
return result[0];
}
EMSCRIPTEN_KEEPALIVE
int get_result_size() {
return result[1];
}
вышесказанноеWebPEncodeRGBA
Результатом выполнения функции является память, выделенная под блок выходных данных, и размер возвращаемой памяти. Поскольку функции C не могут использовать массивы в качестве возвращаемых значений (если только нам не нужно динамическое выделение памяти), мы используем глобальный статический массив для получения возвращаемых результатов, что может быть не очень каноничным при написании кода C, и для этого требуются указатели wasm для иметь длину 32 бита, но ради простоты мы можем пока это терпеть.
Теперь, когда соответствующая логика на стороне C написана, вы можете вызвать функцию кодирования на стороне JavaScript, чтобы получить указатель данных изображения и размер памяти, занимаемой изображением, сохранить эти данные в собственной памяти JavaScript, а затем освобождаем wasm при обработке изображения.С выделенной памятью давайте откроем файл HTML и завершим логику, описанную выше:
<script src="./a.out.js"></script>
<script>
Module.onRuntimeInitialized = async _ => {
const api = {
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
encode: Module.cwrap("encode", "", ["number","number","number","number",]),
free_result: Module.cwrap("free_result", "", ["number"]),
get_result_pointer: Module.cwrap("get_result_pointer", "number", []),
get_result_size: Module.cwrap("get_result_size", "number", []),
};
const image = await loadImage('./image.jpg');
const p = api.create_buffer(image.width, image.height);
Module.HEAP8.set(image.data, p);
api.encode(p, image.width, image.height, 100);
const resultPointer = api.get_result_pointer();
const resultSize = api.get_result_size();
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);
const result = new Uint8Array(resultView);
api.free_result(resultPointer);
api.destroy_buffer(p);
};
async function loadImage(src) {
// 加载图片
const imgBlob = await fetch(src).then(resp => resp.blob());
const img = await createImageBitmap(imgBlob);
// 设置 canvas 画布的大小与图片一致
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
// 将图片绘制到 canvas 上
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, img.width, img.height);
}
</script>
В приведенном выше коде мы передаемloadImage
Функция загружает локальныйimage.jpg
картинку, нужно заранее подготовить картинкуemcc
Он используется в каталоге, выводимом компилятором, то есть в нашем каталоге файлов HTML.
Уведомление:
new Uint8Array(someBuffer)
создаст новое представление в том же блоке памяти иnew Uint8Array(someTypedArray)
просто скопируйsomeTypedArray
Данные.
Когда ваше изображение относительно большое, потому что wasm не может быть расширен для размещенияinput
а такжеoutput
В памяти данных изображения могут возникнуть следующие ошибки:
Но изображение, используемое в нашем примере, относительно маленькое, поэтому нам нужно только добавить параметр фильтра во время компиляции.-s ALLOW_MEMORY_GROWTH=1
Просто игнорируйте это сообщение об ошибке:
emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' \
-I libwebp \
test-dir/webp.c \
libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c \
-s ALLOW_MEMORY_GROWTH=1
Запустите приведенную выше команду еще раз, чтобы получить код wasm и соответствующий связующий код JavaScript с добавленной функцией кодирования, чтобы при открытии файла HTML он мог кодировать файл JPG в формат WebP. Чтобы дополнительно подтвердить это точка, мы можем вывести картинку на веб-интерфейс, изменив HTML-файл и добавив следующий код:
<script>
// ...
api.encode(p, image.width, image.height, 100);
const resultPointer = api.get_result_pointer();
const resultSize = api.get_result_size();
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);
const result = new Uint8Array(resultView);
// 添加到这里
const blob = new Blob([result], {type: 'image/webp'});
const blobURL = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = blobURL;
document.body.appendChild(img)
api.free_result(resultPointer);
api.destroy_buffer(p);
</script>
Затем обновите браузер, вы сможете увидеть изображение WebP, отображаемое на веб-странице, загрузив этот файл на локальный сервер, вы увидите, что его формат преобразован в WebP:
С помощью описанного выше процесса мы успешно скомпилировали существующую библиотеку libwebp C в wasm, преобразовали изображение JPG в формат WebP и отобразили его в веб-интерфейсе Использование wasm для выполнения операций транскодирования, требующих больших вычислительных ресурсов, может значительно повысить производительность веб-страниц. Производительность, которая является одним из основных преимуществ WebAssembly.
Спасибо за поддержку
Выше приведено все содержание этого обмена, я надеюсь, что это поможет вам ^_^
Если вам понравилось, не забудьте поделиться, поставить лайк и добавить в избранное~
Добро пожаловать на общедоступный номеравтобус программиста, интересный, причудливый и теплый автобус программиста, балующийся перед лицом крупных заводов, жизнью программистов, практическими уроками, передовыми технологиями и т. д., следуйте за мной и заводите друзей!