Search code examples
webassemblyemscripten

How can I compile a library into WebAssembly that can be called from JavaScript code?


I am experimenting with WASM and I want to encode a pic as WebP for a learning experience in WASM.

I am attempting to compile libwebp into a single mjs file. Specifically, the encoding utility. I am using Emscripten because I want to run this in my web app in WebAssembly. I am compiling it to be imported as a <script src=foobar> file.

This is what I have so far:

two@develop ...libwebp/src/enc (main)
% emcc src/enc/webp_enc.c -o src/foo.mjs -s ENVIRONMENT='web' -s SINGLE_FILE=1  -s USE_ES6_IMPORT_META=0 -O3 

But I think the input file src/enc/webp_enc.c is not the correct input file. Does anyone know which import file I should be using instead? Also, any other things that I might need to do that I am missing?


Solution

  • You will have a hard time trying to compile a whole library by invoking the Emscripten compiler on individual translation units; the encoder is not entirely contained in just one file. You need to engage the build system to build the full library.

    Fortunately for you, much of the work in porting the library to WebAssembly has already been done; there is even a README file with instructions on building a demo program, which uses SDL to decode a file and render it to a <canvas>. You can look how the demo is set up in CMakeLists.txt and modify it to your liking so that the encoder is exported from the library instead of the decoder.

    Preferably though, you would create your own CMake project that imports the encoder library and exports the functions you would like to be callable from JavaScript code. Assuming you put the library in the libwebp/ subdirectory, create a CMakeLists.txt with something like this:

    project(mywebpenc)
    cmake_minimum_required(VERSION 3.22)
    
    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libwebp)
    
    add_executable(mywebpenc ${CMAKE_CURRENT_SOURCE_DIR}/mywebpenc.c)
    target_include_directories(mywebpenc PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
    target_include_directories(mywebpenc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libwebp/src)
    target_compile_definitions(mywebpenc PUBLIC EMSCRIPTEN)
    target_link_libraries(mywebpenc webpencode webpdsp webputils)
    
    set_target_properties(mywebpenc
        PROPERTIES LINK_FLAGS "-s WASM=1 \
            -s EXPORTED_FUNCTIONS='[\"_mywebpenc_encode\"]' -s INVOKE_RUN=0 \
            -s EXPORTED_RUNTIME_METHODS='[\"cwrap\"]'")
    

    Then put a function named above as mywebpenc_encode with the interface you want to export to JS into mywebpenc.c. As a proof-of-concept, I tried a simple stub:

    #include <webp/encode.h>
    
    extern size_t mywebpenc_encode(
        const uint8_t* rgb, int width, int height, int stride,
        float quality_factor, uint8_t** output)
    {
        return WebPEncodeRGB(rgb, width, height, stride, quality_factor, output);
    }
    

    Build the project with emcmake cmake . && make mywebpenc. You will obtain two files, mywebpenc.js and mywebpenc.wasm, which you should be able to interface with like with any other Emscripten library.