bindings/wasm: Support WebGPU

Updated WASM binding to bring WebGPU on the web player.

binding will generate a WASM binary, which can render animation through both SW and WG raster engine.

A raster engine will be chosen by parameter `engine`, when initializing.
This commit is contained in:
Jinny You 2024-08-20 16:58:11 +09:00 committed by Hermet Park
parent 32954f962d
commit d704a2503a
5 changed files with 111 additions and 47 deletions

View file

@ -12,11 +12,10 @@ exe_suffix = 'js'
[built-in options] [built-in options]
cpp_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions'] cpp_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions']
cpp_link_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions', '--bind', '-sWASM=1', '-sALLOW_MEMORY_GROWTH=1', '-sEXPORT_ES6=1', '-sFORCE_FILESYSTEM=1', '-sMODULARIZE=1', '-sEXPORTED_RUNTIME_METHODS=FS', '-sSINGLE_FILE=1'] cpp_link_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions', '--bind', '-sWASM=1', '-sALLOW_MEMORY_GROWTH=1', '-sEXPORT_ES6=1', '-sFORCE_FILESYSTEM=1', '-sMODULARIZE=1', '-sEXPORTED_RUNTIME_METHODS=FS']
#cpp_link_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions', '--bind', '-sWASM=1', '-sALLOW_MEMORY_GROWTH=1', '-sFORCE_FILESYSTEM=1', '-g'] //enable g flag for wasm debugging
[host_machine] [host_machine]
system = 'emscripten' system = 'emscripten'
cpu_family = 'x86' cpu_family = 'wasm32'
cpu = 'i686' cpu = 'wasm32'
endian = 'little' endian = 'little'

View file

@ -12,7 +12,7 @@ exe_suffix = 'js'
[built-in options] [built-in options]
cpp_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions'] cpp_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions']
cpp_link_args = ['-lembind', '-sMODULARIZE=1', '-sUSE_WEBGPU=1', '-sASYNCIFY=1', '-sFORCE_FILESYSTEM=1', '-sALLOW_MEMORY_GROWTH=1', '-sEXPORT_ES6=1', '-sSINGLE_FILE=1'] cpp_link_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions', '--bind', '-sWASM=1', '-sALLOW_MEMORY_GROWTH=1', '-sEXPORT_ES6=1', '-sFORCE_FILESYSTEM=1', '-sMODULARIZE=1', '-sEXPORTED_RUNTIME_METHODS=FS', '-sUSE_WEBGPU=1', '-sASYNCIFY=1']
[host_machine] [host_machine]
system = 'emscripten' system = 'emscripten'

View file

