1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/dom/BindingDeclarations.h"
7 #include "mozilla/dom/WebGPUBinding.h"
13 #include "SupportedFeatures.h"
14 #include "SupportedLimits.h"
15 #include "ipc/WebGPUChild.h"
16 #include "mozilla/dom/Promise.h"
17 #include "mozilla/webgpu/ffi/wgpu.h"
19 namespace mozilla::webgpu
{
21 GPU_IMPL_CYCLE_COLLECTION(AdapterInfo
, mParent
)
22 GPU_IMPL_JS_WRAP(AdapterInfo
)
24 void AdapterInfo::GetWgpuName(nsString
& s
) const {
25 s
= mAboutSupportInfo
->name
;
28 uint32_t AdapterInfo::WgpuVendor() const { return mAboutSupportInfo
->vendor
; }
30 uint32_t AdapterInfo::WgpuDevice() const { return mAboutSupportInfo
->device
; }
32 void AdapterInfo::GetWgpuDeviceType(nsString
& s
) const {
33 switch (mAboutSupportInfo
->device_type
) {
34 case ffi::WGPUDeviceType_Cpu
:
35 s
.AssignLiteral("Cpu");
37 case ffi::WGPUDeviceType_DiscreteGpu
:
38 s
.AssignLiteral("DiscreteGpu");
40 case ffi::WGPUDeviceType_IntegratedGpu
:
41 s
.AssignLiteral("IntegratedGpu");
43 case ffi::WGPUDeviceType_VirtualGpu
:
44 s
.AssignLiteral("VirtualGpu");
46 case ffi::WGPUDeviceType_Other
:
47 s
.AssignLiteral("Other");
49 case ffi::WGPUDeviceType_Sentinel
:
52 MOZ_CRASH("Bad `ffi::WGPUDeviceType`");
55 void AdapterInfo::GetWgpuDriver(nsString
& s
) const {
56 s
= mAboutSupportInfo
->driver
;
59 void AdapterInfo::GetWgpuDriverInfo(nsString
& s
) const {
60 s
= mAboutSupportInfo
->driver_info
;
63 void AdapterInfo::GetWgpuBackend(nsString
& s
) const {
64 switch (mAboutSupportInfo
->backend
) {
65 case ffi::WGPUBackend_Empty
:
66 s
.AssignLiteral("Empty");
68 case ffi::WGPUBackend_Vulkan
:
69 s
.AssignLiteral("Vulkan");
71 case ffi::WGPUBackend_Metal
:
72 s
.AssignLiteral("Metal");
74 case ffi::WGPUBackend_Dx12
:
75 s
.AssignLiteral("Dx12");
77 case ffi::WGPUBackend_Gl
:
78 s
.AssignLiteral("Gl");
80 case ffi::WGPUBackend_BrowserWebGpu
: // This should never happen, because
81 // we _are_ the browser.
82 case ffi::WGPUBackend_Sentinel
:
85 MOZ_CRASH("Bad `ffi::WGPUBackend`");
90 GPU_IMPL_CYCLE_COLLECTION(Adapter
, mParent
, mBridge
, mFeatures
, mLimits
, mInfo
)
91 GPU_IMPL_JS_WRAP(Adapter
)
93 enum class FeatureImplementationStatusTag
{
98 struct FeatureImplementationStatus
{
99 FeatureImplementationStatusTag tag
=
100 FeatureImplementationStatusTag::NotImplemented
;
103 ffi::WGPUFeatures wgpuBit
;
106 const char* bugzillaUrlAscii
;
111 "https://bugzilla.mozilla.org/"
112 "enter_bug.cgi?product=Core&component=Graphics%3A+WebGPU"}};
114 static FeatureImplementationStatus
fromDomFeature(
115 const dom::GPUFeatureName aFeature
) {
116 auto implemented
= [](const ffi::WGPUFeatures aBit
) {
117 FeatureImplementationStatus feat
;
118 feat
.tag
= FeatureImplementationStatusTag::Implemented
;
119 feat
.value
.implemented
.wgpuBit
= aBit
;
122 auto unimplemented
= [](const char* aBugzillaUrl
) {
123 FeatureImplementationStatus feat
;
124 feat
.tag
= FeatureImplementationStatusTag::NotImplemented
;
125 feat
.value
.unimplemented
.bugzillaUrlAscii
= aBugzillaUrl
;
129 case dom::GPUFeatureName::Depth_clip_control
:
130 return implemented(WGPUFeatures_DEPTH_CLIP_CONTROL
);
132 case dom::GPUFeatureName::Depth32float_stencil8
:
133 return implemented(WGPUFeatures_DEPTH32FLOAT_STENCIL8
);
135 case dom::GPUFeatureName::Texture_compression_bc
:
136 return implemented(WGPUFeatures_TEXTURE_COMPRESSION_BC
);
138 case dom::GPUFeatureName::Texture_compression_etc2
:
139 return implemented(WGPUFeatures_TEXTURE_COMPRESSION_ETC2
);
141 case dom::GPUFeatureName::Texture_compression_astc
:
142 return implemented(WGPUFeatures_TEXTURE_COMPRESSION_ASTC
);
144 case dom::GPUFeatureName::Timestamp_query
:
145 return implemented(WGPUFeatures_TIMESTAMP_QUERY
);
147 case dom::GPUFeatureName::Indirect_first_instance
:
148 return implemented(WGPUFeatures_INDIRECT_FIRST_INSTANCE
);
150 case dom::GPUFeatureName::Shader_f16
:
151 // return implemented(WGPUFeatures_SHADER_F16);
152 return unimplemented(
153 "https://bugzilla.mozilla.org/show_bug.cgi?id=1891593");
155 case dom::GPUFeatureName::Rg11b10ufloat_renderable
:
156 return implemented(WGPUFeatures_RG11B10UFLOAT_RENDERABLE
);
158 case dom::GPUFeatureName::Bgra8unorm_storage
:
159 return implemented(WGPUFeatures_BGRA8UNORM_STORAGE
);
161 case dom::GPUFeatureName::Float32_filterable
:
162 return implemented(WGPUFeatures_FLOAT32_FILTERABLE
);
164 case dom::GPUFeatureName::Float32_blendable
:
165 return unimplemented(
166 "https://bugzilla.mozilla.org/show_bug.cgi?id=1931630");
168 case dom::GPUFeatureName::Clip_distances
:
169 return unimplemented(
170 "https://bugzilla.mozilla.org/show_bug.cgi?id=1931629");
172 case dom::GPUFeatureName::Dual_source_blending
:
173 // return implemented(WGPUFeatures_DUAL_SOURCE_BLENDING);
174 return unimplemented(
175 "https://bugzilla.mozilla.org/show_bug.cgi?id=1924328");
177 MOZ_CRASH("Bad GPUFeatureName.");
181 Adapter::Adapter(Instance
* const aParent
, WebGPUChild
* const aBridge
,
182 const std::shared_ptr
<ffi::WGPUAdapterInformation
>& aInfo
)
186 mFeatures(new SupportedFeatures(this)),
187 mLimits(new SupportedLimits(this, aInfo
->limits
)),
188 mInfo(new AdapterInfo(this, aInfo
)),
190 ErrorResult ignoredRv
; // It's onerous to plumb this in from outside in this
191 // case, and we don't really need to.
193 static const auto FEATURE_BY_BIT
= []() {
194 auto ret
= std::unordered_map
<ffi::WGPUFeatures
, dom::GPUFeatureName
>{};
196 for (const auto feature
:
197 dom::MakeWebIDLEnumeratedRange
<dom::GPUFeatureName
>()) {
198 const auto status
= FeatureImplementationStatus::fromDomFeature(feature
);
199 switch (status
.tag
) {
200 case FeatureImplementationStatusTag::Implemented
:
201 ret
[status
.value
.implemented
.wgpuBit
] = feature
;
203 case FeatureImplementationStatusTag::NotImplemented
:
211 auto remainingFeatureBits
= aInfo
->features
;
212 auto bitMask
= decltype(remainingFeatureBits
){0};
213 while (remainingFeatureBits
) {
219 const auto bit
= remainingFeatureBits
& bitMask
;
220 remainingFeatureBits
&= ~bitMask
; // Clear bit.
225 const auto featureForBit
= FEATURE_BY_BIT
.find(bit
);
226 if (featureForBit
!= FEATURE_BY_BIT
.end()) {
227 mFeatures
->Add(featureForBit
->second
, ignoredRv
);
231 // 1. WGPU claims to implement this, but we've explicitly marked this as
233 // 2. We don't recognize that bit, but maybe it's a wpgu-native-only
239 Adapter::~Adapter() { Cleanup(); }
241 void Adapter::Cleanup() {
242 if (mValid
&& mBridge
&& mBridge
->CanSend()) {
244 mBridge
->SendAdapterDrop(mId
);
248 const RefPtr
<SupportedFeatures
>& Adapter::Features() const { return mFeatures
; }
249 const RefPtr
<SupportedLimits
>& Adapter::Limits() const { return mLimits
; }
250 const RefPtr
<AdapterInfo
>& Adapter::Info() const { return mInfo
; }
252 bool Adapter::IsFallbackAdapter() const {
253 return mInfoInner
->device_type
== ffi::WGPUDeviceType::WGPUDeviceType_Cpu
;
256 bool Adapter::SupportExternalTextureInSwapChain() const {
257 return mInfoInner
->support_use_external_texture_in_swap_chain
;
260 static std::string_view
ToJsKey(const Limit limit
) {
262 case Limit::MaxTextureDimension1D
:
263 return "maxTextureDimension1D";
264 case Limit::MaxTextureDimension2D
:
265 return "maxTextureDimension2D";
266 case Limit::MaxTextureDimension3D
:
267 return "maxTextureDimension3D";
268 case Limit::MaxTextureArrayLayers
:
269 return "maxTextureArrayLayers";
270 case Limit::MaxBindGroups
:
271 return "maxBindGroups";
272 case Limit::MaxBindGroupsPlusVertexBuffers
:
273 return "maxBindGroupsPlusVertexBuffers";
274 case Limit::MaxBindingsPerBindGroup
:
275 return "maxBindingsPerBindGroup";
276 case Limit::MaxDynamicUniformBuffersPerPipelineLayout
:
277 return "maxDynamicUniformBuffersPerPipelineLayout";
278 case Limit::MaxDynamicStorageBuffersPerPipelineLayout
:
279 return "maxDynamicStorageBuffersPerPipelineLayout";
280 case Limit::MaxSampledTexturesPerShaderStage
:
281 return "maxSampledTexturesPerShaderStage";
282 case Limit::MaxSamplersPerShaderStage
:
283 return "maxSamplersPerShaderStage";
284 case Limit::MaxStorageBuffersPerShaderStage
:
285 return "maxStorageBuffersPerShaderStage";
286 case Limit::MaxStorageTexturesPerShaderStage
:
287 return "maxStorageTexturesPerShaderStage";
288 case Limit::MaxUniformBuffersPerShaderStage
:
289 return "maxUniformBuffersPerShaderStage";
290 case Limit::MaxUniformBufferBindingSize
:
291 return "maxUniformBufferBindingSize";
292 case Limit::MaxStorageBufferBindingSize
:
293 return "maxStorageBufferBindingSize";
294 case Limit::MinUniformBufferOffsetAlignment
:
295 return "minUniformBufferOffsetAlignment";
296 case Limit::MinStorageBufferOffsetAlignment
:
297 return "minStorageBufferOffsetAlignment";
298 case Limit::MaxVertexBuffers
:
299 return "maxVertexBuffers";
300 case Limit::MaxBufferSize
:
301 return "maxBufferSize";
302 case Limit::MaxVertexAttributes
:
303 return "maxVertexAttributes";
304 case Limit::MaxVertexBufferArrayStride
:
305 return "maxVertexBufferArrayStride";
306 case Limit::MaxInterStageShaderVariables
:
307 return "maxInterStageShaderVariables";
308 case Limit::MaxColorAttachments
:
309 return "maxColorAttachments";
310 case Limit::MaxColorAttachmentBytesPerSample
:
311 return "maxColorAttachmentBytesPerSample";
312 case Limit::MaxComputeWorkgroupStorageSize
:
313 return "maxComputeWorkgroupStorageSize";
314 case Limit::MaxComputeInvocationsPerWorkgroup
:
315 return "maxComputeInvocationsPerWorkgroup";
316 case Limit::MaxComputeWorkgroupSizeX
:
317 return "maxComputeWorkgroupSizeX";
318 case Limit::MaxComputeWorkgroupSizeY
:
319 return "maxComputeWorkgroupSizeY";
320 case Limit::MaxComputeWorkgroupSizeZ
:
321 return "maxComputeWorkgroupSizeZ";
322 case Limit::MaxComputeWorkgroupsPerDimension
:
323 return "maxComputeWorkgroupsPerDimension";
325 MOZ_CRASH("Bad Limit");
331 static auto ToACString(const nsAString
& s
) { return NS_ConvertUTF16toUTF8(s
); }
334 // Adapter::RequestDevice
336 already_AddRefed
<dom::Promise
> Adapter::RequestDevice(
337 const dom::GPUDeviceDescriptor
& aDesc
, ErrorResult
& aRv
) {
338 RefPtr
<dom::Promise
> promise
= dom::Promise::Create(GetParentObject(), aRv
);
339 if (NS_WARN_IF(aRv
.Failed())) {
343 ffi::WGPULimits deviceLimits
= *mLimits
->mFfi
;
344 for (const auto limit
: MakeInclusiveEnumeratedRange(Limit::_LAST
)) {
345 const auto defaultValue
= [&]() -> double {
348 case Limit::MaxTextureDimension1D
: return 8192;
349 case Limit::MaxTextureDimension2D
: return 8192;
350 case Limit::MaxTextureDimension3D
: return 2048;
351 case Limit::MaxTextureArrayLayers
: return 256;
352 case Limit::MaxBindGroups
: return 4;
353 case Limit::MaxBindGroupsPlusVertexBuffers
: return 24;
354 case Limit::MaxBindingsPerBindGroup
: return 1000;
355 case Limit::MaxDynamicUniformBuffersPerPipelineLayout
: return 8;
356 case Limit::MaxDynamicStorageBuffersPerPipelineLayout
: return 4;
357 case Limit::MaxSampledTexturesPerShaderStage
: return 16;
358 case Limit::MaxSamplersPerShaderStage
: return 16;
359 case Limit::MaxStorageBuffersPerShaderStage
: return 8;
360 case Limit::MaxStorageTexturesPerShaderStage
: return 4;
361 case Limit::MaxUniformBuffersPerShaderStage
: return 12;
362 case Limit::MaxUniformBufferBindingSize
: return 65536;
363 case Limit::MaxStorageBufferBindingSize
: return 134217728;
364 case Limit::MinUniformBufferOffsetAlignment
: return 256;
365 case Limit::MinStorageBufferOffsetAlignment
: return 256;
366 case Limit::MaxVertexBuffers
: return 8;
367 case Limit::MaxBufferSize
: return 268435456;
368 case Limit::MaxVertexAttributes
: return 16;
369 case Limit::MaxVertexBufferArrayStride
: return 2048;
370 case Limit::MaxInterStageShaderVariables
: return 16;
371 case Limit::MaxColorAttachments
: return 8;
372 case Limit::MaxColorAttachmentBytesPerSample
: return 32;
373 case Limit::MaxComputeWorkgroupStorageSize
: return 16384;
374 case Limit::MaxComputeInvocationsPerWorkgroup
: return 256;
375 case Limit::MaxComputeWorkgroupSizeX
: return 256;
376 case Limit::MaxComputeWorkgroupSizeY
: return 256;
377 case Limit::MaxComputeWorkgroupSizeZ
: return 64;
378 case Limit::MaxComputeWorkgroupsPerDimension
: return 65535;
381 MOZ_CRASH("Bad Limit");
383 SetLimit(&deviceLimits
, limit
, defaultValue
);
388 [&]() { // So that we can `return;` instead of `return promise.forget();`.
389 if (!mBridge
->CanSend()) {
390 promise
->MaybeRejectWithInvalidStateError(
391 "WebGPUChild cannot send, must recreate Adapter");
398 ffi::WGPUFeatures featureBits
= 0;
399 for (const auto requested
: aDesc
.mRequiredFeatures
) {
400 auto status
= FeatureImplementationStatus::fromDomFeature(requested
);
401 switch (status
.tag
) {
402 case FeatureImplementationStatusTag::Implemented
:
403 featureBits
|= status
.value
.implemented
.wgpuBit
;
405 case FeatureImplementationStatusTag::NotImplemented
: {
406 const auto featureStr
= dom::GetEnumString(requested
);
409 "`GPUAdapter.requestDevice`: '%s' was requested in "
410 "`requiredFeatures`, but it is not supported by Firefox."
411 "Follow <%s> for updates.",
412 featureStr
.get(), status
.value
.unimplemented
.bugzillaUrlAscii
);
413 promise
->MaybeRejectWithTypeError(msg
);
418 const bool supportedByAdapter
= mFeatures
->Features().count(requested
);
419 if (!supportedByAdapter
) {
420 const auto fstr
= dom::GetEnumString(requested
);
421 const auto astr
= this->LabelOrId();
423 "`GPUAdapter.requestDevice`: '%s' was requested in "
424 "`requiredFeatures`, but it is not supported by adapter %s.",
425 fstr
.get(), astr
.get());
426 promise
->MaybeRejectWithTypeError(msg
);
434 if (aDesc
.mRequiredLimits
.WasPassed()) {
435 static const auto LIMIT_BY_JS_KEY
= []() {
436 std::unordered_map
<std::string_view
, Limit
> ret
;
437 for (const auto limit
: MakeInclusiveEnumeratedRange(Limit::_LAST
)) {
438 const auto jsKeyU8
= ToJsKey(limit
);
439 ret
[jsKeyU8
] = limit
;
444 for (const auto& entry
: aDesc
.mRequiredLimits
.Value().Entries()) {
445 const auto& keyU16
= entry
.mKey
;
446 const nsCString keyU8
= ToACString(keyU16
);
447 const auto itr
= LIMIT_BY_JS_KEY
.find(keyU8
.get());
448 if (itr
== LIMIT_BY_JS_KEY
.end()) {
449 nsPrintfCString
msg("requestDevice: Limit '%s' not recognized.",
451 promise
->MaybeRejectWithOperationError(msg
);
455 const auto& limit
= itr
->second
;
456 uint64_t requestedValue
= entry
.mValue
;
457 const auto supportedValue
= GetLimit(*mLimits
->mFfi
, limit
);
458 if (StringBeginsWith(keyU8
, "max"_ns
)) {
459 if (requestedValue
> supportedValue
) {
461 "requestDevice: Request for limit '%s' must be <= supported "
463 keyU8
.get(), std::to_string(supportedValue
).c_str(),
464 std::to_string(requestedValue
).c_str());
465 promise
->MaybeRejectWithOperationError(msg
);
468 // Clamp to default if lower than default
470 std::max(requestedValue
, GetLimit(deviceLimits
, limit
));
472 MOZ_ASSERT(StringBeginsWith(keyU8
, "min"_ns
));
473 if (requestedValue
< supportedValue
) {
475 "requestDevice: Request for limit '%s' must be >= supported "
477 keyU8
.get(), std::to_string(supportedValue
).c_str(),
478 std::to_string(requestedValue
).c_str());
479 promise
->MaybeRejectWithOperationError(msg
);
482 if (StringEndsWith(keyU8
, "Alignment"_ns
)) {
483 if (!IsPowerOfTwo(requestedValue
)) {
485 "requestDevice: Request for limit '%s' must be a power of "
488 keyU8
.get(), std::to_string(requestedValue
).c_str());
489 promise
->MaybeRejectWithOperationError(msg
);
493 /// Clamp to default if higher than default
495 std::min(requestedValue
, GetLimit(deviceLimits
, limit
));
498 SetLimit(&deviceLimits
, limit
, requestedValue
);
504 ffi::WGPUFfiDeviceDescriptor ffiDesc
= {};
505 ffiDesc
.required_features
= featureBits
;
506 ffiDesc
.required_limits
= deviceLimits
;
507 auto request
= mBridge
->AdapterRequestDevice(mId
, ffiDesc
);
509 promise
->MaybeRejectWithNotSupportedError(
510 "Unable to instantiate a Device");
513 RefPtr
<Device
> device
= new Device(
514 this, request
->mDeviceId
, request
->mQueueId
, ffiDesc
.required_limits
);
515 for (const auto& feature
: aDesc
.mRequiredFeatures
) {
516 device
->mFeatures
->Add(feature
, aRv
);
519 request
->mPromise
->Then(
520 GetCurrentSerialEventTarget(), __func__
,
521 [promise
, device
](bool aSuccess
) {
523 promise
->MaybeResolve(device
);
525 device
->CleanupUnregisteredInParent();
526 promise
->MaybeRejectWithInvalidStateError(
527 "Unable to fulfill requested features and limits");
530 [promise
, device
](const ipc::ResponseRejectReason
& aReason
) {
531 // We can't be sure how far along the WebGPUParent got in handling
532 // our AdapterRequestDevice message, but we can't communicate with it,
533 // so clear up our client state for this Device without trying to
534 // communicate with the parent about it.
535 device
->CleanupUnregisteredInParent();
536 promise
->MaybeRejectWithNotSupportedError("IPC error");
540 return promise
.forget();
543 } // namespace mozilla::webgpu