| // 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 "gpu/command_buffer/client/webgpu_implementation.h" | |
| #include <dawn/wire/client/webgpu.h> | |
| #include <algorithm> | |
| #include <vector> | |
| #include "base/compiler_specific.h" | |
| #include "base/feature_list.h" | |
| #include "base/notimplemented.h" | |
| #include "base/numerics/checked_math.h" | |
| #include "base/run_loop.h" | |
| #include "base/trace_event/trace_event.h" | |
| #include "base/types/optional_util.h" | |
| #include "gpu/command_buffer/client/dawn_client_memory_transfer_service.h" | |
| #include "gpu/command_buffer/client/dawn_client_serializer.h" | |
| #include "gpu/command_buffer/client/gpu_control.h" | |
| #include "gpu/command_buffer/client/shared_memory_limits.h" | |
| #include "gpu/config/gpu_finch_features.h" | |
| #include "third_party/perfetto/include/perfetto/tracing/track_event_args.h" | |
| #define GPU_CLIENT_SINGLE_THREAD_CHECK() | |
| namespace gpu { | |
| namespace webgpu { | |
| #if BUILDFLAG(USE_DAWN) | |
| DawnWireServices::~DawnWireServices() { | |
| wgpuDawnWireClientInstanceRelease(wgpu_instance_); | |
| } | |
| DawnWireServices::DawnWireServices( | |
| WebGPUImplementation* webgpu_implementation, | |
| WebGPUCmdHelper* helper, | |
| MappedMemoryManager* mapped_memory, | |
| std::unique_ptr<TransferBuffer> transfer_buffer, | |
| bool support_locking) | |
| : lock_(support_locking ? std::make_optional<base::Lock>() : std::nullopt), | |
| memory_transfer_service_( | |
| mapped_memory, | |
| base::FeatureList::IsEnabled( | |
| features::kWebGPUUseDedicatedTransferBuffer)), | |
| serializer_(webgpu_implementation, | |
| helper, | |
| &memory_transfer_service_, | |
| std::move(transfer_buffer)), | |
| wire_client_(dawn::wire::WireClientDescriptor{ | |
| &serializer_, | |
| &memory_transfer_service_, | |
| }) { | |
| std::vector<WGPUInstanceFeatureName> instance_features = { | |
| WGPUInstanceFeatureName_TimedWaitAny}; | |
| WGPUInstanceDescriptor instance_desc = WGPU_INSTANCE_DESCRIPTOR_INIT; | |
| instance_desc.requiredFeatureCount = instance_features.size(); | |
| instance_desc.requiredFeatures = instance_features.data(); | |
| wgpu_instance_ = wire_client_.ReserveInstance(&instance_desc).instance; | |
| DCHECK(wgpu_instance_); | |
| } | |
| base::WeakPtr<DawnWireServices> DawnWireServices::AsWeakPtr() { | |
| return weak_ptr_factory_.GetWeakPtr(); | |
| } | |
| WGPUInstance DawnWireServices::GetWGPUInstance() const { | |
| return wgpu_instance_; | |
| } | |
| void DawnWireServices::Disconnect() { | |
| { | |
| base::AutoLockMaybe lock(OptionalToPtr(lock_)); | |
| disconnected_ = true; | |
| } | |
| wire_client_.Disconnect(); | |
| serializer_.Disconnect(); | |
| memory_transfer_service_.Disconnect(); | |
| } | |
| void DawnWireServices::HandleCommands( | |
| uint64_t trace_id, | |
| base::span<const volatile uint8_t> commands) { | |
| TRACE_EVENT(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"), "DawnReturnCommands", | |
| perfetto::TerminatingFlow::Global(trace_id)); | |
| base::AutoLockMaybe lock(OptionalToPtr(lock_)); | |
| if (disconnected_) { | |
| return; | |
| } | |
| // Commands from the GPU process are expected to be well-formed. | |
| CHECK(wire_client_.HandleCommands(commands)); | |
| } | |
| void DawnWireServices::ProcessEvents() { | |
| wgpuDawnWireClientInstanceProcessEvents(wgpu_instance_); | |
| } | |
| dawn::wire::ReservedBuffer DawnWireServices::ReserveBuffer( | |
| WGPUDevice device, | |
| const WGPUBufferDescriptor* desc) { | |
| base::AutoLockMaybe lock(OptionalToPtr(lock_)); | |
| return wire_client_.ReserveBuffer(device, desc); | |
| } | |
| dawn::wire::ReservedTexture DawnWireServices::ReserveTexture( | |
| WGPUDevice device, | |
| const WGPUTextureDescriptor* desc) { | |
| base::AutoLockMaybe lock(OptionalToPtr(lock_)); | |
| return wire_client_.ReserveTexture(device, desc); | |
| } | |
| void DawnWireServices::Commit() { | |
| base::AutoLockMaybe lock(OptionalToPtr(lock_)); | |
| serializer_.Commit(); | |
| } | |
| void DawnWireServices::SetAwaitingFlush(bool awaiting_flush) { | |
| base::AutoLockMaybe lock(OptionalToPtr(lock_)); | |
| serializer_.SetAwaitingFlush(awaiting_flush); | |
| } | |
| bool DawnWireServices::EnsureAwaitingFlush() { | |
| base::AutoLockMaybe lock(OptionalToPtr(lock_)); | |
| // If there is already a flush waiting, we don't need to flush. | |
| // We only want to ask for a flush on state transition from | |
| // false -> true. | |
| if (serializer_.AwaitingFlush()) { | |
| return false; | |
| } | |
| // Set the state to waiting for flush. | |
| serializer_.SetAwaitingFlush(true); | |
| return true; | |
| } | |
| void DawnWireServices::FreeMappedResources(WebGPUCmdHelper* helper) { | |
| base::AutoLockMaybe lock(OptionalToPtr(lock_)); | |
| memory_transfer_service_.FreeHandles(helper); | |
| } | |
| #endif | |
| // Include the auto-generated part of this file. We split this because it means | |
| // we can easily edit the non-auto generated parts right here in this file | |
| // instead of having to edit some template or the code generator. | |
| #include "gpu/command_buffer/client/webgpu_implementation_impl_autogen.h" | |
| WebGPUImplementation::WebGPUImplementation( | |
| WebGPUCmdHelper* helper, | |
| TransferBufferInterface* transfer_buffer, | |
| GpuControl* gpu_control, | |
| bool support_locking) | |
| : ImplementationBase(helper, transfer_buffer, gpu_control), | |
| helper_(helper), | |
| main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {} | |
| WebGPUImplementation::~WebGPUImplementation() { | |
| LoseContext(); | |
| // Before destroying WebGPUImplementation, all mappable buffers | |
| // must be destroyed first. This means that all shared memory mappings are | |
| // detached. If they are not destroyed, MappedMemoryManager (member of | |
| // base class ImplementationBase) will assert on destruction that some | |
| // memory blocks are in use. Calling |FreeMappedResources| marks all | |
| // blocks that are no longer in use as free. | |
| #if BUILDFLAG(USE_DAWN) | |
| if (dawn_wire_) { | |
| dawn_wire_->FreeMappedResources(helper_); | |
| dawn_wire_.reset(); | |
| } | |
| #endif | |
| // Wait for commands to finish before we continue destruction. | |
| // WebGPUImplementation no longer owns the WebGPU transfer buffer, but still | |
| // owns the GPU command buffer. We should not free shared memory that the | |
| // GPU process is using. | |
| helper_->Finish(); | |
| } | |
| void WebGPUImplementation::LoseContext() { | |
| lost_ = true; | |
| #if BUILDFLAG(USE_DAWN) | |
| if (dawn_wire_) { | |
| dawn_wire_->Disconnect(); | |
| } | |
| #endif | |
| } | |
| gpu::ContextResult WebGPUImplementation::Initialize( | |
| const SharedMemoryLimits& limits) { | |
| TRACE_EVENT0("gpu", "WebGPUImplementation::Initialize"); | |
| auto result = ImplementationBase::Initialize(limits); | |
| if (result != gpu::ContextResult::kSuccess) { | |
| return result; | |
| } | |
| std::unique_ptr<TransferBuffer> transfer_buffer = | |
| std::make_unique<TransferBuffer>(helper_); | |
| if (!transfer_buffer->Initialize( | |
| limits.start_transfer_buffer_size, | |
| /* start offset */ 0, limits.min_transfer_buffer_size, | |
| limits.max_transfer_buffer_size, kAlignment)) { | |
| return gpu::ContextResult::kFatalFailure; | |
| } | |
| #if BUILDFLAG(USE_DAWN) | |
| dawn_wire_ = base::MakeRefCounted<DawnWireServices>( | |
| this, helper_, mapped_memory_.get(), std::move(transfer_buffer), | |
| main_task_runner_ != nullptr); | |
| #endif | |
| return gpu::ContextResult::kSuccess; | |
| } | |
| // ContextSupport implementation. | |
| void WebGPUImplementation::SetAggressivelyFreeResources( | |
| bool aggressively_free_resources) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void WebGPUImplementation::SetErrorMessageCallback( | |
| base::RepeatingCallback<void(const char*, int32_t)> callback) { | |
| NOTIMPLEMENTED(); | |
| } | |
| base::span<uint8_t> WebGPUImplementation::MapTransferCacheEntry( | |
| uint32_t serialized_size) { | |
| NOTREACHED(); | |
| } | |
| void WebGPUImplementation::UnmapAndCreateTransferCacheEntry(uint32_t type, | |
| uint32_t id) { | |
| NOTREACHED(); | |
| } | |
| bool WebGPUImplementation::ThreadsafeLockTransferCacheEntry(uint32_t type, | |
| uint32_t id) { | |
| NOTREACHED(); | |
| } | |
| void WebGPUImplementation::UnlockTransferCacheEntries( | |
| const std::vector<std::pair<uint32_t, uint32_t>>& entries) { | |
| NOTREACHED(); | |
| } | |
| void WebGPUImplementation::DeleteTransferCacheEntry(uint32_t type, | |
| uint32_t id) { | |
| NOTREACHED(); | |
| } | |
| unsigned int WebGPUImplementation::GetTransferBufferFreeSize() const { | |
| NOTREACHED(); | |
| } | |
| // InterfaceBase implementation. | |
| void WebGPUImplementation::GenSyncTokenCHROMIUM(GLbyte* sync_token) { | |
| // Need to commit the commands to the GPU command buffer first for SyncToken | |
| // to work. | |
| #if BUILDFLAG(USE_DAWN) | |
| dawn_wire_->Commit(); | |
| #endif | |
| ImplementationBase::GenSyncToken(sync_token); | |
| } | |
| void WebGPUImplementation::GenUnverifiedSyncTokenCHROMIUM(GLbyte* sync_token) { | |
| // Need to commit the commands to the GPU command buffer first for SyncToken | |
| // to work. | |
| #if BUILDFLAG(USE_DAWN) | |
| dawn_wire_->Commit(); | |
| #endif | |
| ImplementationBase::GenUnverifiedSyncToken(sync_token); | |
| } | |
| void WebGPUImplementation::VerifySyncTokensCHROMIUM(GLbyte** sync_tokens, | |
| GLsizei count) { | |
| ImplementationBase::VerifySyncTokens(sync_tokens, count); | |
| } | |
| void WebGPUImplementation::WaitSyncTokenCHROMIUM(const GLbyte* sync_token) { | |
| // Need to commit the commands to the GPU command buffer first for SyncToken | |
| // to work. | |
| #if BUILDFLAG(USE_DAWN) | |
| dawn_wire_->Commit(); | |
| #endif | |
| ImplementationBase::WaitSyncToken(sync_token); | |
| } | |
| void WebGPUImplementation::ShallowFlushCHROMIUM() { | |
| FlushCommands(); | |
| } | |
| // ImplementationBase implementation. | |
| void WebGPUImplementation::IssueShallowFlush() { | |
| NOTIMPLEMENTED(); | |
| } | |
| void WebGPUImplementation::SetGLError(GLenum error, | |
| const char* function_name, | |
| const char* msg) { | |
| GPU_CLIENT_LOG("[" << GetLogPrefix() << "] Client Synthesized Error: " | |
| << gles2::GLES2Util::GetStringError(error) << ": " | |
| << function_name << ": " << msg); | |
| NOTIMPLEMENTED(); | |
| } | |
| // GpuControlClient implementation. | |
| void WebGPUImplementation::OnGpuControlLostContext() { | |
| LoseContext(); | |
| // This should never occur more than once. | |
| DCHECK(!lost_context_callback_run_); | |
| lost_context_callback_run_ = true; | |
| if (!lost_context_callback_.is_null()) { | |
| std::move(lost_context_callback_).Run(); | |
| } | |
| } | |
| void WebGPUImplementation::OnGpuControlLostContextMaybeReentrant() { | |
| // If this function is called, we are guaranteed to also get a call | |
| // to |OnGpuControlLostContext| when the callstack unwinds. Thus, this | |
| // function only handles immediately setting state so that other operations | |
| // which occur while the callstack is unwinding are aware that the context | |
| // is lost. | |
| lost_ = true; | |
| } | |
| void WebGPUImplementation::OnGpuControlErrorMessage(const char* message, | |
| int32_t id) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void WebGPUImplementation::OnGpuControlReturnData( | |
| base::span<const uint8_t> data) { | |
| if (lost_) { | |
| return; | |
| } | |
| #if BUILDFLAG(USE_DAWN) | |
| TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"), | |
| "WebGPUImplementation::OnGpuControlReturnData", "bytes", | |
| data.size()); | |
| CHECK_GT(data.size(), sizeof(cmds::DawnReturnDataHeader)); | |
| const cmds::DawnReturnDataHeader& dawnReturnDataHeader = | |
| *reinterpret_cast<const cmds::DawnReturnDataHeader*>(data.data()); | |
| switch (dawnReturnDataHeader.return_data_type) { | |
| case DawnReturnDataType::kDawnCommands: { | |
| CHECK_GE(data.size(), sizeof(cmds::DawnReturnCommandsInfo)); | |
| dawn_wire_->HandleCommands( | |
| dawnReturnDataHeader.trace_id, | |
| data.subspan( | |
| offsetof(cmds::DawnReturnCommandsInfo, deserialized_buffer))); | |
| // Call ProcessEvents now, always posting the task to do so to the | |
| // runner to handle the callbacks in a fresh call stack to avoid | |
| // re-entrancy. | |
| CHECK(main_task_runner_); | |
| main_task_runner_->PostTask( | |
| FROM_HERE, base::BindOnce(&DawnWireServices::ProcessEvents, | |
| dawn_wire_->AsWeakPtr())); | |
| } break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| #endif | |
| } | |
| void WebGPUImplementation::FlushCommands() { | |
| #if BUILDFLAG(USE_DAWN) | |
| dawn_wire_->Commit(); | |
| helper_->Flush(); | |
| #endif | |
| } | |
| bool WebGPUImplementation::EnsureAwaitingFlush() { | |
| #if BUILDFLAG(USE_DAWN) | |
| return dawn_wire_->EnsureAwaitingFlush(); | |
| #else | |
| return false; | |
| #endif | |
| } | |
| void WebGPUImplementation::FlushAwaitingCommands() { | |
| #if BUILDFLAG(USE_DAWN) | |
| dawn_wire_->Commit(); | |
| helper_->FlushLazy(); | |
| dawn_wire_->SetAwaitingFlush(false); | |
| #endif | |
| } | |
| scoped_refptr<APIChannel> WebGPUImplementation::GetAPIChannel() const { | |
| #if BUILDFLAG(USE_DAWN) | |
| return dawn_wire_.get(); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| ReservedBuffer WebGPUImplementation::ReserveBuffer( | |
| WGPUDevice device, | |
| const WGPUBufferDescriptor* optionalDesc) { | |
| #if BUILDFLAG(USE_DAWN) | |
| // Commit because we need to make sure messages that free a previously used | |
| // buffer is seen first. ReserveBuffer may reuse an existing ID. | |
| dawn_wire_->Commit(); | |
| WGPUBufferDescriptor placeholderDesc; | |
| if (optionalDesc == nullptr) { | |
| placeholderDesc = {}; // Zero initialize. | |
| optionalDesc = &placeholderDesc; | |
| } | |
| auto reserved = dawn_wire_->ReserveBuffer(device, optionalDesc); | |
| ReservedBuffer result; | |
| result.buffer = reserved.buffer; | |
| result.id = reserved.handle.id; | |
| result.generation = reserved.handle.generation; | |
| result.deviceId = reserved.deviceHandle.id; | |
| result.deviceGeneration = reserved.deviceHandle.generation; | |
| return result; | |
| #else | |
| return {}; | |
| #endif | |
| } | |
| ReservedTexture WebGPUImplementation::ReserveTexture( | |
| WGPUDevice device, | |
| const WGPUTextureDescriptor* optionalDesc) { | |
| #if BUILDFLAG(USE_DAWN) | |
| // Commit because we need to make sure messages that free a previously used | |
| // texture are seen first. ReserveTexture may reuse an existing ID. | |
| dawn_wire_->Commit(); | |
| WGPUTextureDescriptor placeholderDesc; | |
| if (optionalDesc == nullptr) { | |
| placeholderDesc = {}; // Zero initialize. | |
| optionalDesc = &placeholderDesc; | |
| } | |
| auto reserved = dawn_wire_->ReserveTexture(device, optionalDesc); | |
| ReservedTexture result; | |
| result.texture = reserved.texture; | |
| result.id = reserved.handle.id; | |
| result.generation = reserved.handle.generation; | |
| result.deviceId = reserved.deviceHandle.id; | |
| result.deviceGeneration = reserved.deviceHandle.generation; | |
| return result; | |
| #else | |
| NOTREACHED(); | |
| #endif | |
| } | |
| WGPUDevice WebGPUImplementation::DeprecatedEnsureDefaultDeviceSync() { | |
| NOTIMPLEMENTED(); | |
| return nullptr; | |
| } | |
| void WebGPUImplementation::AssociateMailbox( | |
| GLuint device_id, | |
| GLuint device_generation, | |
| GLuint texture_id, | |
| GLuint texture_generation, | |
| uint64_t usage, | |
| uint64_t internal_usage, | |
| const WGPUTextureFormat* view_formats, | |
| GLuint view_format_count, | |
| MailboxFlags flags, | |
| const Mailbox& mailbox) { | |
| #if BUILDFLAG(USE_DAWN) | |
| // Commit previous Dawn commands as they may manipulate texture object IDs | |
| // and need to be resolved prior to the AssociateMailbox command. Otherwise | |
| // the service side might not know, for example that the previous texture | |
| // using that ID has been released. | |
| dawn_wire_->Commit(); | |
| // The command buffer transfer data in 4-byte "entries". So the array of data | |
| // we pass must have a byte-length that's a multiple of 4. | |
| constexpr size_t kEntrySize = 4u; | |
| static_assert(sizeof(mailbox.name) % kEntrySize == 0u); | |
| static_assert(sizeof(WGPUTextureFormat) % kEntrySize == 0u); | |
| size_t num_bytes = | |
| sizeof(mailbox.name) + sizeof(WGPUTextureFormat) * view_format_count; | |
| std::vector<char> immediate_data(num_bytes); | |
| uint32_t num_entries = ComputeNumEntries(immediate_data.size()); | |
| UNSAFE_TODO( | |
| memcpy(immediate_data.data(), mailbox.name, sizeof(mailbox.name))); | |
| UNSAFE_TODO(memcpy(immediate_data.data() + sizeof(mailbox.name), view_formats, | |
| sizeof(WGPUTextureFormat) * view_format_count)); | |
| helper_->AssociateMailboxImmediate( | |
| device_id, device_generation, texture_id, texture_generation, usage, | |
| internal_usage, flags, view_format_count, num_entries, | |
| UNSAFE_TODO(reinterpret_cast<GLuint*>(immediate_data.data()))); | |
| #endif | |
| } | |
| void WebGPUImplementation::AssociateMailboxForBuffer(GLuint device_id, | |
| GLuint device_generation, | |
| GLuint buffer_id, | |
| GLuint buffer_generation, | |
| uint64_t usage, | |
| const Mailbox& mailbox) { | |
| #if BUILDFLAG(USE_DAWN) | |
| // Commit previous Dawn commands as they may manipulate buffer object IDs | |
| // and need to be resolved prior to the AssociateMailboxForBuffer command. | |
| // Otherwise the service side might not know, for example that the previous | |
| // buffer using that ID has been released. | |
| dawn_wire_->Commit(); | |
| // The command buffer transfer data in 4-byte "entries". So the array of data | |
| // we pass must have a byte-length that's a multiple of 4. | |
| constexpr size_t kEntrySize = 4u; | |
| static_assert(sizeof(mailbox.name) % kEntrySize == 0u); | |
| helper_->AssociateMailboxForBufferImmediate( | |
| device_id, device_generation, buffer_id, buffer_generation, usage, | |
| reinterpret_cast<const GLuint*>(mailbox.name)); | |
| #endif | |
| } | |
| void WebGPUImplementation::DissociateMailbox(GLuint texture_id, | |
| GLuint texture_generation) { | |
| #if BUILDFLAG(USE_DAWN) | |
| // Commit previous Dawn commands that might be rendering to the texture, prior | |
| // to Dissociating the shared image from that texture. | |
| dawn_wire_->Commit(); | |
| helper_->DissociateMailbox(texture_id, texture_generation); | |
| #endif | |
| } | |
| void WebGPUImplementation::DissociateMailboxForBuffer( | |
| GLuint buffer_id, | |
| GLuint buffer_generation) { | |
| #if BUILDFLAG(USE_DAWN) | |
| // Commit previous Dawn commands that might be rendering to the buffer, prior | |
| // to Dissociating the shared image from that buffer. | |
| dawn_wire_->Commit(); | |
| helper_->DissociateMailboxForBuffer(buffer_id, buffer_generation); | |
| #endif | |
| } | |
| void WebGPUImplementation::DissociateMailboxForPresent( | |
| GLuint device_id, | |
| GLuint device_generation, | |
| GLuint texture_id, | |
| GLuint texture_generation) { | |
| #if BUILDFLAG(USE_DAWN) | |
| // Commit previous Dawn commands that might be rendering to the texture, prior | |
| // to Dissociating the shared image from that texture. | |
| dawn_wire_->Commit(); | |
| helper_->DissociateMailboxForPresent(device_id, device_generation, texture_id, | |
| texture_generation); | |
| #endif | |
| } | |
| void WebGPUImplementation::SetWebGPUExecutionContextToken(uint32_t type, | |
| uint32_t high_high, | |
| uint32_t high_low, | |
| uint32_t low_high, | |
| uint32_t low_low) { | |
| #if BUILDFLAG(USE_DAWN) | |
| helper_->SetWebGPUExecutionContextToken(type, high_high, high_low, low_high, | |
| low_low); | |
| #endif | |
| } | |
| } // namespace webgpu | |
| } // namespace gpu |