@ -23,6 +23,9 @@
#include <thorvg.h> #include <thorvg.h>
#include <emscripten/bind.h> #include <emscripten/bind.h>
#include "tvgPicture.h" #include "tvgPicture.h"
#ifdef THORVG_WG_RASTER_SUPPORT
#include <webgpu/webgpu.h>
#endif
using namespace emscripten; using namespace emscripten;
using namespace std; using namespace std;
@ -35,13 +38,60 @@ class __attribute__((visibility("default"))) TvgLottieAnimation
public: public:
~TvgLottieAnimation() ~TvgLottieAnimation()
{ {
free(buffer); if (engine == tvg::CanvasEngine::Sw) {
#ifdef THORVG_SW_RASTER_SUPPORT
free(buffer);
#endif
} else if (engine == tvg::CanvasEngine::Wg) {
#ifdef THORVG_WG_RASTER_SUPPORT
wgpuSurfaceRelease(surface);
wgpuInstanceRelease(instance);
#endif
}
Initializer::term(); Initializer::term();
} }
static TvgLottieAnimation* create() explicit TvgLottieAnimation(string engine = "sw", string selector = "")
{ {
return new TvgLottieAnimation; errorMsg = NoError;
if (engine == "sw") {
#ifdef THORVG_SW_RASTER_SUPPORT
this->engine = tvg::CanvasEngine::Sw;
if (Initializer::init(0, this->engine) != Result::Success) {
errorMsg = "init() fail";
return;
}
canvas = SwCanvas::gen();
#endif
} else if (engine == "wg") {
#ifdef THORVG_WG_RASTER_SUPPORT
this->engine = tvg::CanvasEngine::Wg;
if (Initializer::init(0, this->engine) != Result::Success) {
errorMsg = "init() fail";
return;
}
//Init WebGPU
instance = wgpuCreateInstance(nullptr);
WGPUSurfaceDescriptorFromCanvasHTMLSelector canvasDesc{};
canvasDesc.chain.next = nullptr;
canvasDesc.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector;
canvasDesc.selector = selector.c_str();
WGPUSurfaceDescriptor surfaceDesc{};
surfaceDesc.nextInChain = &canvasDesc.chain;
surface = wgpuInstanceCreateSurface(instance, &surfaceDesc);
canvas = WgCanvas::gen();
#endif
}
if (!canvas) errorMsg = "Invalid canvas";
animation = Animation::gen();
if (!animation) errorMsg = "Invalid animation";
} }
string error() string error()
@ -85,8 +135,7 @@ public:
return false; return false;
} }
//back up for saving this->data = data; //back up for saving
this->data = data;
canvas->clear(true); canvas->clear(true);
@ -126,7 +175,9 @@ public:
if (!canvas || !animation) return val(typed_memory_view<uint8_t>(0, nullptr)); if (!canvas || !animation) return val(typed_memory_view<uint8_t>(0, nullptr));
if (!updated) return val(typed_memory_view(width * height * 4, buffer)); if (!updated) {
return output();
}
if (canvas->draw() != Result::Success) { if (canvas->draw() != Result::Success) {
errorMsg = "draw() fail"; errorMsg = "draw() fail";
@ -137,7 +188,7 @@ public:
updated = false; updated = false;
return val(typed_memory_view(width * height * 4, buffer)); return output();
} }
bool update() bool update()
@ -186,9 +237,17 @@ public:
this->width = width; this->width = width;
this->height = height; this->height = height;
free(buffer); if (engine == tvg::CanvasEngine::Sw) {
buffer = (uint8_t*)malloc(width * height * sizeof(uint32_t)); #ifdef THORVG_SW_RASTER_SUPPORT
canvas->target((uint32_t *)buffer, width, width, height, SwCanvas::ABGR8888S); free(buffer);
buffer = (uint8_t*)malloc(width * height * sizeof(uint32_t));
static_cast<SwCanvas*>(canvas.get())->target((uint32_t *)buffer, width, width, height, SwCanvas::ABGR8888S);
#endif
} else if (engine == tvg::CanvasEngine::Wg) {
#ifdef THORVG_WG_RASTER_SUPPORT
static_cast<WgCanvas*>(canvas.get())->target(this->instance, this->surface, width, height);
#endif
}
float scale; float scale;
float shiftX = 0.0f, shiftY = 0.0f; float shiftX = 0.0f, shiftY = 0.0f;
@ -311,38 +370,39 @@ public:
// TODO: Advanced APIs wrt Interactivity & theme methods... // TODO: Advanced APIs wrt Interactivity & theme methods...
private: private:
explicit TvgLottieAnimation() val output()
{ {
errorMsg = NoError; if (engine == tvg::CanvasEngine::Sw) {
#ifdef THORVG_SW_RASTER_SUPPORT
if (Initializer::init(0) != Result::Success) { return val(typed_memory_view(width * height * 4, buffer));
errorMsg = "init() fail"; #endif
return; }
} return val(typed_memory_view<uint8_t>(0, nullptr));
canvas = SwCanvas::gen();
if (!canvas) errorMsg = "Invalid canvas";
animation = Animation::gen();
if (!animation) errorMsg = "Invalid animation";
} }
private: private:
string errorMsg; string errorMsg;
unique_ptr<SwCanvas> canvas = nullptr; CanvasEngine engine;
unique_ptr<Canvas> canvas = nullptr;
unique_ptr<Animation> animation = nullptr; unique_ptr<Animation> animation = nullptr;
string data; string data;
uint8_t* buffer = nullptr;
uint32_t width = 0; uint32_t width = 0;
uint32_t height = 0; uint32_t height = 0;
float psize[2]; //picture size float psize[2]; //picture size
bool updated = false; bool updated = false;
#ifdef THORVG_SW_RASTER_SUPPORT
uint8_t* buffer = nullptr;
#endif
#ifdef THORVG_WG_RASTER_SUPPORT
WGPUInstance instance{};
WGPUSurface surface{};
#endif
}; };
EMSCRIPTEN_BINDINGS(thorvg_bindings) EMSCRIPTEN_BINDINGS(thorvg_bindings)
{ {
class_<TvgLottieAnimation>("TvgLottieAnimation") class_<TvgLottieAnimation>("TvgLottieAnimation")
.constructor(&TvgLottieAnimation ::create) .constructor<string, string>()
.function("error", &TvgLottieAnimation ::error, allow_raw_pointers()) .function("error", &TvgLottieAnimation ::error, allow_raw_pointers())
.function("size", &TvgLottieAnimation ::size) .function("size", &TvgLottieAnimation ::size)
.function("duration", &TvgLottieAnimation ::duration) .function("duration", &TvgLottieAnimation ::duration)

View file

@ -2,10 +2,26 @@
#https://github.com/thorvg/thorvg/wiki/ThorVG-Viewer-Development-Guide #https://github.com/thorvg/thorvg/wiki/ThorVG-Viewer-Development-Guide
if [ ! -d "./build_wasm" ]; then BACKEND="$1"
sed "s|EMSDK:|$1|g" ./cross/wasm_x86_i686.txt > /tmp/.wasm_cross.txt EMSDK="$2"
meson -Db_lto=true -Ddefault_library=static -Dstatic=true -Dloaders="all" -Dsavers="all" -Dthreads=false -Dbindings="wasm_beta" --cross-file /tmp/.wasm_cross.txt build_wasm
if [ -z "$2" ]; then
BACKEND="all"
EMSDK="$1"
fi fi
ninja -C build_wasm/ if [ ! -d "./build_wasm32_$BACKEND" ]; then
ls -lrt build_wasm/src/bindings/wasm/thorvg-wasm.* if [[ "$BACKEND" == "wg" ]]; then
sed "s|EMSDK:|$EMSDK|g" ./cross/wasm32_wg.txt > /tmp/.wasm_cross.txt
meson -Db_lto=true -Ddefault_library=static -Dstatic=true -Dloaders="all" -Dsavers="all" -Dthreads=false -Dbindings="wasm_beta" -Dengines="wg_beta" --cross-file /tmp/.wasm_cross.txt build_wasm32_wg
elif [[ "$BACKEND" == "sw" ]]; then
sed "s|EMSDK:|$EMSDK|g" ./cross/wasm32_sw.txt > /tmp/.wasm_cross.txt
meson -Db_lto=true -Ddefault_library=static -Dstatic=true -Dloaders="all" -Dsavers="all" -Dthreads=false -Dbindings="wasm_beta" --cross-file /tmp/.wasm_cross.txt build_wasm32_sw
else
sed "s|EMSDK:|$EMSDK|g" ./cross/wasm32_wg.txt > /tmp/.wasm_cross.txt
meson -Db_lto=true -Ddefault_library=static -Dstatic=true -Dloaders="all" -Dsavers="all" -Dthreads=false -Dbindings="wasm_beta" -Dengines="wg_beta, sw" --cross-file /tmp/.wasm_cross.txt build_wasm32_all
fi
fi
ninja -C build_wasm32_$BACKEND/
ls -lrt build_wasm32_$BACKEND/src/bindings/wasm/*.{js,wasm}

View file

@ -1,11 +0,0 @@
#!/bin/bash
#https://github.com/thorvg/thorvg/wiki/WebGPU-Raster-Engine-Development
if [ ! -d "./build_wasm" ]; then
sed "s|EMSDK:|$1|g" ./cross/wasm_webgpu.txt > /tmp/.wasm_cross.txt
meson -Db_lto=true -Ddefault_library=static -Dstatic=true -Dloaders="all" -Dsavers="all" -Dthreads=false -Dbindings="wasm_beta" -Dengines="wg_beta" --cross-file /tmp/.wasm_cross.txt build_wasm
fi
ninja -C build_wasm/
ls -lrt build_wasm/src/bindings/wasm/thorvg-wasm.*