| // Copyright (c) 2013 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 "services/ws/public/cpp/gpu/context_provider_command_buffer.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/optional.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/memory_dump_manager.h" |
| #include "components/viz/common/gpu/context_cache_controller.h" |
| #include "gpu/command_buffer/client/gles2_cmd_helper.h" |
| #include "gpu/command_buffer/client/gles2_trace_implementation.h" |
| #include "gpu/command_buffer/client/gpu_switches.h" |
| #include "gpu/command_buffer/client/implementation_base.h" |
| #include "gpu/command_buffer/client/raster_cmd_helper.h" |
| #include "gpu/command_buffer/client/raster_implementation.h" |
| #include "gpu/command_buffer/client/raster_implementation_gles.h" |
| #include "gpu/command_buffer/client/transfer_buffer.h" |
| #include "gpu/command_buffer/client/webgpu_cmd_helper.h" |
| #include "gpu/command_buffer/client/webgpu_implementation.h" |
| #include "gpu/command_buffer/common/constants.h" |
| #include "gpu/command_buffer/common/skia_utils.h" |
| #include "gpu/ipc/client/command_buffer_proxy_impl.h" |
| #include "gpu/ipc/client/gpu_channel_host.h" |
| #include "gpu/skia_bindings/gles2_implementation_with_grcontext_support.h" |
| #include "gpu/skia_bindings/grcontext_for_gles2_interface.h" |
| #include "services/ws/public/cpp/gpu/command_buffer_metrics.h" |
| #include "third_party/skia/include/core/SkTraceMemoryDump.h" |
| #include "third_party/skia/include/gpu/GrContext.h" |
| #include "ui/gl/trace_util.h" |
| |
| class SkDiscardableMemory; |
| |
| namespace ws { |
| |
| ContextProviderCommandBuffer::ContextProviderCommandBuffer( |
| scoped_refptr<gpu::GpuChannelHost> channel, |
| gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager, |
| int32_t stream_id, |
| gpu::SchedulingPriority stream_priority, |
| gpu::SurfaceHandle surface_handle, |
| const GURL& active_url, |
| bool automatic_flushes, |
| bool support_locking, |
| bool support_grcontext, |
| const gpu::SharedMemoryLimits& memory_limits, |
| const gpu::ContextCreationAttribs& attributes, |
| command_buffer_metrics::ContextType type) |
| : stream_id_(stream_id), |
| stream_priority_(stream_priority), |
| surface_handle_(surface_handle), |
| active_url_(active_url), |
| automatic_flushes_(automatic_flushes), |
| support_locking_(support_locking), |
| support_grcontext_(support_grcontext), |
| memory_limits_(memory_limits), |
| attributes_(attributes), |
| context_type_(type), |
| channel_(std::move(channel)), |
| gpu_memory_buffer_manager_(gpu_memory_buffer_manager), |
| impl_(nullptr) { |
| DCHECK(main_thread_checker_.CalledOnValidThread()); |
| DCHECK(channel_); |
| context_thread_checker_.DetachFromThread(); |
| } |
| |
| ContextProviderCommandBuffer::~ContextProviderCommandBuffer() { |
| DCHECK(main_thread_checker_.CalledOnValidThread() || |
| context_thread_checker_.CalledOnValidThread()); |
| |
| if (bind_tried_ && bind_result_ == gpu::ContextResult::kSuccess) { |
| // Clear the lock to avoid DCHECKs that the lock is being held during |
| // shutdown. |
| command_buffer_->SetLock(nullptr); |
| // Disconnect lost callbacks during destruction. |
| impl_->SetLostContextCallback(base::OnceClosure()); |
| // Unregister memory dump provider. |
| base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
| this); |
| } |
| } |
| |
| gpu::CommandBufferProxyImpl* |
| ContextProviderCommandBuffer::GetCommandBufferProxy() { |
| return command_buffer_.get(); |
| } |
| |
| uint32_t ContextProviderCommandBuffer::GetCopyTextureInternalFormat() { |
| if (attributes_.alpha_size > 0) |
| return GL_RGBA; |
| DCHECK_NE(attributes_.red_size, 0); |
| DCHECK_NE(attributes_.green_size, 0); |
| DCHECK_NE(attributes_.blue_size, 0); |
| return GL_RGB; |
| } |
| |
| void ContextProviderCommandBuffer::AddRef() const { |
| base::RefCountedThreadSafe<ContextProviderCommandBuffer>::AddRef(); |
| } |
| |
| void ContextProviderCommandBuffer::Release() const { |
| base::RefCountedThreadSafe<ContextProviderCommandBuffer>::Release(); |
| } |
| |
| gpu::ContextResult ContextProviderCommandBuffer::BindToCurrentThread() { |
| // This is called on the thread the context will be used. |
| DCHECK(context_thread_checker_.CalledOnValidThread()); |
| |
| if (bind_tried_) |
| return bind_result_; |
| |
| bind_tried_ = true; |
| // Any early-out should set this to a failure code and return it. |
| bind_result_ = gpu::ContextResult::kSuccess; |
| |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
| default_task_runner_; |
| if (!task_runner) |
| task_runner = base::ThreadTaskRunnerHandle::Get(); |
| |
| // This command buffer is a client-side proxy to the command buffer in the |
| // GPU process. |
| command_buffer_ = std::make_unique<gpu::CommandBufferProxyImpl>( |
| std::move(channel_), gpu_memory_buffer_manager_, stream_id_, task_runner); |
| bind_result_ = command_buffer_->Initialize( |
| surface_handle_, /*shared_command_buffer=*/nullptr, stream_priority_, |
| attributes_, active_url_); |
| if (bind_result_ != gpu::ContextResult::kSuccess) { |
| DLOG(ERROR) << "GpuChannelHost failed to create command buffer."; |
| command_buffer_metrics::UmaRecordContextInitFailed(context_type_); |
| return bind_result_; |
| } |
| |
| if (attributes_.context_type == gpu::CONTEXT_TYPE_WEBGPU) { |
| DCHECK(!attributes_.enable_raster_interface); |
| DCHECK(!attributes_.enable_gles2_interface); |
| |
| auto webgpu_helper = |
| std::make_unique<gpu::webgpu::WebGPUCmdHelper>(command_buffer_.get()); |
| webgpu_helper->SetAutomaticFlushes(automatic_flushes_); |
| bind_result_ = |
| webgpu_helper->Initialize(memory_limits_.command_buffer_size); |
| if (bind_result_ != gpu::ContextResult::kSuccess) { |
| DLOG(ERROR) << "Failed to initialize WebGPUCmdHelper."; |
| return bind_result_; |
| } |
| |
| // The WebGPUImplementation exposes the WebGPUInterface, as well as the |
| // gpu::ContextSupport interface. |
| auto webgpu_impl = std::make_unique<gpu::webgpu::WebGPUImplementation>( |
| webgpu_helper.get()); |
| |
| std::string type_name = |
| command_buffer_metrics::ContextTypeToString(context_type_); |
| std::string unique_context_name = |
| base::StringPrintf("%s-%p", type_name.c_str(), webgpu_impl.get()); |
| |
| impl_ = nullptr; |
| webgpu_interface_ = std::move(webgpu_impl); |
| helper_ = std::move(webgpu_helper); |
| } else if (attributes_.enable_oop_rasterization) { |
| DCHECK(attributes_.enable_raster_interface); |
| DCHECK(!attributes_.enable_gles2_interface); |
| DCHECK(!support_grcontext_); |
| // The raster helper writes the command buffer protocol. |
| auto raster_helper = |
| std::make_unique<gpu::raster::RasterCmdHelper>(command_buffer_.get()); |
| raster_helper->SetAutomaticFlushes(automatic_flushes_); |
| bind_result_ = |
| raster_helper->Initialize(memory_limits_.command_buffer_size); |
| if (bind_result_ != gpu::ContextResult::kSuccess) { |
| DLOG(ERROR) << "Failed to initialize RasterCmdHelper."; |
| return bind_result_; |
| } |
| // The transfer buffer is used to copy resources between the client |
| // process and the GPU process. |
| transfer_buffer_ = |
| std::make_unique<gpu::TransferBuffer>(raster_helper.get()); |
| |
| // The RasterImplementation exposes the RasterInterface, as well as the |
| // gpu::ContextSupport interface. |
| auto raster_impl = std::make_unique<gpu::raster::RasterImplementation>( |
| raster_helper.get(), transfer_buffer_.get(), |
| attributes_.bind_generates_resource, |
| attributes_.lose_context_when_out_of_memory, command_buffer_.get()); |
| bind_result_ = raster_impl->Initialize(memory_limits_); |
| if (bind_result_ != gpu::ContextResult::kSuccess) { |
| DLOG(ERROR) << "Failed to initialize RasterImplementation."; |
| return bind_result_; |
| } |
| |
| std::string type_name = |
| command_buffer_metrics::ContextTypeToString(context_type_); |
| std::string unique_context_name = |
| base::StringPrintf("%s-%p", type_name.c_str(), raster_impl.get()); |
| raster_impl->TraceBeginCHROMIUM("gpu_toplevel", |
| unique_context_name.c_str()); |
| |
| impl_ = raster_impl.get(); |
| raster_interface_ = std::move(raster_impl); |
| helper_ = std::move(raster_helper); |
| } else { |
| // The GLES2 helper writes the command buffer protocol. |
| auto gles2_helper = |
| std::make_unique<gpu::gles2::GLES2CmdHelper>(command_buffer_.get()); |
| gles2_helper->SetAutomaticFlushes(automatic_flushes_); |
| bind_result_ = gles2_helper->Initialize(memory_limits_.command_buffer_size); |
| if (bind_result_ != gpu::ContextResult::kSuccess) { |
| DLOG(ERROR) << "Failed to initialize GLES2CmdHelper."; |
| return bind_result_; |
| } |
| |
| // The transfer buffer is used to copy resources between the client |
| // process and the GPU process. |
| transfer_buffer_ = |
| std::make_unique<gpu::TransferBuffer>(gles2_helper.get()); |
| |
| // The GLES2Implementation exposes the OpenGLES2 API, as well as the |
| // gpu::ContextSupport interface. |
| constexpr bool support_client_side_arrays = false; |
| |
| std::unique_ptr<gpu::gles2::GLES2Implementation> gles2_impl; |
| if (support_grcontext_) { |
| // GLES2ImplementationWithGrContextSupport adds a bit of overhead, so |
| // we only use it if grcontext_support was requested. |
| gles2_impl = std::make_unique< |
| skia_bindings::GLES2ImplementationWithGrContextSupport>( |
| gles2_helper.get(), /*share_group=*/nullptr, transfer_buffer_.get(), |
| attributes_.bind_generates_resource, |
| attributes_.lose_context_when_out_of_memory, |
| support_client_side_arrays, command_buffer_.get()); |
| } else { |
| gles2_impl = std::make_unique<gpu::gles2::GLES2Implementation>( |
| gles2_helper.get(), /*share_group=*/nullptr, transfer_buffer_.get(), |
| attributes_.bind_generates_resource, |
| attributes_.lose_context_when_out_of_memory, |
| support_client_side_arrays, command_buffer_.get()); |
| } |
| bind_result_ = gles2_impl->Initialize(memory_limits_); |
| if (bind_result_ != gpu::ContextResult::kSuccess) { |
| DLOG(ERROR) << "Failed to initialize GLES2Implementation."; |
| return bind_result_; |
| } |
| |
| impl_ = gles2_impl.get(); |
| gles2_impl_ = std::move(gles2_impl); |
| helper_ = std::move(gles2_helper); |
| } |
| |
| if (command_buffer_->GetLastState().error != gpu::error::kNoError) { |
| // The context was DOA, which can be caused by other contexts and we |
| // could try again. |
| LOG(ERROR) << "ContextResult::kTransientFailure: " |
| "Context dead on arrival. Last error: " |
| << command_buffer_->GetLastState().error; |
| bind_result_ = gpu::ContextResult::kTransientFailure; |
| return bind_result_; |
| } |
| |
| cache_controller_ = |
| std::make_unique<viz::ContextCacheController>(impl_, task_runner); |
| |
| // TODO(crbug.com/868192): SetLostContextCallback should probably work on |
| // WebGPU contexts too. |
| if (impl_) { |
| impl_->SetLostContextCallback( |
| base::BindOnce(&ContextProviderCommandBuffer::OnLostContext, |
| // |this| owns the impl_, which holds the callback. |
| base::Unretained(this))); |
| } |
| |
| if (gles2_impl_) { |
| // Grab the implementation directly instead of going through ContextGL() |
| // because the lock hasn't been acquired yet. |
| gpu::gles2::GLES2Interface* gl = gles2_impl_.get(); |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableGpuClientTracing)) { |
| // This wraps the real GLES2Implementation and we should always use this |
| // instead when it's present. |
| trace_impl_ = std::make_unique<gpu::gles2::GLES2TraceImplementation>( |
| gles2_impl_.get()); |
| gl = trace_impl_.get(); |
| } |
| |
| // Do this last once the context is set up. |
| std::string type_name = |
| command_buffer_metrics::ContextTypeToString(context_type_); |
| std::string unique_context_name = |
| base::StringPrintf("%s-%p", type_name.c_str(), gles2_impl_.get()); |
| gl->TraceBeginCHROMIUM("gpu_toplevel", unique_context_name.c_str()); |
| } |
| |
| // If support_locking_ is true, the context may be used from multiple |
| // threads, and any async callstacks will need to hold the same lock, so |
| // give it to the command buffer and cache controller. |
| // We don't hold a lock here since there's no need, so set the lock very last |
| // to prevent asserts that we're not holding it. |
| if (support_locking_) { |
| command_buffer_->SetLock(&context_lock_); |
| cache_controller_->SetLock(&context_lock_); |
| } |
| base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| this, "ContextProviderCommandBuffer", std::move(task_runner)); |
| return bind_result_; |
| } |
| |
| gpu::gles2::GLES2Interface* ContextProviderCommandBuffer::ContextGL() { |
| DCHECK(bind_tried_); |
| DCHECK_EQ(bind_result_, gpu::ContextResult::kSuccess); |
| CheckValidThreadOrLockAcquired(); |
| |
| if (!attributes_.enable_gles2_interface) { |
| DLOG(ERROR) << "Unexpected access to ContextGL()"; |
| return nullptr; |
| } |
| |
| if (trace_impl_) |
| return trace_impl_.get(); |
| return gles2_impl_.get(); |
| } |
| |
| gpu::raster::RasterInterface* ContextProviderCommandBuffer::RasterInterface() { |
| DCHECK(bind_tried_); |
| DCHECK_EQ(bind_result_, gpu::ContextResult::kSuccess); |
| CheckValidThreadOrLockAcquired(); |
| |
| if (raster_interface_) |
| return raster_interface_.get(); |
| |
| if (!attributes_.enable_raster_interface) { |
| DLOG(ERROR) << "Unexpected access to RasterInterface()"; |
| return nullptr; |
| } |
| |
| if (!gles2_impl_.get()) |
| return nullptr; |
| |
| raster_interface_ = std::make_unique<gpu::raster::RasterImplementationGLES>( |
| gles2_impl_.get(), command_buffer_.get(), ContextCapabilities()); |
| return raster_interface_.get(); |
| } |
| |
| gpu::ContextSupport* ContextProviderCommandBuffer::ContextSupport() { |
| return impl_; |
| } |
| |
| class GrContext* ContextProviderCommandBuffer::GrContext() { |
| DCHECK(bind_tried_); |
| DCHECK_EQ(bind_result_, gpu::ContextResult::kSuccess); |
| DCHECK(support_grcontext_); |
| DCHECK(ContextSupport()->HasGrContextSupport()); |
| CheckValidThreadOrLockAcquired(); |
| |
| if (gr_context_) |
| return gr_context_->get(); |
| |
| if (attributes_.enable_oop_rasterization) |
| return nullptr; |
| |
| // TODO(vmiura): crbug.com/793508 Disable access to GrContext if |
| // enable_gles2_interface is disabled, after removing any dependencies on |
| // GrContext in OOP-Raster. |
| |
| size_t max_resource_cache_bytes; |
| size_t max_glyph_cache_texture_bytes; |
| gpu::raster::DetermineGrCacheLimitsFromAvailableMemory( |
| &max_resource_cache_bytes, &max_glyph_cache_texture_bytes); |
| |
| gpu::gles2::GLES2Interface* gl_interface; |
| if (trace_impl_) |
| gl_interface = trace_impl_.get(); |
| else |
| gl_interface = gles2_impl_.get(); |
| |
| gr_context_.reset(new skia_bindings::GrContextForGLES2Interface( |
| gl_interface, ContextSupport(), ContextCapabilities(), |
| max_resource_cache_bytes, max_glyph_cache_texture_bytes)); |
| cache_controller_->SetGrContext(gr_context_->get()); |
| |
| // If GlContext is already lost, also abandon the new GrContext. |
| if (gr_context_->get() && |
| gles2_impl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) |
| gr_context_->get()->abandonContext(); |
| |
| return gr_context_->get(); |
| } |
| |
| viz::ContextCacheController* ContextProviderCommandBuffer::CacheController() { |
| CheckValidThreadOrLockAcquired(); |
| return cache_controller_.get(); |
| } |
| |
| void ContextProviderCommandBuffer::SetDefaultTaskRunner( |
| scoped_refptr<base::SingleThreadTaskRunner> default_task_runner) { |
| DCHECK(!bind_tried_); |
| default_task_runner_ = std::move(default_task_runner); |
| } |
| |
| base::Lock* ContextProviderCommandBuffer::GetLock() { |
| if (!support_locking_) |
| return nullptr; |
| return &context_lock_; |
| } |
| |
| const gpu::Capabilities& ContextProviderCommandBuffer::ContextCapabilities() |
| const { |
| DCHECK(bind_tried_); |
| DCHECK_EQ(bind_result_, gpu::ContextResult::kSuccess); |
| CheckValidThreadOrLockAcquired(); |
| // Skips past the trace_impl_ as it doesn't have capabilities. |
| return impl_->capabilities(); |
| } |
| |
| const gpu::GpuFeatureInfo& ContextProviderCommandBuffer::GetGpuFeatureInfo() |
| const { |
| DCHECK(bind_tried_); |
| DCHECK_EQ(bind_result_, gpu::ContextResult::kSuccess); |
| CheckValidThreadOrLockAcquired(); |
| if (!command_buffer_ || !command_buffer_->channel()) { |
| static const gpu::GpuFeatureInfo default_gpu_feature_info; |
| return default_gpu_feature_info; |
| } |
| return command_buffer_->channel()->gpu_feature_info(); |
| } |
| |
| void ContextProviderCommandBuffer::OnLostContext() { |
| CheckValidThreadOrLockAcquired(); |
| |
| for (auto& observer : observers_) |
| observer.OnContextLost(); |
| if (gr_context_) |
| gr_context_->OnLostContext(); |
| |
| gpu::CommandBuffer::State state = GetCommandBufferProxy()->GetLastState(); |
| command_buffer_metrics::UmaRecordContextLost(context_type_, state.error, |
| state.context_lost_reason); |
| } |
| |
| void ContextProviderCommandBuffer::AddObserver(viz::ContextLostObserver* obs) { |
| CheckValidThreadOrLockAcquired(); |
| observers_.AddObserver(obs); |
| } |
| |
| void ContextProviderCommandBuffer::RemoveObserver( |
| viz::ContextLostObserver* obs) { |
| CheckValidThreadOrLockAcquired(); |
| observers_.RemoveObserver(obs); |
| } |
| |
| gpu::webgpu::WebGPUInterface* ContextProviderCommandBuffer::WebGPUInterface() { |
| DCHECK(bind_tried_); |
| DCHECK_EQ(bind_result_, gpu::ContextResult::kSuccess); |
| CheckValidThreadOrLockAcquired(); |
| |
| return webgpu_interface_.get(); |
| } |
| |
| bool ContextProviderCommandBuffer::OnMemoryDump( |
| const base::trace_event::MemoryDumpArgs& args, |
| base::trace_event::ProcessMemoryDump* pmd) { |
| DCHECK(bind_tried_); |
| DCHECK_EQ(bind_result_, gpu::ContextResult::kSuccess); |
| |
| base::Optional<base::AutoLock> hold; |
| if (support_locking_) |
| hold.emplace(context_lock_); |
| |
| impl_->OnMemoryDump(args, pmd); |
| helper_->OnMemoryDump(args, pmd); |
| |
| if (gr_context_) { |
| context_thread_checker_.DetachFromThread(); |
| gpu::raster::DumpGrMemoryStatistics(gr_context_->get(), pmd, |
| gles2_impl_->ShareGroupTracingGUID()); |
| context_thread_checker_.DetachFromThread(); |
| } |
| return true; |
| } |
| |
| } // namespace ws |