Skip to content

Commit 33bab00

Browse files
feat(🗿): add createSecondaryDevice() to DawnContext (#3768)
Creates an independent Dawn device from the same adapter with its own command queue. Enables concurrent GPU workloads (e.g. ML inference via ONNX Runtime) without mutex contention from ImplicitDeviceSynchronization on the primary rendering device. Includes platform-specific features: - Apple: SharedTextureMemoryIOSurface, DawnMultiPlanarFormats - BufferMapExtendedUsages for staging buffer workflows Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(🗿): extract getMatchedAdapter() and requestDevice() into RNDawnUtils Move shared adapter discovery and device creation logic out of createDawnBackendContext into reusable helpers. createSecondaryDevice now delegates to DawnUtils::requestDevice() instead of duplicating the adapter enumeration, toggles, and device descriptor setup. --------- Co-authored-by: William Candillon <wcandillon@gmail.com>
1 parent 6492f65 commit 33bab00

File tree

2 files changed

+136
-113
lines changed

2 files changed

+136
-113
lines changed

‎packages/skia/cpp/rnskia/RNDawnContext.h‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,27 @@ class DawnContext {
188188
// Get the wgpu::Device for WebGPU bindings
189189
wgpu::Device getWGPUDevice() { return backendContext.fDevice; }
190190

191+
// Create a secondary Dawn device from the same adapter.
192+
// Has its own command queue and does NOT enable ImplicitDeviceSynchronization,
193+
// so it won't contend with the primary rendering device's mutex.
194+
// Safe for concurrent GPU work (e.g. ML inference) alongside Skia rendering.
195+
wgpu::Device createSecondaryDevice() {
196+
auto adapter = DawnUtils::getMatchedAdapter(instance.get());
197+
198+
std::vector<wgpu::FeatureName> features = {
199+
wgpu::FeatureName::BufferMapExtendedUsages,
200+
#ifdef __APPLE__
201+
wgpu::FeatureName::SharedTextureMemoryIOSurface,
202+
wgpu::FeatureName::DawnMultiPlanarFormats,
203+
// Note: SharedFenceMTLSharedEvent intentionally NOT enabled — it causes
204+
// EndAccess to encode fence signals that crash with "uncommitted encoder".
205+
// IOSurface data is already written by the camera before we read it.
206+
#endif
207+
};
208+
209+
return DawnUtils::requestDevice(adapter, features, false);
210+
}
211+
191212
// Create an SkImage from a WebGPU texture
192213
// The texture must have TextureBinding usage
193214
sk_sp<SkImage> MakeImageFromTexture(wgpu::Texture texture, int width,

‎packages/skia/cpp/rnskia/RNDawnUtils.h‎

Lines changed: 115 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -21,44 +21,28 @@ static const wgpu::TextureFormat PreferredTextureFormat =
2121
wgpu::TextureFormat::RGBA8Unorm;
2222
#endif
2323

24-
inline skgpu::graphite::DawnBackendContext
25-
createDawnBackendContext(dawn::native::Instance *instance) {
26-
27-
auto useTintIR = false;
28-
static constexpr const char *kToggles[] = {
29-
#if !defined(SK_DEBUG)
30-
"skip_validation",
31-
#endif
32-
"disable_lazy_clear_for_mapped_at_creation_buffer",
33-
"allow_unsafe_apis",
34-
"use_user_defined_labels_in_backend",
35-
"disable_robustness",
36-
"use_tint_ir",
37-
};
38-
wgpu::DawnTogglesDescriptor togglesDesc;
39-
togglesDesc.enabledToggleCount = std::size(kToggles) - (useTintIR ? 0 : 1);
40-
togglesDesc.enabledToggles = kToggles;
41-
42-
dawn::native::Adapter matchedAdaptor;
43-
44-
wgpu::RequestAdapterOptions options;
24+
// Find the best matching GPU adapter for the current platform.
25+
// Sorts by adapter type (DiscreteGPU > IntegratedGPU > CPU) and selects the
26+
// first adapter matching the platform backend (Metal on Apple, Vulkan on
27+
// Android).
28+
inline dawn::native::Adapter
29+
getMatchedAdapter(dawn::native::Instance *instance) {
4530
#ifdef __APPLE__
4631
constexpr auto kDefaultBackendType = wgpu::BackendType::Metal;
4732
#elif __ANDROID__
4833
constexpr auto kDefaultBackendType = wgpu::BackendType::Vulkan;
4934
#endif
35+
36+
wgpu::RequestAdapterOptions options;
5037
options.backendType = kDefaultBackendType;
5138
options.featureLevel = wgpu::FeatureLevel::Core;
52-
options.nextInChain = &togglesDesc;
5339

5440
std::vector<dawn::native::Adapter> adapters =
5541
instance->EnumerateAdapters(&options);
5642
if (adapters.empty()) {
5743
throw std::runtime_error("No matching adapter found");
5844
}
5945

60-
// Sort adapters by adapterType(DiscreteGPU, IntegratedGPU, CPU) and
61-
// backendType(Metal, Vulkan, OpenGL, OpenGLES, WebGPU).
6246
std::sort(adapters.begin(), adapters.end(),
6347
[](dawn::native::Adapter a, dawn::native::Adapter b) {
6448
wgpu::Adapter wgpuA = a.Get();
@@ -76,16 +60,93 @@ createDawnBackendContext(dawn::native::Instance *instance) {
7660
wgpu::AdapterInfo props;
7761
wgpuAdapter.GetInfo(&props);
7862
if (kDefaultBackendType == props.backendType) {
79-
matchedAdaptor = adapter;
80-
break;
63+
return adapter;
8164
}
8265
}
8366

84-
if (!matchedAdaptor) {
85-
throw std::runtime_error("No matching adapter found");
67+
throw std::runtime_error("No matching adapter found");
68+
}
69+
70+
// Create a Dawn device from the given adapter with the requested features.
71+
// Features not supported by the adapter are silently skipped.
72+
// Set fatalOnDeviceLost=true for primary rendering devices (SK_ABORT on loss),
73+
// false for secondary/compute devices (log only).
74+
inline wgpu::Device
75+
requestDevice(dawn::native::Adapter &nativeAdapter,
76+
const std::vector<wgpu::FeatureName> &requestedFeatures,
77+
bool fatalOnDeviceLost = true) {
78+
wgpu::Adapter adapter = nativeAdapter.Get();
79+
80+
// Filter to only features the adapter supports
81+
std::vector<wgpu::FeatureName> features;
82+
for (auto feature : requestedFeatures) {
83+
if (adapter.HasFeature(feature)) {
84+
features.push_back(feature);
85+
}
8686
}
8787

88-
wgpu::Adapter adapter = matchedAdaptor.Get();
88+
static constexpr const char *kToggles[] = {
89+
#if !defined(SK_DEBUG)
90+
"skip_validation",
91+
#endif
92+
"disable_lazy_clear_for_mapped_at_creation_buffer",
93+
"allow_unsafe_apis",
94+
"use_user_defined_labels_in_backend",
95+
"disable_robustness",
96+
};
97+
wgpu::DawnTogglesDescriptor togglesDesc;
98+
togglesDesc.enabledToggleCount = std::size(kToggles);
99+
togglesDesc.enabledToggles = kToggles;
100+
101+
wgpu::DeviceDescriptor desc;
102+
desc.requiredFeatureCount = features.size();
103+
desc.requiredFeatures = features.data();
104+
desc.nextInChain = &togglesDesc;
105+
106+
if (fatalOnDeviceLost) {
107+
desc.SetDeviceLostCallback(
108+
wgpu::CallbackMode::AllowSpontaneous,
109+
[](const wgpu::Device &, wgpu::DeviceLostReason reason,
110+
wgpu::StringView message) {
111+
if (reason != wgpu::DeviceLostReason::Destroyed) {
112+
SK_ABORT("Device lost: %.*s\n",
113+
static_cast<int>(message.length), message.data);
114+
}
115+
});
116+
desc.SetUncapturedErrorCallback(
117+
[](const wgpu::Device &, wgpu::ErrorType,
118+
wgpu::StringView message) {
119+
SkDebugf("Device error: %.*s\n",
120+
static_cast<int>(message.length), message.data);
121+
});
122+
} else {
123+
desc.SetDeviceLostCallback(
124+
wgpu::CallbackMode::AllowSpontaneous,
125+
[](const wgpu::Device &, wgpu::DeviceLostReason reason,
126+
wgpu::StringView message) {
127+
if (reason != wgpu::DeviceLostReason::Destroyed) {
128+
RNSkia::RNSkLogger::logToConsole(
129+
"Device lost: %.*s",
130+
static_cast<int>(message.length), message.data);
131+
}
132+
});
133+
desc.SetUncapturedErrorCallback(
134+
[](const wgpu::Device &, wgpu::ErrorType,
135+
wgpu::StringView message) {
136+
RNSkia::RNSkLogger::logToConsole(
137+
"Device error: %.*s",
138+
static_cast<int>(message.length), message.data);
139+
});
140+
}
141+
142+
return wgpu::Device::Acquire(nativeAdapter.CreateDevice(&desc));
143+
}
144+
145+
inline skgpu::graphite::DawnBackendContext
146+
createDawnBackendContext(dawn::native::Instance *instance) {
147+
148+
auto matchedAdapter = getMatchedAdapter(instance);
149+
wgpu::Adapter adapter = matchedAdapter.Get();
89150

90151
// Log selected adapter info
91152
wgpu::AdapterInfo adapterInfo;
@@ -130,93 +191,34 @@ createDawnBackendContext(dawn::native::Instance *instance) {
130191
"Selected Dawn adapter - Backend: %s, Device: %s, Description: %s",
131192
backendName.c_str(), deviceName.c_str(), description.c_str());
132193

133-
std::vector<wgpu::FeatureName> features;
134-
if (adapter.HasFeature(wgpu::FeatureName::MSAARenderToSingleSampled)) {
135-
features.push_back(wgpu::FeatureName::MSAARenderToSingleSampled);
136-
}
137-
if (adapter.HasFeature(wgpu::FeatureName::TransientAttachments)) {
138-
features.push_back(wgpu::FeatureName::TransientAttachments);
139-
}
140-
if (adapter.HasFeature(wgpu::FeatureName::Unorm16TextureFormats)) {
141-
features.push_back(wgpu::FeatureName::Unorm16TextureFormats);
142-
}
143-
if (adapter.HasFeature(wgpu::FeatureName::DualSourceBlending)) {
144-
features.push_back(wgpu::FeatureName::DualSourceBlending);
145-
}
146-
if (adapter.HasFeature(wgpu::FeatureName::FramebufferFetch)) {
147-
features.push_back(wgpu::FeatureName::FramebufferFetch);
148-
}
149-
if (adapter.HasFeature(wgpu::FeatureName::BufferMapExtendedUsages)) {
150-
features.push_back(wgpu::FeatureName::BufferMapExtendedUsages);
151-
}
152-
if (adapter.HasFeature(wgpu::FeatureName::TextureCompressionETC2)) {
153-
features.push_back(wgpu::FeatureName::TextureCompressionETC2);
154-
}
155-
if (adapter.HasFeature(wgpu::FeatureName::TextureCompressionBC)) {
156-
features.push_back(wgpu::FeatureName::TextureCompressionBC);
157-
}
158-
if (adapter.HasFeature(wgpu::FeatureName::R8UnormStorage)) {
159-
features.push_back(wgpu::FeatureName::R8UnormStorage);
160-
}
161-
if (adapter.HasFeature(wgpu::FeatureName::DawnLoadResolveTexture)) {
162-
features.push_back(wgpu::FeatureName::DawnLoadResolveTexture);
163-
}
164-
if (adapter.HasFeature(wgpu::FeatureName::DawnPartialLoadResolveTexture)) {
165-
features.push_back(wgpu::FeatureName::DawnPartialLoadResolveTexture);
166-
}
167-
if (adapter.HasFeature(wgpu::FeatureName::TimestampQuery)) {
168-
features.push_back(wgpu::FeatureName::TimestampQuery);
169-
}
170-
if (adapter.HasFeature(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment)) {
171-
features.push_back(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment);
172-
}
173-
if (adapter.HasFeature(wgpu::FeatureName::ImplicitDeviceSynchronization)) {
174-
features.push_back(wgpu::FeatureName::ImplicitDeviceSynchronization);
175-
}
194+
// Primary device features — request everything Skia/Graphite can use
195+
std::vector<wgpu::FeatureName> features = {
196+
wgpu::FeatureName::MSAARenderToSingleSampled,
197+
wgpu::FeatureName::TransientAttachments,
198+
wgpu::FeatureName::Unorm16TextureFormats,
199+
wgpu::FeatureName::DualSourceBlending,
200+
wgpu::FeatureName::FramebufferFetch,
201+
wgpu::FeatureName::BufferMapExtendedUsages,
202+
wgpu::FeatureName::TextureCompressionETC2,
203+
wgpu::FeatureName::TextureCompressionBC,
204+
wgpu::FeatureName::R8UnormStorage,
205+
wgpu::FeatureName::DawnLoadResolveTexture,
206+
wgpu::FeatureName::DawnPartialLoadResolveTexture,
207+
wgpu::FeatureName::TimestampQuery,
208+
wgpu::FeatureName::DawnTexelCopyBufferRowAlignment,
209+
wgpu::FeatureName::ImplicitDeviceSynchronization,
176210
#ifdef __APPLE__
177-
if (adapter.HasFeature(wgpu::FeatureName::SharedTextureMemoryIOSurface)) {
178-
features.push_back(wgpu::FeatureName::SharedTextureMemoryIOSurface);
179-
}
180-
if (adapter.HasFeature(wgpu::FeatureName::DawnMultiPlanarFormats)) {
181-
features.push_back(wgpu::FeatureName::DawnMultiPlanarFormats);
182-
}
183-
if (adapter.HasFeature(wgpu::FeatureName::MultiPlanarFormatP010)) {
184-
features.push_back(wgpu::FeatureName::MultiPlanarFormatP010);
185-
}
186-
if (adapter.HasFeature(wgpu::FeatureName::MultiPlanarFormatP210)) {
187-
features.push_back(wgpu::FeatureName::MultiPlanarFormatP210);
188-
}
189-
if (adapter.HasFeature(wgpu::FeatureName::MultiPlanarFormatExtendedUsages)) {
190-
features.push_back(wgpu::FeatureName::MultiPlanarFormatExtendedUsages);
191-
}
211+
wgpu::FeatureName::SharedTextureMemoryIOSurface,
212+
wgpu::FeatureName::DawnMultiPlanarFormats,
213+
wgpu::FeatureName::MultiPlanarFormatP010,
214+
wgpu::FeatureName::MultiPlanarFormatP210,
215+
wgpu::FeatureName::MultiPlanarFormatExtendedUsages,
192216
#else
193-
if (adapter.HasFeature(
194-
wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer)) {
195-
features.push_back(wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer);
196-
}
217+
wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer,
197218
#endif
219+
};
198220

199-
wgpu::DeviceDescriptor desc;
200-
desc.requiredFeatureCount = features.size();
201-
desc.requiredFeatures = features.data();
202-
desc.nextInChain = &togglesDesc;
203-
desc.SetDeviceLostCallback(
204-
wgpu::CallbackMode::AllowSpontaneous,
205-
[](const wgpu::Device &, wgpu::DeviceLostReason reason,
206-
wgpu::StringView message) {
207-
if (reason != wgpu::DeviceLostReason::Destroyed) {
208-
SK_ABORT("Device lost: %.*s\n", static_cast<int>(message.length),
209-
message.data);
210-
}
211-
});
212-
desc.SetUncapturedErrorCallback(
213-
[](const wgpu::Device &, wgpu::ErrorType, wgpu::StringView message) {
214-
SkDebugf("Device error: %.*s\n", static_cast<int>(message.length),
215-
message.data);
216-
});
217-
218-
wgpu::Device device =
219-
wgpu::Device::Acquire(matchedAdaptor.CreateDevice(&desc));
221+
wgpu::Device device = requestDevice(matchedAdapter, features, true);
220222
SkASSERT(device);
221223

222224
skgpu::graphite::DawnBackendContext backendContext;
@@ -227,4 +229,4 @@ createDawnBackendContext(dawn::native::Instance *instance) {
227229
return backendContext;
228230
}
229231

230-
} // namespace DawnUtils
232+
} // namespace DawnUtils

0 commit comments

Comments
 (0)