| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/viz/public/cpp/gpu/gpu.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "gpu/config/gpu_finch_features.h" |
| #include "gpu/ipc/common/client_gmb_interface.mojom.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "services/viz/public/cpp/gpu/client_gpu_memory_buffer_manager.h" |
| #include "services/viz/public/mojom/gpu.mojom.h" |
| |
| namespace viz { |
| |
| // Encapsulates a mojo::Remote<mojom::Gpu> object that will be used on the IO |
| // thread. This is required because we can't install an error handler on a |
| // mojo::SharedRemote<mojom::Gpu> to detect if the message pipe was closed. Only |
| // the constructor can be called on the main thread. |
| class Gpu::GpuPtrIO { |
| public: |
| GpuPtrIO() { DETACH_FROM_THREAD(thread_checker_); } |
| |
| GpuPtrIO(const GpuPtrIO&) = delete; |
| GpuPtrIO& operator=(const GpuPtrIO&) = delete; |
| |
| ~GpuPtrIO() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); } |
| |
| void Initialize(mojo::PendingRemote<mojom::Gpu> gpu_remote, |
| mojo::PendingReceiver<mojom::GpuMemoryBufferFactory> |
| memory_buffer_factory_receiver, |
| mojo::PendingReceiver<gpu::mojom::ClientGmbInterface> |
| client_gmb_interface_receiver) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| gpu_remote_.Bind(std::move(gpu_remote)); |
| gpu_remote_.set_disconnect_handler( |
| base::BindOnce(&GpuPtrIO::ConnectionError, base::Unretained(this))); |
| gpu_remote_->CreateGpuMemoryBufferFactory( |
| std::move(memory_buffer_factory_receiver)); |
| if (base::FeatureList::IsEnabled(features::kUseClientGmbInterface)) { |
| gpu_remote_->CreateClientGpuMemoryBufferFactory( |
| std::move(client_gmb_interface_receiver)); |
| } |
| } |
| |
| void EstablishGpuChannel(scoped_refptr<EstablishRequest> establish_request) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!establish_request_); |
| establish_request_ = std::move(establish_request); |
| |
| if (gpu_remote_.is_connected()) { |
| gpu_remote_->EstablishGpuChannel(base::BindOnce( |
| &GpuPtrIO::OnEstablishedGpuChannel, base::Unretained(this))); |
| } else { |
| ConnectionError(); |
| } |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| void CreateJpegDecodeAccelerator( |
| mojo::PendingReceiver<chromeos_camera::mojom::MjpegDecodeAccelerator> |
| receiver) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| gpu_remote_->CreateJpegDecodeAccelerator(std::move(receiver)); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| void CreateVideoEncodeAcceleratorProvider( |
| mojo::PendingReceiver<media::mojom::VideoEncodeAcceleratorProvider> |
| receiver) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| gpu_remote_->CreateVideoEncodeAcceleratorProvider(std::move(receiver)); |
| } |
| |
| private: |
| void ConnectionError(); |
| void OnEstablishedGpuChannel(int client_id, |
| mojo::ScopedMessagePipeHandle channel_handle, |
| const gpu::GPUInfo& gpu_info, |
| const gpu::GpuFeatureInfo& gpu_feature_info); |
| |
| mojo::Remote<mojom::Gpu> gpu_remote_; |
| |
| // This will point to a request that is waiting for the result of |
| // EstablishGpuChannel(). |establish_request_| will be notified when the IPC |
| // callback fires or if an interface connection error occurs. |
| scoped_refptr<EstablishRequest> establish_request_; |
| THREAD_CHECKER(thread_checker_); |
| }; |
| |
| // Encapsulates a single request to establish a GPU channel. |
| class Gpu::EstablishRequest |
| : public base::RefCountedThreadSafe<Gpu::EstablishRequest> { |
| public: |
| EstablishRequest(Gpu* parent, |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner) |
| : parent_(parent), main_task_runner_(main_task_runner) {} |
| |
| EstablishRequest(const EstablishRequest&) = delete; |
| EstablishRequest& operator=(const EstablishRequest&) = delete; |
| |
| const scoped_refptr<gpu::GpuChannelHost>& gpu_channel() { |
| return gpu_channel_; |
| } |
| |
| // Sends EstablishGpuChannel() request using |gpu|. This must be called from |
| // the IO thread so that the response is handled on the IO thread. |
| void SendRequest(GpuPtrIO* gpu) { |
| DCHECK(!main_task_runner_->BelongsToCurrentThread()); |
| { |
| base::AutoLock lock(lock_); |
| if (finished_) |
| return; |
| } |
| |
| gpu->EstablishGpuChannel(this); |
| } |
| |
| // Sets a WaitableEvent so the main thread can block for a synchronous |
| // request. This must be called from main thread. |
| void SetWaitableEvent(base::WaitableEvent* establish_event) { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| DCHECK(establish_event); |
| base::AutoLock mutex(lock_); |
| DCHECK(!establish_event_); |
| |
| // If we've already received a response then don't reset |establish_event|. |
| // The caller won't block and will immediately process the response. |
| if (received_) |
| return; |
| |
| establish_event_ = establish_event; |
| establish_event_->Reset(); |
| } |
| |
| // Cancels the pending request. Any asynchronous calls back into this object |
| // will return early and do nothing. This must be called from main thread. |
| void Cancel() { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| base::AutoLock lock(lock_); |
| DCHECK(!finished_); |
| finished_ = true; |
| } |
| |
| // This must be called after OnEstablishedGpuChannel() from the main thread. |
| void FinishOnMain() { |
| // No lock needed, everything will run on |main_task_runner_| now. |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| DCHECK(received_); |
| |
| // It's possible to enter FinishedOnMain() twice if EstablishGpuChannel() is |
| // called, the request returns and schedules a task on |main_task_runner_|. |
| // If EstablishGpuChannelSync() runs before the scheduled task then it will |
| // enter FinishedOnMain() immediately and finish. The scheduled task will |
| // run later and return early here, doing nothing. |
| if (finished_) |
| return; |
| |
| finished_ = true; |
| |
| // |this| might be deleted when running Gpu::OnEstablishedGpuChannel(). |
| parent_->OnEstablishedGpuChannel(); |
| } |
| |
| void OnEstablishedGpuChannel(int client_id, |
| mojo::ScopedMessagePipeHandle channel_handle, |
| const gpu::GPUInfo& gpu_info, |
| const gpu::GpuFeatureInfo& gpu_feature_info) { |
| DCHECK(!main_task_runner_->BelongsToCurrentThread()); |
| base::AutoLock lock(lock_); |
| |
| // Do nothing if Cancel() was called. |
| if (finished_) |
| return; |
| |
| DCHECK(!received_); |
| received_ = true; |
| if (channel_handle.is_valid()) { |
| gpu_channel_ = base::MakeRefCounted<gpu::GpuChannelHost>( |
| client_id, gpu_info, gpu_feature_info, std::move(channel_handle)); |
| } |
| |
| if (establish_event_) { |
| // Gpu::EstablishGpuChannelSync() was called. Unblock the main thread and |
| // let it finish. The main thread owns the event and may destroy it as |
| // soon as the thread is unblocked, so avoid dangling references to it. |
| establish_event_.ExtractAsDangling()->Signal(); |
| } else { |
| main_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&EstablishRequest::FinishOnMain, this)); |
| } |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<Gpu::EstablishRequest>; |
| |
| virtual ~EstablishRequest() = default; |
| |
| const raw_ptr<Gpu> parent_; |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| raw_ptr<base::WaitableEvent> establish_event_ = nullptr; |
| |
| base::Lock lock_; |
| bool received_ = false; |
| bool finished_ = false; |
| |
| scoped_refptr<gpu::GpuChannelHost> gpu_channel_; |
| }; |
| |
| void Gpu::GpuPtrIO::ConnectionError() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!establish_request_) |
| return; |
| |
| // Make sure |establish_request_| fails so the main thread doesn't block |
| // forever after calling Gpu::EstablishGpuChannelSync(). |
| establish_request_->OnEstablishedGpuChannel( |
| 0, mojo::ScopedMessagePipeHandle(), gpu::GPUInfo(), |
| gpu::GpuFeatureInfo()); |
| establish_request_.reset(); |
| } |
| |
| void Gpu::GpuPtrIO::OnEstablishedGpuChannel( |
| int client_id, |
| mojo::ScopedMessagePipeHandle channel_handle, |
| const gpu::GPUInfo& gpu_info, |
| const gpu::GpuFeatureInfo& gpu_feature_info) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(establish_request_); |
| |
| establish_request_->OnEstablishedGpuChannel( |
| client_id, std::move(channel_handle), std::move(gpu_info), |
| std::move(gpu_feature_info)); |
| establish_request_.reset(); |
| } |
| |
| Gpu::Gpu(mojo::PendingRemote<mojom::Gpu> gpu_remote, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) |
| : main_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()), |
| io_task_runner_(std::move(task_runner)), |
| gpu_(new GpuPtrIO(), base::OnTaskRunnerDeleter(io_task_runner_)) { |
| DCHECK(main_task_runner_); |
| DCHECK(io_task_runner_); |
| |
| mojo::PendingRemote<mojom::GpuMemoryBufferFactory> gpu_memory_buffer_factory; |
| auto gpu_memory_buffer_factory_receiver = |
| gpu_memory_buffer_factory.InitWithNewPipeAndPassReceiver(); |
| |
| mojo::PendingRemote<gpu::mojom::ClientGmbInterface> client_gmb_interface; |
| auto client_gmb_interface_receiver = |
| client_gmb_interface.InitWithNewPipeAndPassReceiver(); |
| |
| // Note that since |gpu_memory_buffer_manager_| is a owned by this object, it |
| // is safe to provide a |this| pointer to it. |
| gpu_memory_buffer_manager_ = std::make_unique<ClientGpuMemoryBufferManager>( |
| std::move(gpu_memory_buffer_factory), std::move(client_gmb_interface)); |
| // Initialize mojo::Remote<mojom::Gpu> on the IO thread. |gpu_| can only be |
| // used on the IO thread after this point. It is safe to use base::Unretained |
| // with |gpu_| for IO thread tasks as |gpu_| is destroyed by an IO thread task |
| // posted from the destructor. |
| io_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&GpuPtrIO::Initialize, base::Unretained(gpu_.get()), |
| std::move(gpu_remote), |
| std::move(gpu_memory_buffer_factory_receiver), |
| std::move(client_gmb_interface_receiver))); |
| } |
| |
| Gpu::~Gpu() { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| if (pending_request_) { |
| pending_request_->Cancel(); |
| pending_request_.reset(); |
| } |
| |
| if (gpu_channel_) |
| gpu_channel_->DestroyChannel(); |
| } |
| |
| // static |
| std::unique_ptr<Gpu> Gpu::Create( |
| service_manager::Connector* connector, |
| const std::string& service_name, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| mojo::PendingRemote<mojom::Gpu> remote; |
| connector->Connect(service_name, remote.InitWithNewPipeAndPassReceiver()); |
| return Create(std::move(remote), std::move(task_runner)); |
| } |
| |
| // static |
| std::unique_ptr<Gpu> Gpu::Create( |
| mojo::PendingRemote<mojom::Gpu> remote, |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) { |
| return base::WrapUnique( |
| new Gpu(std::move(remote), std::move(io_task_runner))); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| void Gpu::CreateJpegDecodeAccelerator( |
| mojo::PendingReceiver<chromeos_camera::mojom::MjpegDecodeAccelerator> |
| jda_receiver) { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| io_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&GpuPtrIO::CreateJpegDecodeAccelerator, |
| base::Unretained(gpu_.get()), std::move(jda_receiver))); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| void Gpu::CreateVideoEncodeAcceleratorProvider( |
| mojo::PendingReceiver<media::mojom::VideoEncodeAcceleratorProvider> |
| vea_provider_receiver) { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| io_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&GpuPtrIO::CreateVideoEncodeAcceleratorProvider, |
| base::Unretained(gpu_.get()), |
| std::move(vea_provider_receiver))); |
| } |
| |
| void Gpu::EstablishGpuChannel(gpu::GpuChannelEstablishedCallback callback) { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| scoped_refptr<gpu::GpuChannelHost> channel = GetGpuChannel(); |
| if (channel) { |
| std::move(callback).Run(std::move(channel)); |
| return; |
| } |
| |
| establish_callbacks_.push_back(std::move(callback)); |
| SendEstablishGpuChannelRequest(/*waitable_event=*/nullptr); |
| } |
| |
| scoped_refptr<gpu::GpuChannelHost> Gpu::EstablishGpuChannelSync() { |
| TRACE_EVENT0("gpu", "Gpu::EstablishGpuChannelSync"); |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| |
| scoped_refptr<gpu::GpuChannelHost> channel = GetGpuChannel(); |
| if (channel) |
| return channel; |
| |
| SCOPED_UMA_HISTOGRAM_TIMER("GPU.EstablishGpuChannelSyncTime"); |
| base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::SIGNALED); |
| SendEstablishGpuChannelRequest(&event); |
| event.Wait(); |
| |
| // Running FinishOnMain() will create |gpu_channel_| and run any callbacks |
| // from calls to EstablishGpuChannel() before we return from here. |
| pending_request_->FinishOnMain(); |
| |
| return gpu_channel_; |
| } |
| |
| gpu::GpuMemoryBufferManager* Gpu::GetGpuMemoryBufferManager() { |
| return gpu_memory_buffer_manager_.get(); |
| } |
| |
| void Gpu::LoseChannel() { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| if (gpu_channel_) { |
| gpu_channel_->DestroyChannel(); |
| gpu_channel_.reset(); |
| } |
| } |
| |
| scoped_refptr<gpu::GpuChannelHost> Gpu::GetGpuChannel() { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| if (gpu_channel_ && gpu_channel_->IsLost()) |
| gpu_channel_.reset(); |
| return gpu_channel_; |
| } |
| |
| void Gpu::SendEstablishGpuChannelRequest(base::WaitableEvent* waitable_event) { |
| if (pending_request_) { |
| if (waitable_event) { |
| pending_request_->SetWaitableEvent(waitable_event); |
| } |
| return; |
| } |
| |
| pending_request_ = |
| base::MakeRefCounted<EstablishRequest>(this, main_task_runner_); |
| if (waitable_event) { |
| pending_request_->SetWaitableEvent(waitable_event); |
| } |
| io_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&EstablishRequest::SendRequest, pending_request_, |
| base::Unretained(gpu_.get()))); |
| } |
| |
| void Gpu::OnEstablishedGpuChannel() { |
| DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| DCHECK(pending_request_); |
| DCHECK(!gpu_channel_); |
| |
| gpu_channel_ = pending_request_->gpu_channel(); |
| pending_request_.reset(); |
| |
| std::vector<gpu::GpuChannelEstablishedCallback> callbacks; |
| callbacks.swap(establish_callbacks_); |
| for (auto&& callback : std::move(callbacks)) |
| std::move(callback).Run(gpu_channel_); |
| } |
| |
| } // namespace viz |