Skip to content

Enable WebGPU backend for Emscripten builds#609

Open
j8asic wants to merge 33 commits intoshader-slang:mainfrom
j8asic:emscripten
Open

Enable WebGPU backend for Emscripten builds#609
j8asic wants to merge 33 commits intoshader-slang:mainfrom
j8asic:emscripten

Conversation

@j8asic
Copy link

@j8asic j8asic commented Dec 27, 2025

Added support for compiling slang-rhi using Emscripten (WASM), while enabling the WebGPU backend and handling dependencies for the web platform:

  • Added logic to detect emscripten
  • enable SLANG_RHI_HAS_WGPU and disable incompatible native APIs when building for Emscripten
  • Skipped fetching the native Dawn binary package when building for Emscripten
  • target_compile_options use modern --use-port=emdawnwebgpu for Emscripten builds
  • core rhi code switches between dawn/emscripten functionality

Summary by CodeRabbit

  • New Features

    • Emscripten/WebAssembly build presets and CI steps
    • WebGPU (WASM) backend with canvas-based surface and window-handle canvas selector
    • Buffer APIs extended to accept native and shared handles
    • New wasm test program and minimal HTML host page
  • Compatibility

    • WASM-specific texture/format handling and compiler/linker adjustments
    • WASM-target warning and linker flag tweaks
  • Chores

    • Tooling improved for WebGPU proc extraction
    • .gitignore updated for emscripten build dir
  • Bug Fixes

    • Safer init/cleanup and synchronous wait handling for WASM

@j8asic j8asic requested a review from a team as a code owner December 27, 2025 12:07
@CLAassistant
Copy link

CLAassistant commented Dec 27, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Collaborator

@skallweitNV skallweitNV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the submission. Overall this looks like it's going into the right direction.

FYI, the WebGPU backend in slang-rhi is very much a work-in-progress and very low priority for us at this point in time. We don't expect anything apart from the most trivial examples to run.

If we want to add support for WASM to slang-rhi, we need to add proper testing through CI. My main fear is that we get incompatible versions of Dawn and emdawnwebgpu. Do you know how emdawnwebgpu is versioned? Is it just implicitly versioned through the emscripten version?

@j8asic
Copy link
Author

j8asic commented Jan 8, 2026

FYI, the WebGPU backend in slang-rhi is very much a work-in-progress and very low priority for us at this point in time. We don't expect anything apart from the most trivial examples to run.

understandable, that's why i'd love to help

If we want to add support for WASM to slang-rhi, we need to add proper testing through CI. My main fear is that we get incompatible versions of Dawn and emdawnwebgpu. Do you know how emdawnwebgpu is versioned? Is it just implicitly versioned through the emscripten version?

This is a pain. Current slang dawn version is old (april 2025?), and seems that emscripten changed versioning from that point. In any case, for now I just decided emscripten is using "newer" webgpu api. There are few cases that have preprocessor checks dawn/emscripten to decide the api call. I see few options to enhance it: use template magic to auto-deduce right api call, or dig a bit on connecting versioning emscripten to dawn (but this would make sense only when Slang updates a bit its dawn version?). Going to think about CI tests now as well...

@j8asic j8asic requested a review from skallweitNV January 8, 2026 20:08
Copy link
Collaborator

@skallweitNV skallweitNV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taken another look. I think it's fine to merge initial WASM support with a little bit of additional cleanup.


#define SLANG_RHI_WGPU_PROCS(x) \
x(AdapterInfoFreeMembers) \
// Define native-only procedures that are not available or different in Emscripten
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like this separation because I currently use the tools/extract-webgpu-procs.py script to generate the SLANG_RHI_WGPU_PROCS(x) definition whenever we update Dawn. This will make this tedious.

Maybe just define all of SLANG_RHI_WGPU_PROCS for both the WASM and Dawn case.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main issue for maintenance. I iterated two-three different solutions and found a compromise as you say to "define all of SLANG_RHI_WGPU_PROCS": since you already have a workflow, I extended your script to fetch (according commit of the) EM webgpu.h, and use it to typedef missing stubs.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we extract two sets of stubs, one from the currently used Dawn library, the other from the webgpu headers? Maybe that's the wrong approach, but I'm not sure what mixing the two buys us?!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, to be straightforward the py script now explicitly defines two sets of procs. Em list is loaded from statically linkage, Dawn dynamically. Initial tests for creating the adapter, device, queue work nicely

@j8asic j8asic requested a review from skallweitNV January 12, 2026 09:41

#define SLANG_RHI_WGPU_PROCS(x) \
x(AdapterInfoFreeMembers) \
// Define native-only procedures that are not available or different in Emscripten
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we extract two sets of stubs, one from the currently used Dawn library, the other from the webgpu headers? Maybe that's the wrong approach, but I'm not sure what mixing the two buys us?!

@skallweitNV
Copy link
Collaborator

@j8asic sorry for not responding earlier. Thanks for addressing the review notes.

