| // Copyright 2019 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_shader_module.h" |
| |
| #include "base/numerics/clamped_math.h" |
| #include "gpu/command_buffer/client/webgpu_interface.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_shader_module_descriptor.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu_compilation_info.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu_compilation_message.h" |
| #include "third_party/blink/renderer/modules/webgpu/gpu_device.h" |
| #include "third_party/blink/renderer/modules/webgpu/string_utils.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_callback.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_cpp.h" |
| |
| namespace blink { |
| |
| // static |
| GPUShaderModule* GPUShaderModule::Create( |
| GPUDevice* device, |
| const GPUShaderModuleDescriptor* webgpu_desc) { |
| DCHECK(device); |
| DCHECK(webgpu_desc); |
| |
| wgpu::ShaderSourceWGSL wgsl_desc = {}; |
| const WTF::String& wtf_wgsl_code = webgpu_desc->code(); |
| std::string wgsl_code = wtf_wgsl_code.Utf8(); |
| wgsl_desc.code = wgsl_code.c_str(); |
| |
| wgpu::ShaderModuleCompilationOptions compilation_options = {}; |
| if (webgpu_desc->hasStrictMath() && |
| device->GetHandle().HasFeature( |
| wgpu::FeatureName::ShaderModuleCompilationOptions)) { |
| compilation_options.strictMath = webgpu_desc->strictMath(); |
| wgsl_desc.nextInChain = &compilation_options; |
| } |
| |
| wgpu::ShaderModuleDescriptor dawn_desc = {}; |
| dawn_desc.nextInChain = &wgsl_desc; |
| |
| std::string label = webgpu_desc->label().Utf8(); |
| if (!label.empty()) { |
| dawn_desc.label = label.c_str(); |
| } |
| |
| wgpu::ShaderModule shader_module; |
| bool has_null_character = (wtf_wgsl_code.find('\0') != WTF::kNotFound); |
| if (has_null_character) { |
| shader_module = device->GetHandle().CreateErrorShaderModule( |
| &dawn_desc, "The WGSL shader contains an illegal character '\\0'"); |
| } else { |
| shader_module = device->GetHandle().CreateShaderModule(&dawn_desc); |
| } |
| |
| GPUShaderModule* shader = MakeGarbageCollected<GPUShaderModule>( |
| device, std::move(shader_module), webgpu_desc->label()); |
| |
| // Very roughly approximate how much memory Tint might need for this shader. |
| // Pessimizes if Tint actually holds less memory than this (including if the |
| // shader module ends up being invalid). |
| // |
| // The actual estimate (100x code size) is chosen by profiling: large enough |
| // to show some improvement in peak GPU process memory usage, small enough to |
| // not slow down shader conformance tests (which are much, much heavier on |
| // shader creation than normal workloads) more than a few percent. |
| // |
| // TODO(crbug.com/dawn/2367): Get a real memory estimate from Tint. |
| base::ClampedNumeric<int32_t> input_code_size = wgsl_code.size(); |
| shader->tint_memory_estimate_.Set(v8::Isolate::GetCurrent(), |
| input_code_size * 100); |
| |
| return shader; |
| } |
| |
| GPUShaderModule::GPUShaderModule(GPUDevice* device, |
| wgpu::ShaderModule shader_module, |
| const String& label) |
| : DawnObject<wgpu::ShaderModule>(device, std::move(shader_module), label) {} |
| |
| // TODO(crbug.com/351564777): should be UNSAFE_BUFFER_USAGE |
| void GPUShaderModule::OnCompilationInfoCallback( |
| ScriptPromiseResolver<GPUCompilationInfo>* resolver, |
| wgpu::CompilationInfoRequestStatus status, |
| const wgpu::CompilationInfo* info) { |
| if (status != wgpu::CompilationInfoRequestStatus::Success || !info) { |
| const char* message = nullptr; |
| switch (status) { |
| case wgpu::CompilationInfoRequestStatus::Success: |
| NOTREACHED(); |
| #ifdef WGPU_BREAKING_CHANGE_INSTANCE_DROPPED_RENAME |
| case wgpu::CompilationInfoRequestStatus::CallbackCancelled: |
| #else |
| case wgpu::CompilationInfoRequestStatus::InstanceDropped: |
| #endif // WGPU_BREAKING_CHANGE_INSTANCE_DROPPED_RENAME |
| message = "Instance dropped error in getCompilationInfo"; |
| break; |
| } |
| resolver->RejectWithDOMException(DOMExceptionCode::kOperationError, |
| message); |
| return; |
| } |
| |
| // Temporarily immediately create the CompilationInfo info and resolve the |
| // promise. |
| GPUCompilationInfo* result = MakeGarbageCollected<GPUCompilationInfo>(); |
| // SAFETY: Required from caller |
| const auto info_span = |
| UNSAFE_BUFFERS(base::span<const wgpu::CompilationMessage>( |
| info->messages, info->messageCount)); |
| for (const auto& message : info_span) { |
| const wgpu::DawnCompilationMessageUtf16* utf16 = nullptr; |
| for (const auto* chain = message.nextInChain; chain != nullptr; |
| chain = chain->nextInChain) { |
| if (chain->sType == wgpu::SType::DawnCompilationMessageUtf16) { |
| utf16 = |
| reinterpret_cast<const wgpu::DawnCompilationMessageUtf16*>(chain); |
| } |
| } |
| uint64_t linePos = utf16 ? utf16->linePos : message.linePos; |
| uint64_t offset = utf16 ? utf16->offset : message.offset; |
| uint64_t length = utf16 ? utf16->length : message.length; |
| result->AppendMessage(MakeGarbageCollected<GPUCompilationMessage>( |
| StringFromASCIIAndUTF8(message.message), message.type, message.lineNum, |
| linePos, offset, length)); |
| } |
| |
| resolver->Resolve(result); |
| } |
| |
| GPUShaderModule::~GPUShaderModule() { |
| tint_memory_estimate_.Clear(v8::Isolate::GetCurrent()); |
| } |
| |
| ScriptPromise<GPUCompilationInfo> GPUShaderModule::getCompilationInfo( |
| ScriptState* script_state) { |
| auto* resolver = |
| MakeGarbageCollected<ScriptPromiseResolver<GPUCompilationInfo>>( |
| script_state); |
| auto promise = resolver->Promise(); |
| |
| auto* callback = |
| MakeWGPUOnceCallback(resolver->WrapCallbackInScriptScope(WTF::BindOnce( |
| &GPUShaderModule::OnCompilationInfoCallback, WrapPersistent(this)))); |
| |
| GetHandle().GetCompilationInfo(wgpu::CallbackMode::AllowSpontaneous, |
| callback->UnboundCallback(), |
| callback->AsUserdata()); |
| // WebGPU guarantees that promises are resolved in finite time so we |
| // need to ensure commands are flushed. |
| EnsureFlush(ToEventLoop(script_state)); |
| return promise; |
| } |
| |
| } // namespace blink |