| // Copyright 2021 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/dawn_client_serializer.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/numerics/checked_math.h" | |
| #include "base/trace_event/trace_event.h" | |
| #include "gpu/command_buffer/client/dawn_client_memory_transfer_service.h" | |
| #include "gpu/command_buffer/client/shared_memory_limits.h" | |
| #include "gpu/command_buffer/client/webgpu_cmd_helper.h" | |
| #include "gpu/command_buffer/client/webgpu_implementation.h" | |
| #include "third_party/perfetto/include/perfetto/tracing/track_event_args.h" | |
| namespace gpu { | |
| namespace webgpu { | |
| DawnClientSerializer::DawnClientSerializer( | |
| WebGPUImplementation* client, | |
| WebGPUCmdHelper* helper, | |
| DawnClientMemoryTransferService* memory_transfer_service_, | |
| std::unique_ptr<TransferBuffer> transfer_buffer) | |
| : client_(client), | |
| helper_(helper), | |
| memory_transfer_service_(memory_transfer_service_), | |
| transfer_buffer_(std::move(transfer_buffer)), | |
| buffer_initial_size_(transfer_buffer_->GetSize()), | |
| buffer_(helper_, transfer_buffer_.get()) { | |
| DCHECK_GT(buffer_initial_size_, 0u); | |
| } | |
| DawnClientSerializer::~DawnClientSerializer() = default; | |
| size_t DawnClientSerializer::GetMaximumAllocationSize() const { | |
| return transfer_buffer_->GetMaxSize(); | |
| } | |
| std::optional<std::span<volatile std::byte>> | |
| DawnClientSerializer::GetCommandSpace(size_t size) { | |
| // Note: Dawn will never call this function with |size| > | |
| // GetMaximumAllocationSize(). | |
| DCHECK_LE(size, GetMaximumAllocationSize()); | |
| // The buffer size must be initialized before any commands are serialized. | |
| DCHECK_NE(buffer_initial_size_, 0u); | |
| DCHECK_LE(put_offset_, buffer_.size()); | |
| const bool overflows_remaining_space = | |
| size > static_cast<size_t>(buffer_.size() - put_offset_); | |
| if (buffer_.valid() && !overflows_remaining_space) [[likely]] { | |
| // If the buffer is valid and has sufficient space, return the | |
| // pointer and increment the offset. | |
| auto space = buffer_.as_byte_span().subspan(put_offset_, size); | |
| put_offset_ += static_cast<uint32_t>(size); | |
| return std::span<volatile std::byte>( | |
| reinterpret_cast<std::byte*>(space.data()), space.size()); | |
| } | |
| if (!transfer_buffer_) { | |
| // The serializer hit a fatal error and was disconnected. | |
| return std::nullopt; | |
| } | |
| // Otherwise, flush and reset the command stream. | |
| Flush(); | |
| uint32_t allocation_size = | |
| std::max(buffer_initial_size_, static_cast<uint32_t>(size)); | |
| TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"), | |
| "DawnClientSerializer::GetCommandSpace", "bytes", | |
| allocation_size); | |
| buffer_.Reset(allocation_size); | |
| if (!buffer_.valid() || buffer_.size() < size) { | |
| DLOG(ERROR) << "Dawn wire transfer buffer allocation failed"; | |
| Disconnect(); | |
| client_->OnGpuControlLostContextMaybeReentrant(); | |
| return std::nullopt; | |
| } | |
| put_offset_ = size; | |
| auto space = buffer_.as_byte_span().first(size); | |
| return std::span<volatile std::byte>( | |
| reinterpret_cast<std::byte*>(space.data()), space.size()); | |
| } | |
| void DawnClientSerializer::Commit() { | |
| if (buffer_.valid()) { | |
| TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"), | |
| "DawnClientSerializer::Flush", "bytes", put_offset_); | |
| bool is_tracing = false; | |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"), | |
| &is_tracing); | |
| uint64_t trace_id; | |
| if (is_tracing) { | |
| trace_id = base::RandUint64(); | |
| TRACE_EVENT(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"), "DawnCommands", | |
| perfetto::Flow::Global(trace_id)); | |
| } else { | |
| trace_id = 0; | |
| } | |
| buffer_.Shrink(put_offset_); | |
| helper_->DawnCommands(trace_id >> 32, trace_id & 0xFFFF'FFFF, | |
| buffer_.shm_id(), buffer_.offset(), put_offset_); | |
| put_offset_ = 0; | |
| buffer_.Release(); | |
| memory_transfer_service_->FreeHandles(helper_); | |
| } | |
| } | |
| void DawnClientSerializer::SetAwaitingFlush(bool awaiting_flush) { | |
| // Set awaiting_flush_. Even if there are no commands in buffer_, this may be | |
| // necessary since the buffer_ commands could have been committed and reset, | |
| // but not yet flushed. | |
| awaiting_flush_ = awaiting_flush; | |
| } | |
| void DawnClientSerializer::Disconnect() { | |
| buffer_.Discard(); | |
| if (transfer_buffer_) { | |
| auto transfer_buffer = std::move(transfer_buffer_); | |
| // Wait for commands to finish before we free shared memory that | |
| // the GPU process is using. | |
| // TODO(crbug.com/40779774): This Finish may not be necessary if the | |
| // shared memory is not immediately freed. Investigate this and | |
| // consider optimization. | |
| helper_->Finish(); | |
| transfer_buffer = nullptr; | |
| } | |
| } | |
| bool DawnClientSerializer::Flush() { | |
| Commit(); | |
| return true; | |
| } | |
| } // namespace webgpu | |
| } // namespace gpu |