This repository provides a CMake-consumable WebAssembly build of Mesa llvmpipe and lavapipe.
- CMake targets for wasm llvmpipe and lavapipe
- In-tree consumption with
add_subdirectory - Relocatable package consumption with
find_package - Runtime shader tooling path with DXC in Wasm and Clang in Wasm
- Fast Wasm shader execution path backend (currently validated on compute dispatch)
Public link targets
webvulkan::llvmpipe_wasmwebvulkan::lavapipe_wasmwebvulkan::shader_toolswebvulkan::runtime_shader_registry
- Clang in Wasm is available for LLVM IR to Wasm module generation
- DXC in Wasm is available for HLSL to SPIR-V generation
Mesa source is fetched from
https://github.com/Devsh-Graphics-Programming/mesa
The fork carries wasm-specific patches required for this flow
- Emscripten build and linking adjustments for lavapipe
- runtime shader key capture and runtime module lookup hooks
- runtime Wasm dispatch integration for shader execution experiments
Non-Emscripten paths are kept intact.
DXC source is fetched from
https://github.com/Devsh-Graphics-Programming/DirectXShaderCompiler/tree/wasm
The fork carries build-system patches required to compile DXC to Wasm.
LLVM maintainers explicitly state LLJIT does not support WebAssembly in this form
https://discourse.llvm.org/t/does-lljit-support-webassembly/74864/3
That blocks the classic in-process ORC LLJIT path inside the Wasm sandbox.
Mesa does not ship a production NIR to Wasm backend for this use case. Writing and maintaining one from scratch is a large compiler project.
- Use DXC in Wasm to compile HLSL source to SPIR-V at runtime
- Use Clang in Wasm to compile LLVM IR to Wasm runtime modules
- Build and ship lavapipe and llvmpipe as CMake-consumable wasm targets
- Keep the integration path centered on standard Vulkan API usage through lavapipe
- Yes, that is the point and we are fine with it.
- Tooling artifacts are already distributed in compressed
.zipbundles. - Runtime delivery compression is deployment-specific and should be handled by your host or CDN (gzip or brotli).
- If this payload size blocks your use case, sponsor NIR-to-Wasm backend work or LLVM JIT in Wasm R&D and contact Devsh Graphics Programming.
This backend is under active development. Interfaces and behavior can still change while the runtime path is being expanded.
What is implemented now
- Runtime HLSL to SPIR-V compilation in Wasm through DXC
- Runtime LLVM IR to Wasm module compilation in Wasm through Clang
- Driver-side shader key registration and runtime Wasm module lookup
- Fast Wasm execution path validated for Vulkan compute dispatch in CI
What is not implemented yet
- Full Vulkan stage and feature coverage across all real-world shader patterns
- Full conformance and performance tuning for broad hardware and workload sets
- Stable long-term public API for the runtime shader tooling layer
- Shared-driver output mode (current driver artifact is static only)
Default configuration uses the latest prebuilt LLVM bundle from this repository.
llvm-wasm-prebuilt-latest
To force source LLVM build
cmake -S . -B build -G Ninja -DLLVM_PROVIDER=sourceadd_subdirectory(path/to/llvmpipe2wasm)
add_executable(my_app src/main.c)
target_link_libraries(my_app PRIVATE webvulkan::llvmpipe_wasm)cmake -S . -B build -G Ninja
cmake --build buildBuild and install
cmake -S . -B build -G Ninja
cmake --build build
cmake --install build --prefix <install-prefix>Consume
find_package(WebVulkanLlvmpipeWasm REQUIRED CONFIG)
add_executable(my_app src/main.c)
target_link_libraries(my_app PRIVATE webvulkan::llvmpipe_wasm)Reusable helper
webvulkan_compile_hlsl_to_spirv(...)
Example
add_subdirectory(path/to/llvmpipe2wasm)
set(MY_SHADER_SPV "${CMAKE_BINARY_DIR}/shaders/write_const.spv")
set(WEBVULKAN_DXC_WASM_JS "${CMAKE_SOURCE_DIR}/tools/dxc-wasm/dxc.js")
webvulkan_compile_hlsl_to_spirv(
SOURCE "${CMAKE_SOURCE_DIR}/shaders/write_const.hlsl"
OUTPUT "${MY_SHADER_SPV}"
HLSL_ENTRYPOINT "main"
HLSL_PROFILE "cs_6_0"
)
add_custom_target(my_shaders DEPENDS "${MY_SHADER_SPV}")
add_executable(my_app src/main.c)
add_dependencies(my_app my_shaders)
target_link_libraries(my_app PRIVATE webvulkan::llvmpipe_wasm)The same helper is available in package mode.
find_package(WebVulkanLlvmpipeWasm REQUIRED CONFIG)
set(MY_SHADER_SPV "${CMAKE_BINARY_DIR}/shaders/write_const.spv")
webvulkan_compile_hlsl_to_spirv(
SOURCE "${CMAKE_SOURCE_DIR}/shaders/write_const.hlsl"
OUTPUT "${MY_SHADER_SPV}"
DXC_WASM_JS "$ENV{WEBVULKAN_DXC_WASM_JS}"
)Ahead-of-time mode
- Use
webvulkan_compile_hlsl_to_spirv(...)in CMake to generate SPIR-V during build.
Runtime mode
- The runtime smoke path compiles HLSL to SPIR-V in runtime through DXC in Wasm.
- The same smoke flow then compiles and registers the runtime Wasm module for the same shader key before Vulkan dispatch.
- See
tests/wasm/tools/smoke_runtime.mjsfor the reference runtime orchestration path.
The package exports a CMake helper that attaches the runtime shader registry C source to your target.
webvulkan_attach_runtime_shader_registry(TARGET <your_target>)
Example
add_subdirectory(path/to/llvmpipe2wasm)
add_executable(my_app src/main.c)
webvulkan_attach_runtime_shader_registry(TARGET my_app)
target_link_libraries(my_app PRIVATE webvulkan::llvmpipe_wasm)In package mode the same helper is available after find_package(WebVulkanLlvmpipeWasm REQUIRED CONFIG).
We run runtime_smoke in CI as the runtime validation test.
The test validates
- HLSL to SPIR-V compilation with DXC in Wasm.
- LLVM IR to Wasm module compilation with Clang in Wasm.
- Runtime registration of SPIR-V and Wasm module for the driver shader key.
- Vulkan loader flow through Volk using
vk_icdGetInstanceProcAddrfrom the wasm ICD path. - Vulkan compute pipeline creation and real dispatch execution.
- Output correctness checks for the dispatched shader.
- Driver identity and provider checks to confirm the lavapipe wasm path.
- Timing comparison between both runtime modes on CI-validated dispatch profiles.
Runtime modes validated in CI
lavapipe_runtime_smoke_fast_wasmlavapipe_runtime_smoke_raw_llvm_ir
Dispatch profiles validated in CI
dispatch_overheadprofile to stress dispatch call overheadbalanced_gridprofile to run a balanced dispatch-grid layout
Extended dispatch profile used in local and explicit smoke runs
large_gridprofile to stress large grid coverage per dispatch
On the current local setup we measured
dispatch_overheadprofilefast_wasmvsraw_llvm_irabout6.13xlower wall timebalanced_gridprofilefast_wasmvsraw_llvm_irabout12.79xlower wall timelarge_gridprofilefast_wasmvsraw_llvm_irabout153.10xlower wall time
CI coverage note
- Default CI smoke validates
dispatch_overheadandbalanced_grid. large_gridis measured through explicit extended smoke target runs.
Shader behavior in this benchmark is intentionally simple and deterministic.
It runs integer mixing ops and writes 0x12345678 into a storage buffer.
Dispatch details in this run
- Workgroup size in this shader is
numthreads(1,1,1) dispatch_overheadrecords1024timesvkCmdDispatch(1,1,1)per submit and runs16submits so total dispatch calls are16384balanced_gridrecords256timesvkCmdDispatch(4,1,1)per submit and runs16submits so total dispatch calls are4096large_gridrecords1timevkCmdDispatch(256,1,1)per submit and runs64submits so total dispatch calls are64- All profiles execute
16384total shader invocations per run for fair cross-profile comparison - The shader writes and validates one 32-bit value in the bound storage buffer
Log excerpt from extended local run (click to expand)
dispatch timing summary
mode=fast_wasm
profile=dispatch_overhead
samples=5
dispatches_per_submit=1024
submit_iterations=16
total_dispatches_per_run=16384
invocations_per_dispatch=1
total_invocations_per_run=16384
min_ms=0.001102
avg_ms=0.001275
max_ms=0.001553
min_ns_per_invocation=1101.575
avg_ns_per_invocation=1274.652
max_ns_per_invocation=1552.795
proof.execute_path=fast_wasm
proof.interpreter=disabled_for_dispatch
proof.llvm_ir_wasm_provider=clang/clang c-runtime+shared-memory-shim
dispatch timing summary
mode=raw_llvm_ir
profile=dispatch_overhead
samples=5
dispatches_per_submit=1024
submit_iterations=16
total_dispatches_per_run=16384
invocations_per_dispatch=1
total_invocations_per_run=16384
min_ms=0.007003
avg_ms=0.007820
max_ms=0.008926
min_ns_per_invocation=7002.985
avg_ns_per_invocation=7819.901
max_ns_per_invocation=8926.306
proof.execute_path=raw_llvm_ir
proof.fast_wasm_provider=raw_llvm_ir
dispatch timing summary
mode=fast_wasm
profile=balanced_grid
samples=5
dispatches_per_submit=256
submit_iterations=16
total_dispatches_per_run=4096
invocations_per_dispatch=4
total_invocations_per_run=16384
min_ms=0.001533
avg_ms=0.002188
max_ms=0.002730
min_ns_per_invocation=383.160
avg_ns_per_invocation=546.995
max_ns_per_invocation=682.477
proof.execute_path=fast_wasm
proof.interpreter=disabled_for_dispatch
proof.llvm_ir_wasm_provider=clang/clang c-runtime+shared-memory-shim
dispatch timing summary
mode=raw_llvm_ir
profile=balanced_grid
samples=5
dispatches_per_submit=256
submit_iterations=16
total_dispatches_per_run=4096
invocations_per_dispatch=4
total_invocations_per_run=16384
min_ms=0.026282
avg_ms=0.027976
max_ms=0.031003
min_ns_per_invocation=6570.514
avg_ns_per_invocation=6994.097
max_ns_per_invocation=7750.800
proof.execute_path=raw_llvm_ir
proof.fast_wasm_provider=raw_llvm_ir
dispatch timing summary
mode=fast_wasm
profile=large_grid
samples=5
dispatches_per_submit=1
submit_iterations=64
total_dispatches_per_run=64
invocations_per_dispatch=256
total_invocations_per_run=16384
min_ms=0.010723
avg_ms=0.017264
max_ms=0.024392
min_ns_per_invocation=41.888
avg_ns_per_invocation=67.439
max_ns_per_invocation=95.282
proof.execute_path=fast_wasm
proof.interpreter=disabled_for_dispatch
proof.llvm_ir_wasm_provider=clang/clang c-runtime+shared-memory-shim
dispatch timing summary
mode=raw_llvm_ir
profile=large_grid
samples=5
dispatches_per_submit=1
submit_iterations=64
total_dispatches_per_run=64
invocations_per_dispatch=256
total_invocations_per_run=16384
min_ms=2.298788
avg_ms=2.643183
max_ms=3.019331
min_ns_per_invocation=8979.639
avg_ns_per_invocation=10324.934
max_ns_per_invocation=11794.263
proof.execute_path=raw_llvm_ir
proof.fast_wasm_provider=raw_llvm_ir
Fairness note for this measurement
- Local dev PC
AMD Ryzen 5 5600G6C/12T,Windows 11 Pro 64-bit (10.0.26200) - CI logs validate the same mode comparison for
dispatch_overheadandbalanced_grid large_gridnumbers come from explicit extended local smoke run
- Driver output is static only
.a - Shared driver output mode is planned
- This repository is not a full browser rendering framework
llvm-wasm-prebuilt-latestincludes LLVM prebuilt bundle onlywebvulkan-package-latestincludes relocatable CMake package onlydxc-wasm-prebuilt-latestincludes DXC-in-Wasm prebuilt bundle only
Each channel ships
- bundle
.zip - checksum
.sha256 - Sigstore signature
.sig - Sigstore certificate
.pem
Verify a downloaded bundle
sha256sum -c <bundle>.zip.sha256
cosign verify-blob \
--signature <bundle>.zip.sig \
--certificate <bundle>.zip.pem \
--certificate-identity-regexp "https://github.com/Devsh-Graphics-Programming/llvmpipe2wasm/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
<bundle>.zipThis project is licensed under GNU AGPL v3.
For dual licensing including proprietary or commercial terms contact Devsh Graphics Programming.