| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/webgpu/gpu_adapter.h" |
| |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_device_descriptor.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_request_adapter_options.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu_adapter_info.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu_device.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu_device_lost_info.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu_supported_features.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu_supported_limits.h" |
| #include "third_party/blink/renderer/modules/webgpu/string_utils.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| absl::optional<V8GPUFeatureName::Enum> ToV8FeatureNameEnum(WGPUFeatureName f) { |
| switch (f) { |
| case WGPUFeatureName_Depth32FloatStencil8: |
| return V8GPUFeatureName::Enum::kDepth32FloatStencil8; |
| case WGPUFeatureName_TimestampQuery: |
| return V8GPUFeatureName::Enum::kTimestampQuery; |
| case WGPUFeatureName_TimestampQueryInsidePasses: |
| return V8GPUFeatureName::Enum::kTimestampQueryInsidePasses; |
| case WGPUFeatureName_PipelineStatisticsQuery: |
| return V8GPUFeatureName::Enum::kPipelineStatisticsQuery; |
| case WGPUFeatureName_TextureCompressionBC: |
| return V8GPUFeatureName::Enum::kTextureCompressionBc; |
| case WGPUFeatureName_TextureCompressionETC2: |
| return V8GPUFeatureName::Enum::kTextureCompressionEtc2; |
| case WGPUFeatureName_TextureCompressionASTC: |
| return V8GPUFeatureName::Enum::kTextureCompressionAstc; |
| case WGPUFeatureName_IndirectFirstInstance: |
| return V8GPUFeatureName::Enum::kIndirectFirstInstance; |
| case WGPUFeatureName_DepthClipControl: |
| return V8GPUFeatureName::Enum::kDepthClipControl; |
| case WGPUFeatureName_RG11B10UfloatRenderable: |
| return V8GPUFeatureName::Enum::kRg11B10UfloatRenderable; |
| case WGPUFeatureName_BGRA8UnormStorage: |
| return V8GPUFeatureName::Enum::kBgra8UnormStorage; |
| case WGPUFeatureName_ChromiumExperimentalDp4a: |
| return V8GPUFeatureName::Enum::kChromiumExperimentalDp4A; |
| case WGPUFeatureName_ShaderF16: |
| return V8GPUFeatureName::Enum::kShaderF16; |
| case WGPUFeatureName_Float32Filterable: |
| return V8GPUFeatureName::Enum::kFloat32Filterable; |
| default: |
| return absl::nullopt; |
| } |
| } |
| |
| } // anonymous namespace |
| |
| namespace { |
| |
| GPUSupportedFeatures* MakeFeatureNameSet(const DawnProcTable& procs, |
| WGPUAdapter adapter) { |
| GPUSupportedFeatures* features = MakeGarbageCollected<GPUSupportedFeatures>(); |
| DCHECK(features->FeatureNameSet().empty()); |
| |
| size_t feature_count = procs.adapterEnumerateFeatures(adapter, nullptr); |
| DCHECK(feature_count <= std::numeric_limits<wtf_size_t>::max()); |
| |
| Vector<WGPUFeatureName> feature_names(static_cast<wtf_size_t>(feature_count)); |
| procs.adapterEnumerateFeatures(adapter, feature_names.data()); |
| |
| for (WGPUFeatureName f : feature_names) { |
| auto feature_name_enum_optional = ToV8FeatureNameEnum(f); |
| if (feature_name_enum_optional) { |
| features->AddFeatureName( |
| V8GPUFeatureName(feature_name_enum_optional.value())); |
| } |
| } |
| return features; |
| } |
| |
| } // anonymous namespace |
| |
| GPUAdapter::GPUAdapter( |
| GPU* gpu, |
| WGPUAdapter handle, |
| scoped_refptr<DawnControlClientHolder> dawn_control_client) |
| : DawnObjectBase(dawn_control_client), handle_(handle), gpu_(gpu) { |
| WGPUAdapterProperties properties = {}; |
| GetProcs().adapterGetProperties(handle_, &properties); |
| is_fallback_adapter_ = properties.adapterType == WGPUAdapterType_CPU; |
| backend_type_ = properties.backendType; |
| is_compatibility_mode_ = properties.compatibilityMode; |
| |
| vendor_ = properties.vendorName; |
| architecture_ = properties.architecture; |
| if (properties.deviceID <= 0xffff) { |
| device_ = String::Format("0x%04x", properties.deviceID); |
| } else { |
| device_ = String::Format("0x%08x", properties.deviceID); |
| } |
| description_ = properties.name; |
| driver_ = properties.driverDescription; |
| |
| WGPUSupportedLimits limits = {}; |
| GetProcs().adapterGetLimits(handle_, &limits); |
| limits_ = MakeGarbageCollected<GPUSupportedLimits>(limits); |
| |
| features_ = MakeFeatureNameSet(GetProcs(), handle_); |
| } |
| |
| void GPUAdapter::AddConsoleWarning(ExecutionContext* execution_context, |
| const char* message) { |
| if (execution_context && allowed_console_warnings_remaining_ > 0) { |
| auto* console_message = MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kRendering, |
| mojom::blink::ConsoleMessageLevel::kWarning, |
| StringFromASCIIAndUTF8(message)); |
| execution_context->AddConsoleMessage(console_message); |
| |
| allowed_console_warnings_remaining_--; |
| if (allowed_console_warnings_remaining_ == 0) { |
| auto* final_message = MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kRendering, |
| mojom::blink::ConsoleMessageLevel::kWarning, |
| "WebGPU: too many warnings, no more warnings will be reported to the " |
| "console for this GPUAdapter."); |
| execution_context->AddConsoleMessage(final_message); |
| } |
| } |
| } |
| |
| GPUSupportedFeatures* GPUAdapter::features() const { |
| return features_; |
| } |
| |
| bool GPUAdapter::isFallbackAdapter() const { |
| return is_fallback_adapter_; |
| } |
| |
| WGPUBackendType GPUAdapter::backendType() const { |
| return backend_type_; |
| } |
| |
| bool GPUAdapter::SupportsMultiPlanarFormats() const { |
| return GetProcs().adapterHasFeature(handle_, |
| WGPUFeatureName_DawnMultiPlanarFormats); |
| } |
| |
| bool GPUAdapter::isCompatibilityMode() const { |
| return is_compatibility_mode_; |
| } |
| |
| void GPUAdapter::OnRequestDeviceCallback(ScriptState* script_state, |
| ScriptPromiseResolver* resolver, |
| const GPUDeviceDescriptor* descriptor, |
| WGPURequestDeviceStatus status, |
| WGPUDevice dawn_device, |
| const char* error_message) { |
| switch (status) { |
| case WGPURequestDeviceStatus_Success: { |
| DCHECK(dawn_device); |
| |
| GPUDeviceLostInfo* device_lost_info = nullptr; |
| if (is_consumed_) { |
| // Immediately force the device to be lost. |
| // TODO: Ideally this should be handled in Dawn, which can return an |
| // error device. |
| device_lost_info = MakeGarbageCollected<GPUDeviceLostInfo>( |
| WGPUDeviceLostReason_Undefined, |
| StringFromASCIIAndUTF8( |
| "The adapter is invalid because it has already been used to " |
| "create a device. A lost device has been returned.")); |
| } |
| is_consumed_ = true; |
| |
| ExecutionContext* execution_context = |
| ExecutionContext::From(script_state); |
| auto* device = MakeGarbageCollected<GPUDevice>( |
| execution_context, GetDawnControlClient(), this, dawn_device, |
| descriptor, device_lost_info); |
| |
| if (device_lost_info) { |
| // Ensure the Dawn device is marked as lost as well. |
| device->InjectError( |
| WGPUErrorType_DeviceLost, |
| "Device was marked as lost due to a stale adapter."); |
| } |
| |
| resolver->Resolve(device); |
| |
| ukm::builders::ClientRenderingAPI(execution_context->UkmSourceID()) |
| .SetGPUDevice(static_cast<int>(true)) |
| .Record(execution_context->UkmRecorder()); |
| break; |
| } |
| |
| case WGPURequestDeviceStatus_Error: |
| case WGPURequestDeviceStatus_Unknown: |
| if (dawn_device) { |
| // Immediately force the device to be lost. |
| auto* device_lost_info = MakeGarbageCollected<GPUDeviceLostInfo>( |
| WGPUDeviceLostReason_Undefined, |
| StringFromASCIIAndUTF8(error_message)); |
| ExecutionContext* execution_context = |
| ExecutionContext::From(script_state); |
| auto* device = MakeGarbageCollected<GPUDevice>( |
| execution_context, GetDawnControlClient(), this, dawn_device, |
| descriptor, device_lost_info); |
| // Resolve with the lost device. |
| resolver->Resolve(device); |
| } else { |
| // If a device is not returned, that means that an error occurred while |
| // validating features or limits, and as a result the promise should be |
| // rejected with an OperationError. |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kOperationError, |
| StringFromASCIIAndUTF8(error_message))); |
| } |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| ScriptPromise GPUAdapter::requestDevice(ScriptState* script_state, |
| GPUDeviceDescriptor* descriptor) { |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>( |
| script_state, |
| ExceptionContext(ExceptionContext::Context::kOperationInvoke, |
| "GPUAdapter", "requestDevice")); |
| ScriptPromise promise = resolver->Promise(); |
| |
| WGPUDeviceDescriptor dawn_desc = {}; |
| |
| WGPURequiredLimits required_limits = {}; |
| if (descriptor->hasRequiredLimits()) { |
| dawn_desc.requiredLimits = &required_limits; |
| GPUSupportedLimits::MakeUndefined(&required_limits); |
| DOMException* exception = GPUSupportedLimits::Populate( |
| &required_limits, descriptor->requiredLimits()); |
| if (exception) { |
| resolver->Reject(exception); |
| return promise; |
| } |
| } |
| |
| Vector<WGPUFeatureName> required_features; |
| if (descriptor->hasRequiredFeatures()) { |
| // Insert features into a set to dedup them. |
| HashSet<WGPUFeatureName> required_features_set; |
| for (const V8GPUFeatureName& f : descriptor->requiredFeatures()) { |
| // If the feature is not a valid feature reject with a type error. |
| if (!features_->has(f.AsEnum())) { |
| resolver->RejectWithTypeError( |
| String::Format("Unsupported feature: %s", f.AsCStr())); |
| return promise; |
| } |
| required_features_set.insert(AsDawnEnum(f)); |
| } |
| |
| // Then, push the deduped features into a vector. |
| required_features.AppendRange(required_features_set.begin(), |
| required_features_set.end()); |
| dawn_desc.requiredFeatures = required_features.data(); |
| dawn_desc.requiredFeaturesCount = required_features.size(); |
| } |
| |
| auto* callback = BindWGPUOnceCallback( |
| &GPUAdapter::OnRequestDeviceCallback, WrapPersistent(this), |
| WrapPersistent(script_state), WrapPersistent(resolver), |
| WrapPersistent(descriptor)); |
| |
| GetProcs().adapterRequestDevice( |
| handle_, &dawn_desc, callback->UnboundCallback(), callback->AsUserdata()); |
| EnsureFlush(ToEventLoop(script_state)); |
| |
| return promise; |
| } |
| |
| ScriptPromise GPUAdapter::requestAdapterInfo( |
| ScriptState* script_state, |
| const Vector<String>& unmask_hints) { |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); |
| ScriptPromise promise = resolver->Promise(); |
| |
| // If any unmask hints have been given, the method must also have been called |
| // during user activation. If not, reject the promise. |
| if (unmask_hints.size()) { |
| LocalDOMWindow* domWindow = gpu_->DomWindow(); |
| if (!domWindow || |
| !LocalFrame::HasTransientUserActivation(domWindow->GetFrame())) { |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotAllowedError, |
| "requestAdapterInfo requires user activation if any unmaskHints are " |
| "given.")); |
| return promise; |
| } |
| |
| // TODO(crbug.com/1405528): Handling unmask hints is not yet supported. |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotSupportedError, |
| "Passing unmaskHints to requestAdapterInfo is not yet implemented. In " |
| "the future, doing so may trigger a permissions prompt.")); |
| |
| return promise; |
| } |
| |
| GPUAdapterInfo* adapter_info; |
| if (RuntimeEnabledFeatures::WebGPUDeveloperFeaturesEnabled()) { |
| // If WebGPU developer features have been enabled then provide unmasked |
| // versions of all available adapter info values, including some that are |
| // only available when the flag is enabled. |
| adapter_info = MakeGarbageCollected<GPUAdapterInfo>( |
| vendor_, architecture_, device_, description_, driver_); |
| } else { |
| // TODO(dawn:1427): If unmask_hints are given ask the user for consent to |
| // expose more information and, if given, include device_ and description_ |
| // in the returned GPUAdapterInfo. |
| adapter_info = MakeGarbageCollected<GPUAdapterInfo>(vendor_, architecture_); |
| } |
| |
| resolver->Resolve(adapter_info); |
| |
| return promise; |
| } |
| |
| void GPUAdapter::Trace(Visitor* visitor) const { |
| visitor->Trace(gpu_); |
| visitor->Trace(features_); |
| visitor->Trace(limits_); |
| ScriptWrappable::Trace(visitor); |
| } |
| |
| } // namespace blink |