I think the remaining open question is regarding the WebGPU API entrypoints. I'd prefer defining the stubs twice, one set for Emscripten, the other for Dawn. The script can then just simply iterate over the two headers and emit a set of stubs for each header. This will make diffs cleaner and generally avoid confusion between the two sets of entry points. Maybe I'm missing something obvious here, but this just seems the simpler more straight forward approach to me than extracting a common set.

Did your change to ship WASM binaries for slang releases already land? If so, it would be neat to clean this up as well. Let me know if we need a new Slang release after your changes have landed.

@j8asic
Copy link
Author

j8asic commented Jan 20, 2026

I think the remaining open question is regarding the WebGPU API entrypoints. I'd prefer defining the stubs twice, one set for Emscripten, the other for Dawn. The script can then just simply iterate over the two headers and emit a set of stubs for each header. This will make diffs cleaner and generally avoid confusion between the two sets of entry points. Maybe I'm missing something obvious here, but this just seems the simpler more straight forward approach to me than extracting a common set.

I thought about this option as well, but went with the current implementation, because Dawn impl assures it implements webgpu.h of emdawngpu + its own extensions; see 1 2. This is also connected to your initial worry on how to define syncing of API versions of dawn and emdawn. I had to manually check em releases compared to current chosen Dawn version. Let me know on your final decision and I will implement any.

Did your change to ship WASM binaries for slang releases already land? If so, it would be neat to clean this up as well. Let me know if we need a new Slang release after your changes have landed.

This got merged after 2026.01, so I similarly suggest we wait for new release containing wasm-libs and clean this up.

@j8asic j8asic requested a review from skallweitNV February 2, 2026 11:43
@j8asic
Copy link
Author

j8asic commented Feb 2, 2026

Added explicit distinction of dawn/em API to be loaded, and tests are running nicely. N.b. I have not created any html tests in the repo, to be run. If you made a decision on how to approach automatic testing let me know.

Please re-review, IMHO this can now be merged, as we are running late with WASM-libs PR (shader-slang/slang#9818). When done, I will add auto fetching from Slang releases.

Btw I have SGL almost running via WASM

@skallweitNV
Copy link
Collaborator

@j8asic sorry for not responding earlier. I will take another look tomorrow hopefully. Thanks for all the effort on this and nice to hear SGL almost works in WASM :)

@j8asic
Copy link
Author

j8asic commented Feb 6, 2026

SGL now works in browsers...
image

Copy link
Collaborator

@skallweitNV skallweitNV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Some suggestions. I think especially the code duplication should be reduced unless there are slight differences that I didn't notice.


#include "core/deferred.h"

#ifdef SLANG_WASM
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of splatting the <emscripten.h> include everywhere, we could just put it into wgpu-api.h once if we need emscripten API in the WASM case in different places.

