| // 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/implementation_base.h" | |
| #include <algorithm> | |
| #include "base/compiler_specific.h" | |
| #include "base/functional/bind.h" | |
| #include "base/logging.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/trace_event/memory_dump_manager.h" | |
| #include "base/trace_event/trace_event.h" | |
| #include "gpu/command_buffer/client/cmd_buffer_helper.h" | |
| #include "gpu/command_buffer/client/gpu_control.h" | |
| #include "gpu/command_buffer/client/mapped_memory.h" | |
| #include "gpu/command_buffer/client/query_tracker.h" | |
| #include "gpu/command_buffer/client/shared_memory_limits.h" | |
| #include "gpu/command_buffer/common/sync_token.h" | |
| namespace gpu { | |
| const uint32_t ImplementationBase::kMaxSizeOfSimpleResult; | |
| const uint32_t ImplementationBase::kStartingOffset; | |
| ImplementationBase::ImplementationBase(CommandBufferHelper* helper, | |
| TransferBufferInterface* transfer_buffer, | |
| GpuControl* gpu_control) | |
| : transfer_buffer_(transfer_buffer), | |
| gpu_control_(gpu_control), | |
| capabilities_(gpu_control->GetCapabilities()), | |
| helper_(helper) {} | |
| ImplementationBase::~ImplementationBase() { | |
| // The gpu_control_ outlives this class, so clear the client on it before we | |
| // self-destruct. | |
| gpu_control_->SetGpuControlClient(nullptr); | |
| } | |
| void ImplementationBase::FreeUnusedSharedMemory() { | |
| mapped_memory_->FreeUnused(); | |
| } | |
| void ImplementationBase::FreeEverything() { | |
| query_tracker_->Shrink(helper_); | |
| FreeUnusedSharedMemory(); | |
| transfer_buffer_->Free(); | |
| helper_->FreeRingBuffer(); | |
| } | |
| void ImplementationBase::SetLostContextCallback(base::OnceClosure callback) { | |
| lost_context_callback_ = std::move(callback); | |
| } | |
| void ImplementationBase::FlushPendingWork() { | |
| gpu_control_->FlushPendingWork(); | |
| } | |
| void ImplementationBase::SignalSyncToken(const SyncToken& sync_token, | |
| base::OnceClosure callback) { | |
| SyncToken verified_sync_token; | |
| if (sync_token.HasData() && | |
| GetVerifiedSyncTokenForIPC(sync_token, &verified_sync_token)) { | |
| // We can only send verified sync tokens across IPC. | |
| gpu_control_->SignalSyncToken( | |
| verified_sync_token, | |
| base::BindOnce(&ImplementationBase::RunIfContextNotLost, | |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | |
| } else { | |
| // Invalid sync token, just call the callback immediately. | |
| std::move(callback).Run(); | |
| } | |
| } | |
| // This may be called from any thread. It's safe to access gpu_control_ without | |
| // the lock because it is const. | |
| bool ImplementationBase::IsSyncTokenSignaled(const SyncToken& sync_token) { | |
| // Check that the sync token belongs to this context. | |
| DCHECK_EQ(gpu_control_->GetNamespaceID(), sync_token.namespace_id()); | |
| DCHECK_EQ(gpu_control_->GetCommandBufferID(), sync_token.command_buffer_id()); | |
| return gpu_control_->IsFenceSyncReleased(sync_token.release_count()); | |
| } | |
| SyncPointClientId ImplementationBase::GetSyncPointClientId() const { | |
| return {gpu_control_->GetNamespaceID(), gpu_control_->GetCommandBufferID()}; | |
| } | |
| void ImplementationBase::GenSyncToken(GLbyte* sync_token) { | |
| if (!sync_token) { | |
| SetGLError(GL_INVALID_VALUE, "glGenSyncTokenCHROMIUM", "empty sync_token"); | |
| return; | |
| } | |
| uint64_t fence_sync = helper_->InsertFenceSync( | |
| [this]() { return gpu_control_->GenerateFenceSyncRelease(); }); | |
| helper_->CommandBufferHelper::OrderingBarrier(); | |
| gpu_control_->EnsureWorkVisible(); | |
| // Copy the data over after setting the data to ensure alignment. | |
| SyncToken sync_token_data(gpu_control_->GetNamespaceID(), | |
| gpu_control_->GetCommandBufferID(), fence_sync); | |
| sync_token_data.SetVerifyFlush(); | |
| // SAFETY: The caller must ensure `sync_token` points to | |
| // `sizeof(sync_token_data)` bytes. | |
| UNSAFE_TODO(base::span(sync_token, sizeof(sync_token_data)) | |
| .copy_from(base::span(sync_token_data.GetConstData(), | |
| sizeof(sync_token_data)))); | |
| } | |
| void ImplementationBase::GenUnverifiedSyncToken(GLbyte* sync_token) { | |
| if (!sync_token) { | |
| SetGLError(GL_INVALID_VALUE, "glGenUnverifiedSyncTokenCHROMIUM", | |
| "empty sync_token"); | |
| return; | |
| } | |
| uint64_t fence_sync = helper_->InsertFenceSync( | |
| [this]() { return gpu_control_->GenerateFenceSyncRelease(); }); | |
| helper_->CommandBufferHelper::OrderingBarrier(); | |
| // Copy the data over after setting the data to ensure alignment. | |
| SyncToken sync_token_data(gpu_control_->GetNamespaceID(), | |
| gpu_control_->GetCommandBufferID(), fence_sync); | |
| // SAFETY: The caller must ensure `sync_token` points to | |
| // `sizeof(sync_token_data)` bytes. | |
| UNSAFE_TODO(base::span(sync_token, sizeof(sync_token_data)) | |
| .copy_from(base::span(sync_token_data.GetConstData(), | |
| sizeof(sync_token_data)))); | |
| } | |
| void ImplementationBase::VerifySyncTokens(GLbyte** sync_tokens, GLsizei count) { | |
| bool requires_synchronization = false; | |
| for (GLsizei i = 0; i < count; ++i) { | |
| if (UNSAFE_TODO(sync_tokens[i])) { | |
| SyncToken sync_token; | |
| // SAFETY: The API passes an array of sync-token pointers; this branch | |
| // handles only non-null entries. Each entry points to one sync-token | |
| // blob, and SyncToken is guaranteed to fit (see sync_token.h | |
| // static_assert), so this fixed-size copy is bounded. | |
| UNSAFE_TODO( | |
| base::span(sync_token.GetData(), sizeof(sync_token)) | |
| .copy_from(base::span(sync_tokens[i], sizeof(sync_token)))); | |
| if (sync_token.HasData() && !sync_token.verified_flush()) { | |
| if (!GetVerifiedSyncTokenForIPC(sync_token, &sync_token)) { | |
| SetGLError(GL_INVALID_VALUE, "glVerifySyncTokensCHROMIUM", | |
| "Cannot verify sync token using this context."); | |
| return; | |
| } | |
| requires_synchronization = true; | |
| DCHECK(sync_token.verified_flush()); | |
| } | |
| // Set verify bit on empty sync tokens too. | |
| sync_token.SetVerifyFlush(); | |
| // SAFETY: Same reasoning as above; writing back exactly sizeof(SyncToken) | |
| // bytes to the same non-null sync-token blob. | |
| UNSAFE_TODO(base::span(sync_tokens[i], sizeof(sync_token)) | |
| .copy_from(base::span(sync_token.GetConstData(), | |
| sizeof(sync_token)))); | |
| } | |
| } | |
| // Ensure all the fence syncs are visible on GPU service. | |
| if (requires_synchronization) | |
| gpu_control_->EnsureWorkVisible(); | |
| } | |
| void ImplementationBase::WaitSyncToken(const GLbyte* sync_token_data) { | |
| if (!sync_token_data) | |
| return; | |
| // Copy the data over before data access to ensure alignment. | |
| SyncToken sync_token, verified_sync_token; | |
| // SAFETY: The caller must ensure `sync_token_data` points to | |
| // `sizeof(sync_token)` bytes. | |
| UNSAFE_TODO(base::span(sync_token.GetData(), sizeof(SyncToken)) | |
| .copy_from(base::span(sync_token_data, sizeof(SyncToken)))); | |
| if (!sync_token.HasData()) | |
| return; | |
| if (!GetVerifiedSyncTokenForIPC(sync_token, &verified_sync_token)) { | |
| SetGLError(GL_INVALID_VALUE, "glWaitSyncTokenCHROMIUM", | |
| "Cannot wait on sync_token which has not been verified"); | |
| return; | |
| } | |
| // Enqueue sync token in flush after inserting command so that it's not | |
| // included in an automatic flush. | |
| gpu_control_->WaitSyncToken(verified_sync_token); | |
| } | |
| void ImplementationBase::SignalQuery(uint32_t query, | |
| base::OnceClosure callback) { | |
| // Flush previously entered commands to ensure ordering with any | |
| // glBeginQueryEXT() calls that may have been put into the context. | |
| IssueShallowFlush(); | |
| gpu_control_->SignalQuery( | |
| query, | |
| base::BindOnce(&ImplementationBase::RunIfContextNotLost, | |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | |
| } | |
| void ImplementationBase::GetGpuFence( | |
| uint32_t gpu_fence_id, | |
| base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) { | |
| // This ShallowFlush is required to ensure that the GetGpuFence | |
| // call is processed after the preceding CreateGpuFenceCHROMIUM call. | |
| IssueShallowFlush(); | |
| gpu_control_->GetGpuFence(gpu_fence_id, std::move(callback)); | |
| } | |
| bool ImplementationBase::OnMemoryDump( | |
| const base::trace_event::MemoryDumpArgs& args, | |
| base::trace_event::ProcessMemoryDump* pmd) { | |
| using base::trace_event::MemoryAllocatorDump; | |
| using base::trace_event::MemoryDumpLevelOfDetail; | |
| // Dump owned MappedMemoryManager memory as well. | |
| mapped_memory_->OnMemoryDump(args, pmd); | |
| if (!transfer_buffer_->HaveBuffer()) | |
| return true; | |
| const uint64_t tracing_process_id = | |
| base::trace_event::MemoryDumpManager::GetInstance() | |
| ->GetTracingProcessId(); | |
| MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(base::StringPrintf( | |
| "gpu/transfer_buffer_memory/buffer_0x%x", transfer_buffer_->GetShmId())); | |
| dump->AddScalar(MemoryAllocatorDump::kNameSize, | |
| MemoryAllocatorDump::kUnitsBytes, | |
| transfer_buffer_->GetSize()); | |
| if (args.level_of_detail != MemoryDumpLevelOfDetail::kBackground) { | |
| dump->AddScalar("free_size", MemoryAllocatorDump::kUnitsBytes, | |
| transfer_buffer_->GetFragmentedFreeSize()); | |
| auto shared_memory_guid = transfer_buffer_->shared_memory_guid(); | |
| const int kImportance = 2; | |
| if (!shared_memory_guid.is_empty()) { | |
| pmd->CreateSharedMemoryOwnershipEdge(dump->guid(), shared_memory_guid, | |
| kImportance); | |
| } else { | |
| auto guid = GetBufferGUIDForTracing(tracing_process_id, | |
| transfer_buffer_->GetShmId()); | |
| pmd->CreateSharedGlobalAllocatorDump(guid); | |
| pmd->AddOwnershipEdge(dump->guid(), guid, kImportance); | |
| } | |
| } | |
| return true; | |
| } | |
| gpu::ContextResult ImplementationBase::Initialize( | |
| const SharedMemoryLimits& limits) { | |
| TRACE_EVENT0("gpu", "ImplementationBase::Initialize"); | |
| DCHECK_GE(limits.start_transfer_buffer_size, limits.min_transfer_buffer_size); | |
| DCHECK_LE(limits.start_transfer_buffer_size, limits.max_transfer_buffer_size); | |
| DCHECK_GE(limits.min_transfer_buffer_size, kStartingOffset); | |
| gpu_control_->SetGpuControlClient(this); | |
| if (!transfer_buffer_->Initialize( | |
| limits.start_transfer_buffer_size, kStartingOffset, | |
| limits.min_transfer_buffer_size, limits.max_transfer_buffer_size, | |
| kAlignment)) { | |
| // TransferBuffer::Initialize doesn't fail for transient reasons such as if | |
| // the context was lost. See http://crrev.com/c/720269 | |
| LOG(ERROR) << "ContextResult::kFatalFailure: " | |
| << "TransferBuffer::Initialize() failed"; | |
| return gpu::ContextResult::kFatalFailure; | |
| } | |
| mapped_memory_ = std::make_unique<MappedMemoryManager>( | |
| helper_, limits.mapped_memory_reclaim_limit); | |
| mapped_memory_->set_chunk_size_multiple(limits.mapped_memory_chunk_size); | |
| query_tracker_ = std::make_unique<gles2::QueryTracker>(mapped_memory_.get()); | |
| return gpu::ContextResult::kSuccess; | |
| } | |
| bool ImplementationBase::WaitForCmd() { | |
| TRACE_EVENT0("gpu", "ImplementationBase::WaitForCmd"); | |
| return helper_->Finish(); | |
| } | |
| int32_t ImplementationBase::GetResultShmId() { | |
| return transfer_buffer_->GetShmId(); | |
| } | |
| bool ImplementationBase::GetBucketContents(uint32_t bucket_id, | |
| std::vector<int8_t>* data) { | |
| TRACE_EVENT0("gpu", "ImplementationBase::GetBucketContents"); | |
| DCHECK(data); | |
| const uint32_t kStartSize = 32 * 1024; | |
| ScopedTransferBufferPtr buffer(kStartSize, helper_, transfer_buffer_); | |
| if (!buffer.valid()) { | |
| return false; | |
| } | |
| uint32_t size = 0; | |
| { | |
| // The Result pointer must be scoped to this block because it can be | |
| // invalidated below if resizing the ScopedTransferBufferPtr causes the | |
| // transfer buffer to be reallocated. | |
| typedef cmd::GetBucketStart::Result Result; | |
| auto result = GetResultAs<Result>(); | |
| if (!result) { | |
| return false; | |
| } | |
| *result = 0; | |
| helper_->GetBucketStart(bucket_id, GetResultShmId(), result.offset(), | |
| buffer.size(), buffer.shm_id(), buffer.offset()); | |
| WaitForCmd(); | |
| size = *result; | |
| } | |
| data->resize(size); | |
| if (size > 0u) { | |
| uint32_t offset = 0; | |
| while (size) { | |
| if (!buffer.valid()) { | |
| buffer.Reset(size); | |
| if (!buffer.valid()) { | |
| return false; | |
| } | |
| helper_->GetBucketData(bucket_id, offset, buffer.size(), | |
| buffer.shm_id(), buffer.offset()); | |
| if (!WaitForCmd()) { | |
| data->clear(); | |
| return false; | |
| } | |
| } | |
| uint32_t size_to_copy = std::min(size, buffer.size()); | |
| // SAFETY: `size_to_copy` is bounded by both remaining destination bytes | |
| // (`size`) and available source bytes (`buffer.size()`), and `offset` | |
| // tracks bytes already copied into `data`. | |
| base::as_writable_bytes(base::span(*data).subspan(offset, size_to_copy)) | |
| .copy_from(buffer.as_byte_span().first(size_to_copy)); | |
| offset += size_to_copy; | |
| size -= size_to_copy; | |
| buffer.Release(); | |
| } | |
| // Free the bucket. This is not required but it does free up the memory. | |
| // and we don't have to wait for the result so from the client's perspective | |
| // it's cheap. | |
| helper_->SetBucketSize(bucket_id, 0); | |
| } | |
| return true; | |
| } | |
| void ImplementationBase::SetBucketContents(uint32_t bucket_id, | |
| const void* data, | |
| uint32_t size) { | |
| DCHECK(data); | |
| helper_->SetBucketSize(bucket_id, size); | |
| if (size > 0u) { | |
| uint32_t offset = 0; | |
| while (size) { | |
| ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_); | |
| if (!buffer.valid()) { | |
| return; | |
| } | |
| auto span = UNSAFE_TODO(base::span( | |
| static_cast<const uint8_t*>(data) + offset, buffer.size())); | |
| buffer.as_byte_span().copy_from(span); | |
| helper_->SetBucketData(bucket_id, offset, buffer.size(), buffer.shm_id(), | |
| buffer.offset()); | |
| offset += buffer.size(); | |
| size -= buffer.size(); | |
| } | |
| } | |
| } | |
| void ImplementationBase::SetBucketAsCString(uint32_t bucket_id, | |
| const char* str) { | |
| // NOTE: strings are passed NULL terminated. That means the empty | |
| // string will have a size of 1 and no-string will have a size of 0 | |
| if (str) { | |
| base::CheckedNumeric<uint32_t> len = strlen(str); | |
| len += 1; | |
| SetBucketContents(bucket_id, str, len.ValueOrDefault(0)); | |
| } else { | |
| helper_->SetBucketSize(bucket_id, 0); | |
| } | |
| } | |
| bool ImplementationBase::GetBucketAsString(uint32_t bucket_id, | |
| std::string* str) { | |
| DCHECK(str); | |
| std::vector<int8_t> data; | |
| // NOTE: strings are passed NULL terminated. That means the empty | |
| // string will have a size of 1 and no-string will have a size of 0 | |
| if (!GetBucketContents(bucket_id, &data)) { | |
| return false; | |
| } | |
| if (data.empty()) { | |
| return false; | |
| } | |
| str->assign(data.begin(), data.end() - 1); | |
| return true; | |
| } | |
| void ImplementationBase::SetBucketAsString(uint32_t bucket_id, | |
| const std::string& str) { | |
| // NOTE: strings are passed NULL terminated. That means the empty | |
| // string will have a size of 1 and no-string will have a size of 0 | |
| base::CheckedNumeric<uint32_t> len = str.size(); | |
| len += 1; | |
| SetBucketContents(bucket_id, str.c_str(), len.ValueOrDefault(0)); | |
| } | |
| bool ImplementationBase::GetVerifiedSyncTokenForIPC( | |
| const SyncToken& sync_token, | |
| SyncToken* verified_sync_token) { | |
| DCHECK(sync_token.HasData()); | |
| DCHECK(verified_sync_token); | |
| if (!sync_token.verified_flush() && | |
| !gpu_control_->CanWaitUnverifiedSyncToken(sync_token)) | |
| return false; | |
| *verified_sync_token = sync_token; | |
| verified_sync_token->SetVerifyFlush(); | |
| return true; | |
| } | |
| void ImplementationBase::RunIfContextNotLost(base::OnceClosure callback) { | |
| if (!lost_context_callback_run_) { | |
| std::move(callback).Run(); | |
| } | |
| } | |
| void RelaxedAtomicWriteMemcpyImageRowsSkippingPadding(base::span<uint8_t> dst, | |
| base::span<uint8_t> src, | |
| size_t row_bytes, | |
| size_t height, | |
| size_t dst_stride, | |
| size_t src_stride) { | |
| // We need to use `RelaxedAtomicWriteMemcpy` because we might be writing into | |
| // memory observed by JS at the same time. | |
| if (row_bytes == dst_stride && row_bytes == src_stride) { | |
| base::subtle::RelaxedAtomicWriteMemcpy(dst, src); | |
| } else { | |
| // If there's padding between rows, copy each row separately to leave | |
| // padding untouched | |
| for (size_t y = 0; y < height; ++y) { | |
| base::subtle::RelaxedAtomicWriteMemcpy( | |
| dst.subspan(y * dst_stride, row_bytes), | |
| src.subspan(y * src_stride, row_bytes)); | |
| } | |
| } | |
| } | |
| } // namespace gpu |