Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/webgpu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,41 @@ const style = StyleSheet.create({
});
```

## Dawn Debug Toggles

Dawn instance toggles are configured natively before WebGPU is installed.
This keeps initialization order deterministic and avoids JS import-order issues.

On iOS, add the following keys to your app's `Info.plist`:

```xml
<key>RNWebGPUEnableToggles</key>
<array>
<string>allow_unsafe_apis</string>
</array>
<key>RNWebGPUDisableToggles</key>
<array>
<string>disallow_spirv</string>
</array>
```

On Android, add `meta-data` entries to your app's `AndroidManifest.xml`:

```xml
<application>
<meta-data
android:name="com.webgpu.enable_toggles"
android:value="allow_unsafe_apis" />
<meta-data
android:name="com.webgpu.disable_toggles"
android:value="disallow_spirv" />
</application>
```

Android toggle lists are comma-separated strings. Empty entries are ignored.
If you inject these values from build-time environment variables or config plugins,
emit them into `Info.plist` and `AndroidManifest.xml`.

## Example App

To run the example app you first need to [install Dawn](#installing-dawn).
Expand Down
49 changes: 45 additions & 4 deletions packages/webgpu/android/cpp/cpp-adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ std::shared_ptr<rnwgpu::RNWebGPUManager> manager;

extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUModule_initializeNative(
JNIEnv *env, jobject /* this */, jlong jsRuntime,
jobject jsCallInvokerHolder, jobject blobModule) {
jobject jsCallInvokerHolder, jobject blobModule,
jobjectArray enableToggles, jobjectArray disableToggles) {
auto runtime = reinterpret_cast<facebook::jsi::Runtime *>(jsRuntime);
jobject globalBlobModule = env->NewGlobalRef(blobModule);
auto jsCallInvoker{
Expand All @@ -29,8 +30,48 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUModule_initializeNative(
->getCallInvoker()};
auto platformContext =
std::make_shared<rnwgpu::AndroidPlatformContext>(globalBlobModule);
manager = std::make_shared<rnwgpu::RNWebGPUManager>(runtime, jsCallInvoker,
platformContext);

// Convert Java string arrays to std::vector<std::string>
std::vector<std::string> enableVec;
std::vector<std::string> disableVec;
if (enableToggles != nullptr) {
jsize len = env->GetArrayLength(enableToggles);
for (jsize i = 0; i < len; i++) {
auto jstr = (jstring)env->GetObjectArrayElement(enableToggles, i);
if (jstr == nullptr) {
continue;
}
const char *cstr = env->GetStringUTFChars(jstr, nullptr);
if (cstr == nullptr) {
env->DeleteLocalRef(jstr);
continue;
}
enableVec.emplace_back(cstr);
env->ReleaseStringUTFChars(jstr, cstr);
env->DeleteLocalRef(jstr);
}
}
if (disableToggles != nullptr) {
jsize len = env->GetArrayLength(disableToggles);
for (jsize i = 0; i < len; i++) {
auto jstr = (jstring)env->GetObjectArrayElement(disableToggles, i);
if (jstr == nullptr) {
continue;
}
const char *cstr = env->GetStringUTFChars(jstr, nullptr);
if (cstr == nullptr) {
env->DeleteLocalRef(jstr);
continue;
}
disableVec.emplace_back(cstr);
env->ReleaseStringUTFChars(jstr, cstr);
env->DeleteLocalRef(jstr);
}
}

manager = std::make_shared<rnwgpu::RNWebGPUManager>(
runtime, jsCallInvoker, platformContext,
std::move(enableVec), std::move(disableVec));
}

extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceChanged(
Expand Down Expand Up @@ -68,4 +109,4 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceDestroy(
JNIEnv *env, jobject thiz, jint contextId) {
auto &registry = rnwgpu::SurfaceRegistry::getInstance();
registry.removeSurfaceInfo(contextId);
}
}
56 changes: 50 additions & 6 deletions packages/webgpu/android/src/main/java/com/webgpu/WebGPUModule.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.webgpu;

import android.util.Log;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.annotation.OptIn;

import java.util.HashSet;
import java.util.Set;
import java.util.ArrayList;
import java.util.List;

import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactApplicationContext;
Expand All @@ -14,7 +17,6 @@
import com.facebook.react.common.annotations.FrameworkAPI;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.blob.BlobModule;
import com.facebook.react.modules.blob.BlobProvider;
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder;

Expand All @@ -40,11 +42,53 @@ public boolean install() {
if (blobModule == null) {
throw new RuntimeException("React Native's BlobModule was not found!");
}
initializeNative(jsContext.get(), (CallInvokerHolderImpl) callInvokerHolder, blobModule);

String[] enableToggles = readToggleMetadata("com.webgpu.enable_toggles");
String[] disableToggles = readToggleMetadata("com.webgpu.disable_toggles");

initializeNative(jsContext.get(), (CallInvokerHolderImpl) callInvokerHolder, blobModule,
enableToggles, disableToggles);
return true;
}

private String[] readToggleMetadata(String key) {
Bundle metadata = getApplicationMetadata();
if (metadata == null) {
return new String[0];
}
return parseToggleList(metadata.getString(key));
}

@Nullable
private Bundle getApplicationMetadata() {
ReactApplicationContext context = getReactApplicationContext();
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
context.getPackageName(), PackageManager.GET_META_DATA);
return appInfo.metaData;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}

private static String[] parseToggleList(@Nullable String rawValue) {
if (rawValue == null || rawValue.trim().isEmpty()) {
return new String[0];
}

List<String> toggles = new ArrayList<>();
String[] parts = rawValue.split(",");
for (String part : parts) {
String toggle = part.trim();
if (!toggle.isEmpty()) {
toggles.add(toggle);
}
}
return toggles.toArray(new String[0]);
}

@OptIn(markerClass = FrameworkAPI.class)
@DoNotStrip
private native void initializeNative(long jsRuntime, CallInvokerHolderImpl jsInvoker, BlobModule blobModule);
private native void initializeNative(long jsRuntime, CallInvokerHolderImpl jsInvoker,
BlobModule blobModule, String[] enableToggles, String[] disableToggles);
}
44 changes: 44 additions & 0 deletions packages/webgpu/app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const config_plugins_1 = require("@expo/config-plugins");
const withWebGPUAndroid = (config, { enableToggles = [], disableToggles = [] } = {}) => {
return (0, config_plugins_1.withAndroidManifest)(config, (config) => {
const app = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
if (!app['meta-data'])
app['meta-data'] = [];
if (enableToggles.length > 0) {
app['meta-data'].push({
$: {
'android:name': 'com.webgpu.enable_toggles',
'android:value': enableToggles.join(','),
},
});
}
if (disableToggles.length > 0) {
app['meta-data'].push({
$: {
'android:name': 'com.webgpu.disable_toggles',
'android:value': disableToggles.join(','),
},
});
}
return config;
});
};
const withWebGPUIos = (config, { enableToggles = [], disableToggles = [] } = {}) => {
return (0, config_plugins_1.withInfoPlist)(config, (config) => {
if (enableToggles.length > 0) {
config.modResults['RNWebGPUEnableToggles'] = enableToggles;
}
if (disableToggles.length > 0) {
config.modResults['RNWebGPUDisableToggles'] = disableToggles;
}
return config;
});
};
const withWebGPU = (config, options = {}) => {
config = withWebGPUAndroid(config, options);
config = withWebGPUIos(config, options);
return config;
};
exports.default = withWebGPU;
35 changes: 33 additions & 2 deletions packages/webgpu/apple/WebGPUModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,30 @@ @interface RCTBridge (JSIRuntime)
- (void *)runtime;
@end

static std::vector<std::string> ReadToggleArray(NSDictionary *infoDictionary,
NSString *key) {
std::vector<std::string> toggles;
id value = infoDictionary[key];
if (![value isKindOfClass:[NSArray class]]) {
return toggles;
}

for (id item in (NSArray *)value) {
if (![item isKindOfClass:[NSString class]]) {
continue;
}
NSString *toggle = [(NSString *)item
stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (toggle.length == 0) {
continue;
}
toggles.push_back(toggle.UTF8String);
}

return toggles;
}

@implementation WebGPUModule

RCT_EXPORT_MODULE(WebGPUModule)
Expand Down Expand Up @@ -72,10 +96,17 @@ - (void)invalidate {
return [NSNumber numberWithBool:NO];
}

NSDictionary *infoDictionary = NSBundle.mainBundle.infoDictionary ?: @{};
auto enableToggles =
ReadToggleArray(infoDictionary, @"RNWebGPUEnableToggles");
auto disableToggles =
ReadToggleArray(infoDictionary, @"RNWebGPUDisableToggles");

std::shared_ptr<rnwgpu::PlatformContext> platformContext =
std::make_shared<rnwgpu::ApplePlatformContext>();
webgpuManager = std::make_shared<rnwgpu::RNWebGPUManager>(runtime, jsInvoker,
platformContext);
webgpuManager = std::make_shared<rnwgpu::RNWebGPUManager>(
runtime, jsInvoker, platformContext,
std::move(enableToggles), std::move(disableToggles));
return @true;
}

Expand Down
8 changes: 6 additions & 2 deletions packages/webgpu/cpp/rnwgpu/RNWebGPUManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,18 @@ namespace rnwgpu {
RNWebGPUManager::RNWebGPUManager(
jsi::Runtime *jsRuntime,
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker,
std::shared_ptr<PlatformContext> platformContext)
std::shared_ptr<PlatformContext> platformContext,
std::vector<std::string> enableToggles,
std::vector<std::string> disableToggles)
: _jsRuntime(jsRuntime), _jsCallInvoker(jsCallInvoker),
_platformContext(platformContext) {

// Register main runtime for RuntimeAwareCache
BaseRuntimeAwareCache::setMainJsRuntime(_jsRuntime);

auto gpu = std::make_shared<GPU>(*_jsRuntime);
auto gpu = std::make_shared<GPU>(*_jsRuntime,
std::move(enableToggles),
std::move(disableToggles));
auto rnWebGPU =
std::make_shared<RNWebGPU>(gpu, _platformContext, _jsCallInvoker);
_gpu = gpu->get();
Expand Down
6 changes: 5 additions & 1 deletion packages/webgpu/cpp/rnwgpu/RNWebGPUManager.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <memory>
#include <string>
#include <vector>

#include "GPU.h"
#include "PlatformContext.h"
Expand All @@ -24,7 +26,9 @@ class RNWebGPUManager {
public:
RNWebGPUManager(jsi::Runtime *jsRuntime,
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker,
std::shared_ptr<PlatformContext> platformContext);
std::shared_ptr<PlatformContext> platformContext,
std::vector<std::string> enableToggles = {},
std::vector<std::string> disableToggles = {});
~RNWebGPUManager();

/**
Expand Down
49 changes: 45 additions & 4 deletions packages/webgpu/cpp/rnwgpu/api/GPU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,33 @@

namespace rnwgpu {

GPU::GPU(jsi::Runtime &runtime) : NativeObject(CLASS_NAME) {
GPU::GPU(jsi::Runtime &runtime,
std::vector<std::string> enableToggles,
std::vector<std::string> disableToggles)
: NativeObject(CLASS_NAME),
_enableToggles(std::move(enableToggles)),
_disableToggles(std::move(disableToggles)) {
static const auto kTimedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny;
wgpu::InstanceDescriptor instanceDesc{.requiredFeatureCount = 1,
.requiredFeatures = &kTimedWaitAny};

wgpu::InstanceLimits limits{.timedWaitAnyMaxCount = 64};
instanceDesc.requiredLimits = &limits;

// Build Dawn toggles descriptor and chain it if any toggles are specified
std::vector<const char *> enablePtrs, disablePtrs;
wgpu::DawnTogglesDescriptor togglesDesc;
if (!_enableToggles.empty() || !_disableToggles.empty()) {
for (const auto &s : _enableToggles) enablePtrs.push_back(s.c_str());
for (const auto &s : _disableToggles) disablePtrs.push_back(s.c_str());
togglesDesc.enabledToggles = enablePtrs.empty() ? nullptr : enablePtrs.data();
togglesDesc.enabledToggleCount = enablePtrs.size();
togglesDesc.disabledToggles = disablePtrs.empty() ? nullptr : disablePtrs.data();
togglesDesc.disabledToggleCount = disablePtrs.size();
togglesDesc.nextInChain = instanceDesc.nextInChain;
instanceDesc.nextInChain = &togglesDesc;
}

_instance = wgpu::CreateInstance(&instanceDesc);

auto dispatcher = std::make_shared<async::JSIMicrotaskDispatcher>(runtime);
Expand All @@ -39,11 +59,32 @@ async::AsyncTaskHandle GPU::requestAdapter(
constexpr auto kDefaultBackendType = wgpu::BackendType::Vulkan;
#endif
aOptions.backendType = kDefaultBackendType;

// Capture toggle strings by value so the lambda owns them
auto enableToggles = _enableToggles;
auto disableToggles = _disableToggles;

return _async->postTask(
[this, aOptions](const async::AsyncTaskHandle::ResolveFunction &resolve,
const async::AsyncTaskHandle::RejectFunction &reject) {
[this, aOptions, enableToggles = std::move(enableToggles),
disableToggles = std::move(disableToggles)](
const async::AsyncTaskHandle::ResolveFunction &resolve,
const async::AsyncTaskHandle::RejectFunction &reject) {
// Build Dawn toggles chain inside the task so pointers remain valid
std::vector<const char *> enablePtrs, disablePtrs;
wgpu::DawnTogglesDescriptor togglesDesc;
auto localOptions = aOptions;
if (!enableToggles.empty() || !disableToggles.empty()) {
for (const auto &s : enableToggles) enablePtrs.push_back(s.c_str());
for (const auto &s : disableToggles) disablePtrs.push_back(s.c_str());
togglesDesc.enabledToggles = enablePtrs.empty() ? nullptr : enablePtrs.data();
togglesDesc.enabledToggleCount = enablePtrs.size();
togglesDesc.disabledToggles = disablePtrs.empty() ? nullptr : disablePtrs.data();
togglesDesc.disabledToggleCount = disablePtrs.size();
togglesDesc.nextInChain = localOptions.nextInChain;
localOptions.nextInChain = &togglesDesc;
}
_instance.RequestAdapter(
&aOptions, wgpu::CallbackMode::AllowProcessEvents,
&localOptions, wgpu::CallbackMode::AllowProcessEvents,
[asyncRunner = _async, resolve,
reject](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter,
wgpu::StringView message) {
Expand Down
Loading