blob: 148e884ad7b5acfe95fc439962920a1841d6e995 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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_device.h"
#include "gpu/command_buffer/client/webgpu_interface.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_compute_pipeline_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_device_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_feature_name.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_uncaptured_error_event_init.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/modules/event_target_modules.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_adapter.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_bind_group.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_buffer.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_command_encoder.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_compute_pipeline.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_device_lost_info.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_out_of_memory_error.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_pipeline_layout.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_query_set.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_queue.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_sampler.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_shader_module.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_texture.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_uncaptured_error_event.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_validation_error.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
namespace blink {
namespace {
#ifdef USE_BLINK_V8_BINDING_NEW_IDL_DICTIONARY
Vector<String> ToStringVector(
const Vector<V8GPUExtensionName>& gpu_extension_names) {
Vector<String> result;
for (auto& name : gpu_extension_names)
result.push_back(IDLEnumAsString(name));
return result;
}
#endif
} // anonymous namespace
// TODO(enga): Handle adapter options and device descriptor
GPUDevice::GPUDevice(ExecutionContext* execution_context,
scoped_refptr<DawnControlClientHolder> dawn_control_client,
GPUAdapter* adapter,
WGPUDevice dawn_device,
const GPUDeviceDescriptor* descriptor)
: ExecutionContextClient(execution_context),
DawnObject(dawn_control_client, dawn_device),
adapter_(adapter),
#ifdef USE_BLINK_V8_BINDING_NEW_IDL_DICTIONARY
feature_name_list_(ToStringVector(descriptor->nonGuaranteedFeatures())),
#else
feature_name_list_(descriptor->nonGuaranteedFeatures()),
#endif
queue_(MakeGarbageCollected<GPUQueue>(
this,
GetProcs().deviceGetDefaultQueue(GetHandle()))),
lost_property_(MakeGarbageCollected<LostProperty>(execution_context)),
error_callback_(BindDawnRepeatingCallback(&GPUDevice::OnUncapturedError,
WrapWeakPersistent(this))),
// Note: This is a *repeating* callback even though we expect it to only
// be called once. This is because it may be called *zero* times.
// Because it might never be called, the GPUDevice needs to own the
// allocation so it can be appropriately freed on destruction. Thus, the
// callback should not be a OnceCallback which self-deletes after it is
// called.
lost_callback_(BindDawnRepeatingCallback(&GPUDevice::OnDeviceLostError,
WrapWeakPersistent(this))) {
DCHECK(dawn_device);
GetProcs().deviceSetUncapturedErrorCallback(
GetHandle(), error_callback_->UnboundCallback(),
error_callback_->AsUserdata());
GetProcs().deviceSetDeviceLostCallback(GetHandle(),
lost_callback_->UnboundCallback(),
lost_callback_->AsUserdata());
setLabel(descriptor->label());
}
GPUDevice::~GPUDevice() {
// Clear the callbacks since we can't handle callbacks after finalization.
// error_callback_, logging_callback_, and lost_callback_ will be deleted.
GetProcs().deviceSetUncapturedErrorCallback(GetHandle(), nullptr, nullptr);
GetProcs().deviceSetDeviceLostCallback(GetHandle(), nullptr, nullptr);
}
void GPUDevice::InjectError(WGPUErrorType type, const char* message) {
GetProcs().deviceInjectError(GetHandle(), type, message);
}
void GPUDevice::AddConsoleWarning(const char* message) {
ExecutionContext* execution_context = GetExecutionContext();
if (execution_context && allowed_console_warnings_remaining_ > 0) {
auto* console_message = MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kRendering,
mojom::blink::ConsoleMessageLevel::kWarning, 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 GPUDevice.");
execution_context->AddConsoleMessage(final_message);
}
}
}
void GPUDevice::OnUncapturedError(WGPUErrorType errorType,
const char* message) {
DCHECK_NE(errorType, WGPUErrorType_NoError);
DCHECK_NE(errorType, WGPUErrorType_DeviceLost);
LOG(ERROR) << "GPUDevice: " << message;
AddConsoleWarning(message);
GPUUncapturedErrorEventInit* init = GPUUncapturedErrorEventInit::Create();
if (errorType == WGPUErrorType_Validation) {
auto* error = MakeGarbageCollected<GPUValidationError>(message);
init->setError(
GPUOutOfMemoryErrorOrGPUValidationError::FromGPUValidationError(error));
} else if (errorType == WGPUErrorType_OutOfMemory) {
GPUOutOfMemoryError* error = GPUOutOfMemoryError::Create();
init->setError(
GPUOutOfMemoryErrorOrGPUValidationError::FromGPUOutOfMemoryError(
error));
} else {
return;
}
this->DispatchEvent(*GPUUncapturedErrorEvent::Create(
event_type_names::kUncapturederror, init));
}
void GPUDevice::OnDeviceLostError(const char* message) {
AddConsoleWarning(message);
if (lost_property_->GetState() == LostProperty::kPending) {
auto* device_lost_info = MakeGarbageCollected<GPUDeviceLostInfo>(message);
lost_property_->Resolve(device_lost_info);
}
}
void GPUDevice::OnCreateRenderPipelineAsyncCallback(
ScriptPromiseResolver* resolver,
WGPUCreatePipelineAsyncStatus status,
WGPURenderPipeline render_pipeline,
const char* message) {
switch (status) {
case WGPUCreatePipelineAsyncStatus_Success: {
resolver->Resolve(
MakeGarbageCollected<GPURenderPipeline>(this, render_pipeline));
break;
}
case WGPUCreatePipelineAsyncStatus_Error:
case WGPUCreatePipelineAsyncStatus_DeviceLost:
case WGPUCreatePipelineAsyncStatus_DeviceDestroyed:
case WGPUCreatePipelineAsyncStatus_Unknown: {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kOperationError, message));
break;
}
default: {
NOTREACHED();
}
}
}
void GPUDevice::OnCreateComputePipelineAsyncCallback(
ScriptPromiseResolver* resolver,
WGPUCreatePipelineAsyncStatus status,
WGPUComputePipeline compute_pipeline,
const char* message) {
switch (status) {
case WGPUCreatePipelineAsyncStatus_Success: {
resolver->Resolve(
MakeGarbageCollected<GPUComputePipeline>(this, compute_pipeline));
break;
}
case WGPUCreatePipelineAsyncStatus_Error:
case WGPUCreatePipelineAsyncStatus_DeviceLost:
case WGPUCreatePipelineAsyncStatus_DeviceDestroyed:
case WGPUCreatePipelineAsyncStatus_Unknown: {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kOperationError, message));
break;
}
default: {
NOTREACHED();
}
}
}
GPUAdapter* GPUDevice::adapter() const {
return adapter_;
}
Vector<String> GPUDevice::features() const {
return feature_name_list_;
}
Vector<String> GPUDevice::extensions() {
AddConsoleWarning(
"The extensions attribute has been deprecated in favor of the features "
"attribute, and will soon be removed.");
return feature_name_list_;
}
ScriptPromise GPUDevice::lost(ScriptState* script_state) {
return lost_property_->Promise(script_state->World());
}
GPUQueue* GPUDevice::queue() {
return queue_;
}
GPUQueue* GPUDevice::defaultQueue() {
AddConsoleWarning(
"The defaultQueue attribute has been deprecated in favor of the queue "
"attribute, and will soon be removed.");
return queue_;
}
GPUBuffer* GPUDevice::createBuffer(const GPUBufferDescriptor* descriptor) {
return GPUBuffer::Create(this, descriptor);
}
GPUTexture* GPUDevice::createTexture(const GPUTextureDescriptor* descriptor,
ExceptionState& exception_state) {
return GPUTexture::Create(this, descriptor, exception_state);
}
GPUTexture* GPUDevice::experimentalImportTexture(
HTMLVideoElement* video,
unsigned int usage_flags,
ExceptionState& exception_state) {
return GPUTexture::FromVideo(
this, video, static_cast<WGPUTextureUsage>(usage_flags), exception_state);
}
GPUSampler* GPUDevice::createSampler(const GPUSamplerDescriptor* descriptor) {
return GPUSampler::Create(this, descriptor);
}
GPUBindGroup* GPUDevice::createBindGroup(
const GPUBindGroupDescriptor* descriptor,
ExceptionState& exception_state) {
return GPUBindGroup::Create(this, descriptor, exception_state);
}
GPUBindGroupLayout* GPUDevice::createBindGroupLayout(
const GPUBindGroupLayoutDescriptor* descriptor,
ExceptionState& exception_state) {
return GPUBindGroupLayout::Create(this, descriptor, exception_state);
}
GPUPipelineLayout* GPUDevice::createPipelineLayout(
const GPUPipelineLayoutDescriptor* descriptor) {
return GPUPipelineLayout::Create(this, descriptor);
}
GPUShaderModule* GPUDevice::createShaderModule(
const GPUShaderModuleDescriptor* descriptor,
ExceptionState& exception_state) {
return GPUShaderModule::Create(this, descriptor, exception_state);
}
GPURenderPipeline* GPUDevice::createRenderPipeline(
ScriptState* script_state,
const GPURenderPipelineDescriptor* descriptor) {
return GPURenderPipeline::Create(script_state, this, descriptor);
}
GPUComputePipeline* GPUDevice::createComputePipeline(
const GPUComputePipelineDescriptor* descriptor) {
return GPUComputePipeline::Create(this, descriptor);
}
ScriptPromise GPUDevice::createRenderPipelineAsync(
ScriptState* script_state,
const GPURenderPipelineDescriptor* descriptor) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
OwnedRenderPipelineDescriptor dawn_desc_info;
v8::Isolate* isolate = script_state->GetIsolate();
ExceptionState exception_state(isolate, ExceptionState::kConstructionContext,
"GPUVertexStateDescriptor");
ConvertToDawnType(isolate, descriptor, &dawn_desc_info, exception_state);
if (exception_state.HadException()) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kOperationError,
"Error in parsing GPURenderPipelineDescriptor"));
} else {
auto* callback =
BindDawnOnceCallback(&GPUDevice::OnCreateRenderPipelineAsyncCallback,
WrapPersistent(this), WrapPersistent(resolver));
GetProcs().deviceCreateRenderPipelineAsync(
GetHandle(), &dawn_desc_info.dawn_desc, callback->UnboundCallback(),
callback->AsUserdata());
}
// WebGPU guarantees that promises are resolved in finite time so we need to
// ensure commands are flushed.
EnsureFlush();
return promise;
}
ScriptPromise GPUDevice::createComputePipelineAsync(
ScriptState* script_state,
const GPUComputePipelineDescriptor* descriptor) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
std::string label;
OwnedProgrammableStageDescriptor computeStageDescriptor;
WGPUComputePipelineDescriptor dawn_desc =
AsDawnType(descriptor, &label, &computeStageDescriptor);
auto* callback =
BindDawnOnceCallback(&GPUDevice::OnCreateComputePipelineAsyncCallback,
WrapPersistent(this), WrapPersistent(resolver));
GetProcs().deviceCreateComputePipelineAsync(GetHandle(), &dawn_desc,
callback->UnboundCallback(),
callback->AsUserdata());
// WebGPU guarantees that promises are resolved in finite time so we need to
// ensure commands are flushed.
EnsureFlush();
return promise;
}
ScriptPromise GPUDevice::createReadyRenderPipeline(
ScriptState* script_state,
const GPURenderPipelineDescriptor* descriptor) {
AddConsoleWarning(
"createReadyRenderPipeline is deprecated in favor of "
"createRenderPipelineAsync");
return createRenderPipelineAsync(script_state, descriptor);
}
ScriptPromise GPUDevice::createReadyComputePipeline(
ScriptState* script_state,
const GPUComputePipelineDescriptor* descriptor) {
AddConsoleWarning(
"createReadyComputePipeline is deprecated in favor of "
"createComputePipelineAsync");
return createComputePipelineAsync(script_state, descriptor);
}
GPUCommandEncoder* GPUDevice::createCommandEncoder(
const GPUCommandEncoderDescriptor* descriptor) {
return GPUCommandEncoder::Create(this, descriptor);
}
GPURenderBundleEncoder* GPUDevice::createRenderBundleEncoder(
const GPURenderBundleEncoderDescriptor* descriptor) {
return GPURenderBundleEncoder::Create(this, descriptor);
}
GPUQuerySet* GPUDevice::createQuerySet(
const GPUQuerySetDescriptor* descriptor) {
return GPUQuerySet::Create(this, descriptor);
}
void GPUDevice::pushErrorScope(const WTF::String& filter) {
GetProcs().devicePushErrorScope(GetHandle(),
AsDawnEnum<WGPUErrorFilter>(filter));
}
ScriptPromise GPUDevice::popErrorScope(ScriptState* script_state) {
ScriptPromiseResolver* resolver =
MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
auto* callback =
BindDawnOnceCallback(&GPUDevice::OnPopErrorScopeCallback,
WrapPersistent(this), WrapPersistent(resolver));
if (!GetProcs().devicePopErrorScope(GetHandle(), callback->UnboundCallback(),
callback->AsUserdata())) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kOperationError, "No error scopes to pop."));
delete callback;
return promise;
}
// WebGPU guarantees that promises are resolved in finite time so we
// need to ensure commands are flushed.
EnsureFlush();
return promise;
}
void GPUDevice::OnPopErrorScopeCallback(ScriptPromiseResolver* resolver,
WGPUErrorType type,
const char* message) {
v8::Isolate* isolate = resolver->GetScriptState()->GetIsolate();
switch (type) {
case WGPUErrorType_NoError:
resolver->Resolve(v8::Null(isolate));
break;
case WGPUErrorType_OutOfMemory:
resolver->Resolve(GPUOutOfMemoryError::Create());
break;
case WGPUErrorType_Validation:
resolver->Resolve(MakeGarbageCollected<GPUValidationError>(message));
break;
case WGPUErrorType_Unknown:
case WGPUErrorType_DeviceLost:
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kOperationError));
break;
default:
NOTREACHED();
}
}
ExecutionContext* GPUDevice::GetExecutionContext() const {
return ExecutionContextClient::GetExecutionContext();
}
const AtomicString& GPUDevice::InterfaceName() const {
return event_target_names::kGPUDevice;
}
void GPUDevice::Trace(Visitor* visitor) const {
visitor->Trace(adapter_);
visitor->Trace(queue_);
visitor->Trace(lost_property_);
ExecutionContextClient::Trace(visitor);
EventTargetWithInlineData::Trace(visitor);
}
} // namespace blink