Comment on lines +178 to +188
#if SLANG_WASM
uint64_t timeoutNS = 0;
WGPUWaitStatus waitStatus = WGPUWaitStatus_Success;
while (true)
{
waitStatus = m_ctx.api.wgpuInstanceWaitAny(m_ctx.instance, SLANG_COUNT_OF(futures), futures, timeoutNS);
if (futures[0].completed)
break;
#ifdef __EMSCRIPTEN__
emscripten_sleep(1);
#endif
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used in a few places, should we wrap this into a helper and put it into wgpu-wasm-utils.h or something? Then actually the emscripten include could go there as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good stuff. i managed to unify the logic into a utils function that should be also optimized for browsers as well. pushing soon

@@ -1,29 +1,106 @@
from pathlib import Path
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes here look good. Thanks for moving towards generating the two sets of procs.

CMakeLists.txt Outdated
# Dawn WebGPU
if(NOT DEFINED SLANG_RHI_DAWN_URL)
# Dawn WebGPU (not used for Emscripten - uses emdawnwebgpu port instead)
if(NOT DEFINED SLANG_RHI_DAWN_URL AND NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also use AND NOT EMSCRIPTEN?

CMakeLists.txt Outdated
Comment on lines 484 to 488
elseif(SLANG_RHI_ENABLE_WGPU AND EMSCRIPTEN)
# For Emscripten, WebGPU headers are provided by emdawnwebgpu port
# Add compile option to use the port which provides include paths
target_compile_options(slang-rhi PRIVATE "--use-port=emdawnwebgpu")
endif()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just move this to a second if block commented with # Link with Emscripten WebGPU. The elseif doesn't really make sense with the # Fetch & setup Dawn WebGPU

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Emscripten/WASM build and runtime support: CI workflow and CMake presets for Emscripten, Slang static/WASM library handling, tools to extract Emscripten WebGPU procs, new WASM-targeted example and HTML, and extensive WASM-specific codepaths across the WGPU layer (static proc linkage, polling waits, canvas surface, and buffer APIs).

Changes

Cohort / File(s) Summary
CI & Workflow
.github/workflows/ci.yml
Adds Emscripten-related CI entries and conditional steps (commented matrix exclusions, Setup/Configure/Build (Emscripten) steps) to run when matrix.compiler == emscripten.
CMake Presets
CMakePresets.json
Adds emscripten configure preset and emscripten-Release / emscripten-Debug build presets pointing to the Emscripten toolchain and WASM-specific flags/linker settings.
Top-level CMake / Slang fetch
CMakeLists.txt
Adds EMSCRIPTEN branches: disables native backends, enables WGPU, adjusts Dawn/WebGPU/GLFW fetch behavior, appends -wasm-libs.zip to Slang URL, treats Slang as STATIC IMPORTED GLOBAL for EMSCRIPTEN and links additional .a libs; adds wasm-target example handling.
Public RHI header
include/slang-rhi.h
Under SLANG_WASM, adds WindowHandleType::WGPUCanvas, char canvasSelector[128], and static WindowHandle::fromWGPUCanvas(...) for HTML canvas selectors.
Core runtime tweaks
src/core/arena-allocator.h, src/core/platform.cpp, src/heap.cpp
Removes a Page-size static_assert; extends dynamic-loading guards to include SLANG_WASM and returns nullptr on unsupported path; casts allocation addresses to uintptr_t.
WGPU procs extraction tool
tools/extract-webgpu-procs.py
Refactors proc extraction to parse header content and fetch Emscripten header remotely: adds EMSCRIPTEN_HEADER_URL, fetch_url(), extract_procs_from_content(), and a new main() that emits separate non-WASM and WASM macro lists.
WGPU API & header
src/wgpu/wgpu-api.h, src/wgpu/wgpu-api.cpp
Introduces WASM static proc linkage and header includes; non-WASM retains dynamic shared-library loading and gains SharedLibraryHandle m_module member guarded by !SLANG_WASM; WASM init statically initializes procs.
Device creation & control flow
src/wgpu/wgpu-device.cpp, src/wgpu/wgpu-device.h, src/rhi.cpp
Reworks adapter/device request flows to use local request-state structs and wgpu::wait polling (WASM uses emscripten sleep), adds Default path fallback to WGPU for WASM, and declares createBufferFromSharedHandle.
Buffer, command, shader, surface changes
src/wgpu/wgpu-buffer.cpp, src/wgpu/wgpu-command.cpp, src/wgpu/wgpu-shader-program.cpp, src/wgpu/wgpu-surface.cpp, src/wgpu/wgpu-utils.cpp
Adds createBufferFromNativeHandle (and stub for shared handle), replaces blocking waits with wgpu::wait and WASM polling, disables indirect draw on WASM, uses WGSL WGPUStringView on WASM, adds HTML canvas surface handling, forces render-target usage when needed, and maps some 16-bit formats to Undefined under WASM.
WGPU utils & public API additions
src/wgpu/wgpu-utils.h, src/wgpu/wgpu-utils.cpp
Adds struct Context forward decl and two wait overloads; implements platform-specific wait: busy-poll with emscripten_get_now/emscripten_sleep for WASM and wgpuInstanceWaitAny for non-WASM.
WASM test example & web assets
examples/wasm/wasm-test.cpp, examples/wasm/index.html
Adds wasm-test.cpp exercising WGPU device/queue/buffer lifecycle and invalid-handle checks, plus index.html hosting WASM output with Module print handlers.
Misc
.gitignore
Adds build.em/ ignore rule.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant App as Application
    participant Init as DeviceImpl::initCommon()
    participant Inst as WGPU Instance
    participant Adpt as AdapterRequestState
    participant Dev as DeviceRequestState
    participant EM as Emscripten Event Loop

    App->>Init: initCommon()
    Init->>Inst: wgpuInstanceRequestAdapter(callback)
    Inst-->>Adpt: callback scheduled

    Note over Init,EM: WASM polling loop (wgpu::wait)
    loop poll until adapter ready
      Init->>EM: emscripten_sleep(...)
      EM->>Inst: process events
      Inst-->>Adpt: update state
      Init->>Init: check state.isComplete
    end

    Adpt-->>Init: adapter provided
    Init->>Inst: wgpuAdapterRequestDevice(callback)
    Inst-->>Dev: callback scheduled

    Note over Init,EM: WASM polling loop (wgpu::wait)
    loop poll until device ready
      Init->>EM: emscripten_sleep(...)
      EM->>Inst: process events
      Inst-->>Dev: update state
      Init->>Init: check state.isComplete
    end

    Dev-->>Init: device provided
    Init->>App: return SLANG_OK
Loading
sequenceDiagram
    autonumber
    participant Caller as mapBuffer()
    participant WGPU as WGPU Buffer
    participant EM as Emscripten Event Loop

    Caller->>WGPU: request map (wgpuBufferMapAsync)
    alt SLANG_WASM
        loop until mapped
            Caller->>WGPU: poll status (wgpu::wait)
            WGPU-->>Caller: not ready
            Caller->>EM: emscripten_sleep(1)
            EM->>WGPU: process events
        end
    else Non-WASM
        Caller->>WGPU: wgpuInstanceWaitAny() (blocking)
        WGPU-->>Caller: ready
    end
    Caller->>Caller: return mapped data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped through code to build a WebAssembly tune,
Polling tiny sleeps beneath a crescent moon,
Canvas selectors, static procs we weave,
Buffers mapped and shaders on the eve,
Emscripten hums — the rabbit gives a tune!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Enable WebGPU backend for Emscripten builds' clearly and concisely describes the primary objective of the changeset: adding WebGPU support for Emscripten/WASM compilation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the high-level summary generated by CodeRabbit.

Configure the reviews.high_level_summary_instructions setting to provide custom instructions for generating the high-level summary.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/platform.cpp (1)

5-71: ⚠️ Potential issue | 🔴 Critical

Add _GNU_SOURCE to Emscripten build configuration or guard dladdr for SLANG_WASM.

The code unconditionally uses dladdr() and Dl_info for SLANG_WASM, but Emscripten's dlfcn.h only declares these when _GNU_SOURCE or _BSD_SOURCE is defined. This will cause a compilation error. Either enable _GNU_SOURCE in the CMakeLists.txt for Emscripten builds, or conditionally stub findSharedLibraryPath() for SLANG_WASM since dynamic symbol lookup has limited practical use in WebAssembly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/platform.cpp` around lines 5 - 71, The SLANG_WASM build fails
because findSharedLibraryPath() uses dladdr() and Dl_info which Emscripten only
exposes when _GNU_SOURCE/_BSD_SOURCE is defined; fix by either (A) enabling
_GNU_SOURCE for Emscripten targets in your build (CMake) so dladdr/Dl_info are
declared, or (B) guard the wasm case in findSharedLibraryPath() (and related
uses of dladdr/Dl_info) with SLANG_WASM and provide a safe stub that returns
nullptr (or an appropriate fallback) since dynamic symbol lookup is limited in
WebAssembly; update the implementation around findSharedLibraryPath, the
SLANG_WASM preprocessor branches, and any callers that assume a non-null return.
🧹 Nitpick comments (2)
src/heap.cpp (1)

51-51: Consider using C++ style cast for consistency.

The explicit cast to uintptr_t is appropriate for WASM/Emscripten compatibility. However, line 93 uses static_cast<Page*>(), so using C++ style casts here would be more consistent throughout the file.

♻️ Suggested change for consistency
-                *outAllocation = {offset, size, page, pageAllocation.metadata, (uintptr_t)page->offsetToAddress(offset)};
+                *outAllocation = {offset, size, page, pageAllocation.metadata, reinterpret_cast<uintptr_t>(page->offsetToAddress(offset))};
-            *outAllocation = {offset, size, newPage, pageAllocation.metadata, (uintptr_t)newPage->offsetToAddress(offset)};
+            *outAllocation = {offset, size, newPage, pageAllocation.metadata, reinterpret_cast<uintptr_t>(newPage->offsetToAddress(offset))};

Also applies to: 82-82

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/heap.cpp` at line 51, Replace the C-style cast used when constructing
outAllocation so it uses a C++ style cast: change the
(uintptr_t)page->offsetToAddress(offset) expression to
static_cast<uintptr_t>(page->offsetToAddress(offset)), and make the same change
for the other occurrence referenced (the one around the code that uses Page and
offsetToAddress); locate the allocation construction where outAllocation is
assigned and update the cast to static_cast<uintptr_t> to match the existing use
of static_cast<Page*> and keep casting consistent across this file.
src/wgpu/wgpu-device.cpp (1)

65-136: Apply m_ prefix to request‑state members.

Rename the struct fields (e.g., status, adapter, device, message) to m_status, m_adapter, m_device, m_message and update their uses for consistency.

As per coding guidelines, "Member variables should start with 'm_' prefix and be in camelCase".

Also applies to: 229-319

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wgpu/wgpu-device.cpp` around lines 65 - 136, Rename AdapterRequestState's
member fields to use the m_ prefix (e.g., status -> m_status, adapter ->
m_adapter) and update all references accordingly: set requestState->m_status and
requestState->m_adapter inside the WGPURequestAdapterCallback, check
state->m_status in both the WASM and non‑WASM wait paths, and read
state->m_adapter when assigning resultAdapter; also update the delete/state/null
checks to use the new names. Apply the same m_ prefix renaming and fixups for
the other request-state structs referenced elsewhere (e.g., fields named device,
message) so all member accesses match the new m_<name> identifiers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/wgpu/wgpu-buffer.cpp`:
- Around line 124-138: In DeviceImpl::createBufferFromNativeHandle, validate the
native handle value before using it: if handle.type !=
NativeHandleType::WGPUBuffer or handle.value == 0, set *outBuffer = nullptr and
return SLANG_E_INVALID_HANDLE (avoid calling m_ctx.api.wgpuBufferAddRef on a
zero handle). When successful, proceed to create BufferImpl, assign
buffer->m_buffer from handle.value, call m_ctx.api.wgpuBufferAddRef, and return
via returnComPtr(outBuffer, buffer); ensure every early error path clears the
output pointer to nullptr to match the established pattern.

In `@tools/extract-webgpu-procs.py`:
- Around line 71-78: The except block for fetching EMSCRIPTEN_HEADER_URL only
initializes em_procs, leaving em_items undefined and causing UnboundLocalError;
modify the exception handling in the try/except around
fetch_url(EMSCRIPTEN_HEADER_URL) / extract_procs_from_content to either
initialize em_items (e.g., em_items = [] or set()) along with em_procs, or
return/exit early after logging the error so later code that references em_items
doesn't run; update the block that catches Exception as e to ensure both
em_items and em_procs are defined (or short-circuit) so subsequent references
are safe.
- Around line 14-49: The fallback branch in extract_procs_from_content returns
an empty items list when BEGIN_MARKER is missing, causing later macro emission
to have no proc entries; change the begin==-1 branch to build and return both
items (list of ("proc", name) tuples) and proc_names by iterating the typedef
regex matches (the re.finditer(r"typedef\s+\S+\s+\(\*WGPUProc(\w+)\)",
content)), appending ("proc", match.group(1)) to items and adding to proc_names
so downstream macro generation has valid proc entries.

---

Outside diff comments:
In `@src/core/platform.cpp`:
- Around line 5-71: The SLANG_WASM build fails because findSharedLibraryPath()
uses dladdr() and Dl_info which Emscripten only exposes when
_GNU_SOURCE/_BSD_SOURCE is defined; fix by either (A) enabling _GNU_SOURCE for
Emscripten targets in your build (CMake) so dladdr/Dl_info are declared, or (B)
guard the wasm case in findSharedLibraryPath() (and related uses of
dladdr/Dl_info) with SLANG_WASM and provide a safe stub that returns nullptr (or
an appropriate fallback) since dynamic symbol lookup is limited in WebAssembly;
update the implementation around findSharedLibraryPath, the SLANG_WASM
preprocessor branches, and any callers that assume a non-null return.

---

Nitpick comments:
In `@src/heap.cpp`:
- Line 51: Replace the C-style cast used when constructing outAllocation so it
uses a C++ style cast: change the (uintptr_t)page->offsetToAddress(offset)
expression to static_cast<uintptr_t>(page->offsetToAddress(offset)), and make
the same change for the other occurrence referenced (the one around the code
that uses Page and offsetToAddress); locate the allocation construction where
outAllocation is assigned and update the cast to static_cast<uintptr_t> to match
the existing use of static_cast<Page*> and keep casting consistent across this
file.

In `@src/wgpu/wgpu-device.cpp`:
- Around line 65-136: Rename AdapterRequestState's member fields to use the m_
prefix (e.g., status -> m_status, adapter -> m_adapter) and update all
references accordingly: set requestState->m_status and requestState->m_adapter
inside the WGPURequestAdapterCallback, check state->m_status in both the WASM
and non‑WASM wait paths, and read state->m_adapter when assigning resultAdapter;
also update the delete/state/null checks to use the new names. Apply the same m_
prefix renaming and fixups for the other request-state structs referenced
elsewhere (e.g., fields named device, message) so all member accesses match the
new m_<name> identifiers.

Comment on lines +14 to +49
def extract_procs_from_content(content: str) -> tuple[list[tuple[str, str]], set[str]]:
"""
Extract procs from header content.
Returns:
- List of (type, name) tuples where type is 'comment' or 'proc'
- Set of proc names
"""
begin = content.find(BEGIN_MARKER)
if begin == -1:
# Emscripten header uses different format - search for typedef patterns
procs = set()
for match in re.finditer(r"typedef\s+\S+\s+\(\*WGPUProc(\w+)\)", content):
procs.add(match.group(1))
return [], procs

begin += len(BEGIN_MARKER)
end = content.find(END_MARKER)
lines = content[begin:end].splitlines()

items = []
proc_names = set()

for line in lines:
line = line.strip()
if line == "":
continue
name = RE_PROC.search(line).group(1)
output += " x(" + name + ") \\\n"
if line.startswith("//"):
items.append(("comment", line[3:]))
else:
match = RE_PROC.search(line)
if match:
name = match.group(1)
items.append(("proc", name))
proc_names.add(name)

return items, proc_names
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fallback extraction drops proc items, producing invalid macro output.

When BEGIN_MARKER is missing you return an empty items list, but later emit the macro from em_items. That yields a dangling backslash and no procs. Populate items from the typedef matches so output remains valid.

🐛 Proposed fix
-    if begin == -1:
-        # Emscripten header uses different format - search for typedef patterns
-        procs = set()
-        for match in re.finditer(r"typedef\s+\S+\s+\(\*WGPUProc(\w+)\)", content):
-            procs.add(match.group(1))
-        return [], procs
+    if begin == -1:
+        # Emscripten header uses different format - search for typedef patterns
+        items = []
+        procs = set()
+        for match in re.finditer(r"typedef\s+\S+\s+\(\*WGPUProc(\w+)\)", content):
+            name = match.group(1)
+            if name in procs:
+                continue
+            items.append(("proc", name))
+            procs.add(name)
+        return items, procs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/extract-webgpu-procs.py` around lines 14 - 49, The fallback branch in
extract_procs_from_content returns an empty items list when BEGIN_MARKER is
missing, causing later macro emission to have no proc entries; change the
begin==-1 branch to build and return both items (list of ("proc", name) tuples)
and proc_names by iterating the typedef regex matches (the
re.finditer(r"typedef\s+\S+\s+\(\*WGPUProc(\w+)\)", content)), appending
("proc", match.group(1)) to items and adding to proc_names so downstream macro
generation has valid proc entries.

Comment on lines +71 to +78
print(f"// Fetching Emscripten header from {EMSCRIPTEN_HEADER_URL}...")
try:
em_content = fetch_url(EMSCRIPTEN_HEADER_URL)
em_items, em_procs = extract_procs_from_content(em_content)
except Exception as e:
print(f"// Error fetching Emscripten header: {e}")
em_procs = set()

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

em_items is undefined on fetch failure.

The exception path only sets em_procs, but em_items is referenced later, causing an UnboundLocalError. Initialize it or return early.

🐛 Proposed fix
-    except Exception as e:
-        print(f"// Error fetching Emscripten header: {e}")
-        em_procs = set()
+    except Exception as e:
+        print(f"// Error fetching Emscripten header: {e}")
+        em_items = []
+        em_procs = set()
🧰 Tools
🪛 Ruff (0.15.1)

[warning] 75-75: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/extract-webgpu-procs.py` around lines 71 - 78, The except block for
fetching EMSCRIPTEN_HEADER_URL only initializes em_procs, leaving em_items
undefined and causing UnboundLocalError; modify the exception handling in the
try/except around fetch_url(EMSCRIPTEN_HEADER_URL) / extract_procs_from_content
to either initialize em_items (e.g., em_items = [] or set()) along with
em_procs, or return/exit early after logging the error so later code that
references em_items doesn't run; update the block that catches Exception as e to
ensure both em_items and em_procs are defined (or short-circuit) so subsequent
references are safe.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
CMakeLists.txt (1)

500-517: Consider restructuring WebGPU port handling into a separate block.

A past review suggested using a separate if block with a distinct comment for the Emscripten WebGPU port, rather than an elseif attached to the Dawn fetch block. This would improve clarity since the emdawnwebgpu port is conceptually distinct from the Dawn binary fetch.

Suggested restructure
 # Fetch & setup Dawn WebGPU
 if(SLANG_RHI_ENABLE_WGPU AND NOT EMSCRIPTEN)
     message(STATUS "Fetching Dawn WebGPU ...")
     FetchPackage(dawn URL ${SLANG_RHI_DAWN_URL})
     add_library(slang-rhi-dawn INTERFACE)
     target_include_directories(slang-rhi-dawn INTERFACE ${dawn_SOURCE_DIR}/include)
     if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
         copy_file(${dawn_SOURCE_DIR}/bin/dawn.dll .)
     elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
         copy_file(${dawn_SOURCE_DIR}/lib64/libdawn.so .)
     elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
         copy_file(${dawn_SOURCE_DIR}/lib/libdawn.dylib .)
     endif()
     target_link_libraries(slang-rhi PRIVATE slang-rhi-dawn)
-elseif(SLANG_RHI_ENABLE_WGPU AND EMSCRIPTEN)
+endif()
+
+# Link with Emscripten WebGPU
+if(SLANG_RHI_ENABLE_WGPU AND EMSCRIPTEN)
     # For Emscripten, WebGPU headers are provided by emdawnwebgpu port
     # Add compile option to use the port which provides include paths
     target_compile_options(slang-rhi PRIVATE "--use-port=emdawnwebgpu")
 endif()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 500 - 517, The WebGPU Emscripten handling should
be split out from the Dawn fetch elseif into its own clearly commented if-block:
when SLANG_RHI_ENABLE_WGPU is true and EMSCRIPTEN is set, add the compile option
"--use-port=emdawnwebgpu" for target_compile_options(slang-rhi) in a standalone
if(SLANG_RHI_ENABLE_WGPU AND EMSCRIPTEN) block with a comment explaining the
emscripten port, and keep the
FetchPackage(dawn)/target_include_directories(slang-rhi-dawn)/copy_file logic
inside the separate if(SLANG_RHI_ENABLE_WGPU AND NOT EMSCRIPTEN) block so the
two flows are conceptually and visually distinct.
🧹 Nitpick comments (1)
CMakeLists.txt (1)

275-282: Consider using WARNING instead of STATUS for version override.

When the Slang version is overridden due to missing WASM support, a WARNING level message would be more visible to users who may have explicitly set an older version.

Suggestion
         if(SLANG_VERSION VERSION_LESS SLANG_WASM_MIN_VERSION)
-            message(STATUS "Slang ${SLANG_VERSION} did not release WASM libraries, overriding to ${SLANG_WASM_MIN_VERSION}")
+            message(WARNING "Slang ${SLANG_VERSION} did not release WASM libraries, overriding to ${SLANG_WASM_MIN_VERSION}")
             set(SLANG_VERSION ${SLANG_WASM_MIN_VERSION})
         endif()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 275 - 282, Change the message() level from
STATUS to WARNING in the Slang WASM override block: when CMAKE_SYSTEM_NAME is
"Emscripten" and SLANG_VERSION is forced to SLANG_WASM_MIN_VERSION, call
message(WARNING ...) instead of message(STATUS ...) so the override of
SLANG_VERSION and the value of SLANG_WASM_MIN_VERSION are more visible to users;
update the existing message invocation that references SLANG_VERSION and
SLANG_WASM_MIN_VERSION accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@CMakeLists.txt`:
- Around line 500-517: The WebGPU Emscripten handling should be split out from
the Dawn fetch elseif into its own clearly commented if-block: when
SLANG_RHI_ENABLE_WGPU is true and EMSCRIPTEN is set, add the compile option
"--use-port=emdawnwebgpu" for target_compile_options(slang-rhi) in a standalone
if(SLANG_RHI_ENABLE_WGPU AND EMSCRIPTEN) block with a comment explaining the
emscripten port, and keep the
FetchPackage(dawn)/target_include_directories(slang-rhi-dawn)/copy_file logic
inside the separate if(SLANG_RHI_ENABLE_WGPU AND NOT EMSCRIPTEN) block so the
two flows are conceptually and visually distinct.

---

Nitpick comments:
In `@CMakeLists.txt`:
- Around line 275-282: Change the message() level from STATUS to WARNING in the
Slang WASM override block: when CMAKE_SYSTEM_NAME is "Emscripten" and
SLANG_VERSION is forced to SLANG_WASM_MIN_VERSION, call message(WARNING ...)
instead of message(STATUS ...) so the override of SLANG_VERSION and the value of
SLANG_WASM_MIN_VERSION are more visible to users; update the existing message
invocation that references SLANG_VERSION and SLANG_WASM_MIN_VERSION accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff7c064 and 06c7821.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • CMakeLists.txt
  • include/slang-rhi.h
  • src/heap.cpp
  • src/rhi.cpp
  • src/wgpu/wgpu-command.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/ci.yml
  • src/wgpu/wgpu-command.cpp

@skallweitNV
Copy link
Collaborator

@j8asic Looks like there are bunch of compilation failures. Can you also look at the review comments from coderabbit? I'll take another look when you feel it's ready.

@j8asic
Copy link
Author

j8asic commented Feb 23, 2026

@j8asic Looks like there are bunch of compilation failures. Can you also look at the review comments from coderabbit? I'll take another look when you feel it's ready.

yeah I had to stop in the middle of pushing and now resumed based on your comments sorry

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/wasm/index.html`:
- Around line 1-25: The HTML is missing a language declaration and the live log
element isn't exposed to assistive tech; update the <html> element to include a
lang attribute (e.g., lang="en") and make the <pre id="output"> a live region by
adding ARIA attributes such as role="status" and aria-live="polite" (optionally
aria-atomic="true") so output appended by Module.print / Module.printErr is
announced to screen readers.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33d103f and 8354f5d.

📒 Files selected for processing (2)
  • .gitignore
  • examples/wasm/index.html

@j8asic j8asic requested a review from skallweitNV February 24, 2026 19:08
Copy link
Collaborator

@skallweitNV skallweitNV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks good now.

CMakeLists.txt Outdated
Comment on lines +275 to +282
if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
# Emscripten WASM releases are available starting from v2026.3.1
set(SLANG_WASM_MIN_VERSION "2026.3.1")
if(SLANG_VERSION VERSION_LESS SLANG_WASM_MIN_VERSION)
message(STATUS "Slang ${SLANG_VERSION} did not release WASM libraries, overriding to ${SLANG_WASM_MIN_VERSION}")
set(SLANG_VERSION ${SLANG_WASM_MIN_VERSION})
endif()
endif()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this? slang-rhi is now upated to use 2026.3.1 so this should be obsolete.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok that is good news, this was temp till rhi gets updated

CMakeLists.txt Outdated
INTERFACE_INCLUDE_DIRECTORIES ${SLANG_RHI_SLANG_INCLUDE_DIR}
IMPORTED_LOCATION ${SLANG_RHI_SLANG_BINARY_DIR}/lib/lib${SLANG_LIB_NAME}.dylib
)
elseif(EMSCRIPTEN)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use elseif(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") here for consistency?

Comment on lines +991 to +1016
if(NOT EMSCRIPTEN)
function(add_example name source)
add_executable(${name} ${source})
target_compile_features(${name} PRIVATE cxx_std_20)
get_filename_component(EXAMPLE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${source}" PATH)
target_compile_definitions(${name} PRIVATE SLANG_RHI_DEBUG=$<BOOL:$<CONFIG:Debug>> EXAMPLE_DIR="${EXAMPLE_DIR}" NOMINMAX)
target_include_directories(${name} PRIVATE examples/base)
target_link_libraries(${name} PRIVATE slang-rhi slang glfw)
endfunction()

add_example(example-shader-toy examples/shader-toy/example-shader-toy.cpp)
add_example(example-surface examples/surface/example-surface.cpp)
add_example(example-triangle examples/triangle/example-triangle.cpp)

add_example(example-shader-toy examples/shader-toy/example-shader-toy.cpp)
add_example(example-surface examples/surface/example-surface.cpp)
add_example(example-triangle examples/triangle/example-triangle.cpp)
else()
add_executable(wasm-test examples/wasm/wasm-test.cpp)
target_compile_features(wasm-test PRIVATE cxx_std_17)
target_link_libraries(wasm-test PRIVATE slang-rhi slang)
set_target_properties(wasm-test PROPERTIES
OUTPUT_NAME "wasm-test"
SUFFIX ".html"
)
target_link_options(wasm-test PRIVATE
"--use-port=emdawnwebgpu"
"--shell-file" "${CMAKE_CURRENT_SOURCE_DIR}/examples/wasm/index.html"
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not super happy about this .. I think the goal should be that examples generally also work on WASM. But we can leave this for now.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would rather try to make another PR for testing (iirc you wrote you need to think how to handle automatic testing?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this is fine for now

Comment on lines +92 to +93
"CMAKE_C_FLAGS_INIT": "-fwasm-exceptions -Os",
"CMAKE_CXX_FLAGS_INIT": "-fwasm-exceptions -Os",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want -Os size optimizations?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

"SLANG_RHI_ENABLE_CPU": "OFF",
"CMAKE_C_FLAGS_INIT": "-fwasm-exceptions -Os",
"CMAKE_CXX_FLAGS_INIT": "-fwasm-exceptions -Os",
"CMAKE_EXE_LINKER_FLAGS": "-sASSERTIONS -sALLOW_MEMORY_GROWTH -fwasm-exceptions --export=__cpp_exception -sASYNCIFY -sASYNCIFY_IGNORE_INDIRECT=1 -sASYNCIFY_ONLY=['*main*','*wait*','*initialize*','*map*','*readBuffer*','*createBuffer*']"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does ASYNCIFY do? Feels bad to have to add this to the linker flags ...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the price for using sync functions without freezing the browser, and this is the best outcome i.e. to tag only specific functions needing to use asyncify. Otherwise the file size would be large, and compilation is slow

.DS_Store
.vscode/
build/
build.em/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does emscripten build not use build folder?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH don't know, you tell me (taken this practice from the Slang repo)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is our build ending up in build.em at all?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes "binaryDir": "${sourceDir}/build.em",

Comment on lines +34 to +35
struct Context;
typedef Context WGPUContext;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just use Context without introducing a type alias?


#include "core/deferred.h"


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra newline

#include "wgpu-shader-object.h"
#include "wgpu-utils.h"


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra newline

WGPURequestAdapterStatus status = WGPURequestAdapterStatus(0);
WGPUAdapter adapter = nullptr;
};
AdapterRequestState* state = new AdapterRequestState();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be heap allocated?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've read about the browser caveats, and went with safer option not to trust asyncify stack saving. But maybe newer emscripten and browsers have it under control. Let me test the stack version and then update

@skallweitNV
Copy link
Collaborator

@j8asic sorry for not being responsive on this, I've been busy. Is this ready for a "final" review? :D

@j8asic j8asic requested a review from skallweitNV March 17, 2026 11:05
@j8asic
Copy link
Author

j8asic commented Mar 17, 2026

@skallweitNV no worries I have been busy as well. The code is used already in prod from my side (along with sgl in wasm)

Copy link
Collaborator

@skallweitNV skallweitNV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, let's update this to latest main and then we can merge.

buffer->m_buffer = m_ctx.api.wgpuDeviceCreateBuffer(m_ctx.device, &bufferDesc);
if (!buffer->m_buffer)
{
*outBuffer = nullptr;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think client code should not rely on this. Is this needed?

);
#else
SLANG_UNUSED(cmd);
NOT_SUPPORTED(S_RenderPassEncoder_drawIndirect);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will need to be changed to NOT_SUPPORTED(IRenderPassEncoder, drawIndirect); when updating to latest main.

);
#else
SLANG_UNUSED(cmd);
NOT_SUPPORTED(S_RenderPassEncoder_drawIndexedIndirect);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT_SUPPORTED(IRenderPassEncoder, drawIndexedIndirect);


#include "core/assert.h"

#include "wgpu-device.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wgpu includes should come first, move this before or after wgpu-utils.h

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants