| // Copyright 2019 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 "media/gpu/chromeos/mailbox_video_frame_converter.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/location.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/task/post_task.h" |
| #include "base/task_runner_util.h" |
| #include "base/trace_event/trace_event.h" |
| #include "gpu/command_buffer/common/shared_image_usage.h" |
| #include "gpu/command_buffer/service/scheduler.h" |
| #include "gpu/ipc/common/gpu_client_ids.h" |
| #include "gpu/ipc/service/gpu_channel.h" |
| #include "gpu/ipc/service/shared_image_stub.h" |
| #include "media/base/format_utils.h" |
| #include "media/base/video_frame.h" |
| #include "media/gpu/chromeos/platform_video_frame_utils.h" |
| #include "media/gpu/macros.h" |
| #include "ui/gfx/gpu_memory_buffer.h" |
| #include "ui/gl/gl_bindings.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| constexpr GLenum kTextureTarget = GL_TEXTURE_EXTERNAL_OES; |
| |
| } // anonymous namespace |
| |
| // A SharedImage wrapper that calls |destroy_shared_image_cb| in dtor on |
| // |gpu_task_runner|. |
| class MailboxVideoFrameConverter::ScopedSharedImage { |
| public: |
| using DestroySharedImageCB = |
| gpu::SharedImageStub::SharedImageDestructionCallback; |
| |
| ScopedSharedImage(scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner) |
| : destruction_task_runner_(std::move(gpu_task_runner)) {} |
| ~ScopedSharedImage() { Destroy(); } |
| |
| void Reset(const gpu::Mailbox& mailbox, |
| const gfx::Rect& rect, |
| DestroySharedImageCB destroy_shared_image_cb) { |
| Destroy(); |
| DCHECK(!mailbox.IsZero()); |
| mailbox_ = mailbox; |
| rect_ = rect; |
| destroy_shared_image_cb_ = std::move(destroy_shared_image_cb); |
| } |
| |
| bool HasData() const { return !mailbox_.IsZero(); } |
| const gpu::Mailbox& mailbox() const { return mailbox_; } |
| const gfx::Rect& rect() const { return rect_; } |
| |
| private: |
| void Destroy() { |
| if (destroy_shared_image_cb_) { |
| if (destruction_task_runner_->RunsTasksInCurrentSequence()) { |
| std::move(destroy_shared_image_cb_).Run(gpu::SyncToken()); |
| return; |
| } |
| destruction_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(std::move(destroy_shared_image_cb_), |
| gpu::SyncToken())); |
| } |
| } |
| |
| gpu::Mailbox mailbox_; |
| gfx::Rect rect_; |
| DestroySharedImageCB destroy_shared_image_cb_; |
| const scoped_refptr<base::SequencedTaskRunner> destruction_task_runner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedSharedImage); |
| }; |
| |
| // static |
| std::unique_ptr<VideoFrameConverter> MailboxVideoFrameConverter::Create( |
| UnwrapFrameCB unwrap_frame_cb, |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| GetCommandBufferStubCB get_stub_cb) { |
| if (!unwrap_frame_cb || !gpu_task_runner || !get_stub_cb) |
| return nullptr; |
| |
| auto get_gpu_channel_cb = base::BindRepeating( |
| [](base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb) { |
| gpu::CommandBufferStub* stub = get_stub_cb.Run(); |
| if (!stub) |
| return base::WeakPtr<gpu::GpuChannel>(); |
| DCHECK(stub->channel()); |
| return stub->channel()->AsWeakPtr(); |
| }, |
| get_stub_cb); |
| |
| return base::WrapUnique<VideoFrameConverter>(new MailboxVideoFrameConverter( |
| std::move(unwrap_frame_cb), std::move(gpu_task_runner), |
| get_gpu_channel_cb)); |
| } |
| |
| MailboxVideoFrameConverter::MailboxVideoFrameConverter( |
| UnwrapFrameCB unwrap_frame_cb, |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| GetGpuChannelCB get_gpu_channel_cb) |
| : unwrap_frame_cb_(std::move(unwrap_frame_cb)), |
| gpu_task_runner_(std::move(gpu_task_runner)), |
| get_gpu_channel_cb_(get_gpu_channel_cb) { |
| DVLOGF(2); |
| |
| parent_weak_this_ = parent_weak_this_factory_.GetWeakPtr(); |
| gpu_weak_this_ = gpu_weak_this_factory_.GetWeakPtr(); |
| } |
| |
| void MailboxVideoFrameConverter::Destroy() { |
| DCHECK(!parent_task_runner_ || |
| parent_task_runner_->RunsTasksInCurrentSequence()); |
| DVLOGF(2); |
| |
| parent_weak_this_factory_.InvalidateWeakPtrs(); |
| gpu_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MailboxVideoFrameConverter::DestroyOnGPUThread, |
| gpu_weak_this_)); |
| } |
| |
| void MailboxVideoFrameConverter::DestroyOnGPUThread() { |
| DCHECK(gpu_task_runner_->RunsTasksInCurrentSequence()); |
| DVLOGF(2); |
| |
| gpu_weak_this_factory_.InvalidateWeakPtrs(); |
| delete this; |
| } |
| |
| MailboxVideoFrameConverter::~MailboxVideoFrameConverter() { |
| // |gpu_weak_this_factory_| is already invalidated here. |
| DCHECK(gpu_task_runner_->RunsTasksInCurrentSequence()); |
| DVLOGF(2); |
| } |
| |
| bool MailboxVideoFrameConverter::InitializeOnGPUThread() { |
| DVLOGF(4); |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| |
| // Use |gpu_channel_| as a marker that we have been initialized already. |
| if (gpu_channel_) |
| return true; |
| |
| gpu_channel_ = get_gpu_channel_cb_.Run(); |
| return !!gpu_channel_; |
| } |
| |
| void MailboxVideoFrameConverter::ConvertFrame(scoped_refptr<VideoFrame> frame) { |
| DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); |
| DVLOGF(4); |
| |
| if (!frame || frame->storage_type() != VideoFrame::STORAGE_GPU_MEMORY_BUFFER) |
| return OnError(FROM_HERE, "Invalid frame."); |
| |
| VideoFrame* origin_frame = unwrap_frame_cb_.Run(*frame); |
| if (!origin_frame) |
| return OnError(FROM_HERE, "Failed to get origin frame."); |
| |
| ScopedSharedImage* shared_image = nullptr; |
| const UniqueID origin_frame_id = origin_frame->unique_id(); |
| auto shared_image_it = shared_images_.find(origin_frame_id); |
| if (shared_image_it != shared_images_.end()) |
| shared_image = shared_image_it->second; |
| |
| input_frame_queue_.emplace(frame, origin_frame_id); |
| |
| // |frame| keeps a refptr of |origin_frame|. |origin_frame| is guaranteed |
| // alive by carrying |frame|. |origin_frame| owns the SharedImage, so as long |
| // as |frame| lives, |shared_image| is valid. Hence, it's safe to use |
| // base::Unretained here. |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MailboxVideoFrameConverter::ConvertFrameOnGPUThread, |
| gpu_weak_this_, base::Unretained(origin_frame), |
| std::move(frame), base::Unretained(shared_image))); |
| } |
| |
| void MailboxVideoFrameConverter::WrapMailboxAndVideoFrameAndOutput( |
| VideoFrame* origin_frame, |
| scoped_refptr<VideoFrame> frame, |
| const gpu::Mailbox& mailbox) { |
| DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(!mailbox.IsZero()); |
| |
| const UniqueID origin_frame_id = origin_frame->unique_id(); |
| DCHECK(base::Contains(shared_images_, origin_frame_id)); |
| |
| // While we were on |gpu_task_runner_|, AbortPendingFrames() might have been |
| // called and/or possibly different frames enqueued in |input_frame_queue_|. |
| if (input_frame_queue_.empty()) |
| return; |
| if (input_frame_queue_.front().second != origin_frame_id) |
| return; |
| input_frame_queue_.pop(); |
| |
| gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes]; |
| mailbox_holders[0] = |
| gpu::MailboxHolder(mailbox, gpu::SyncToken(), kTextureTarget); |
| |
| VideoFrame::ReleaseMailboxCB release_mailbox_cb = base::BindOnce( |
| [](scoped_refptr<base::SequencedTaskRunner> gpu_task_runner, |
| base::WeakPtr<MailboxVideoFrameConverter> gpu_weak_ptr, |
| scoped_refptr<VideoFrame> frame, const gpu::SyncToken& sync_token) { |
| if (gpu_task_runner->RunsTasksInCurrentSequence()) { |
| if (gpu_weak_ptr) { |
| gpu_weak_ptr->WaitOnSyncTokenAndReleaseFrameOnGPUThread( |
| std::move(frame), sync_token); |
| } |
| return; |
| } |
| gpu_task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MailboxVideoFrameConverter:: |
| WaitOnSyncTokenAndReleaseFrameOnGPUThread, |
| gpu_weak_ptr, std::move(frame), sync_token)); |
| }, |
| gpu_task_runner_, gpu_weak_this_, frame); |
| |
| scoped_refptr<VideoFrame> mailbox_frame = VideoFrame::WrapNativeTextures( |
| frame->format(), mailbox_holders, std::move(release_mailbox_cb), |
| frame->coded_size(), frame->visible_rect(), frame->natural_size(), |
| frame->timestamp()); |
| mailbox_frame->set_color_space(frame->ColorSpace()); |
| mailbox_frame->set_metadata(*(frame->metadata())); |
| mailbox_frame->metadata()->read_lock_fences_enabled = true; |
| |
| output_cb_.Run(mailbox_frame); |
| } |
| |
| void MailboxVideoFrameConverter::ConvertFrameOnGPUThread( |
| VideoFrame* origin_frame, |
| scoped_refptr<VideoFrame> frame, |
| ScopedSharedImage* stored_shared_image) { |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| TRACE_EVENT1("media,gpu", "ConvertFrameOnGPUThread", "VideoFrame id", |
| origin_frame->unique_id()); |
| const gfx::Rect visible_rect = frame->visible_rect(); |
| |
| // |origin_frame| is kept alive by |frame|. |
| auto wrap_mailbox_and_video_frame_and_output_cb = base::BindOnce( |
| &MailboxVideoFrameConverter::WrapMailboxAndVideoFrameAndOutput, |
| parent_weak_this_, base::Unretained(origin_frame), std::move(frame)); |
| |
| // If there's a |stored_shared_image| associated with |origin_frame|, update |
| // it and call the continuation callback, otherwise create a SharedImage and |
| // register it. |
| if (stored_shared_image) { |
| DCHECK(!stored_shared_image->mailbox().IsZero()); |
| bool res; |
| if (stored_shared_image->rect() == visible_rect) { |
| res = UpdateSharedImageOnGPUThread(stored_shared_image->mailbox()); |
| } else { |
| // The visible rectangle changed, so we need to recreate the SharedImage |
| // with the new rectangle. |
| res = GenerateSharedImageOnGPUThread(origin_frame, visible_rect, |
| stored_shared_image); |
| } |
| if (res) { |
| DCHECK(stored_shared_image->HasData()); |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(wrap_mailbox_and_video_frame_and_output_cb), |
| stored_shared_image->mailbox())); |
| } |
| return; |
| } |
| |
| // There was no existing SharedImage: create a new one. |
| auto new_shared_image = std::make_unique<ScopedSharedImage>(gpu_task_runner_); |
| if (!GenerateSharedImageOnGPUThread(origin_frame, visible_rect, |
| new_shared_image.get())) { |
| return; |
| } |
| DCHECK(new_shared_image->HasData()); |
| |
| const gpu::Mailbox mailbox = new_shared_image->mailbox(); |
| // |origin_frame| is kept alive by |frame| in |
| // |wrap_mailbox_and_video_frame_and_output_cb|. |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MailboxVideoFrameConverter::RegisterSharedImage, |
| parent_weak_this_, base::Unretained(origin_frame), |
| std::move(new_shared_image))); |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(wrap_mailbox_and_video_frame_and_output_cb), |
| mailbox)); |
| } |
| |
| bool MailboxVideoFrameConverter::GenerateSharedImageOnGPUThread( |
| VideoFrame* video_frame, |
| const gfx::Rect& destination_visible_rect, |
| ScopedSharedImage* shared_image) { |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(shared_image); |
| DVLOGF(4) << "frame: " << video_frame->unique_id(); |
| |
| // TODO(crbug.com/998279): consider eager initialization. |
| if (!InitializeOnGPUThread()) { |
| OnError(FROM_HERE, "InitializeOnGPUThread failed"); |
| return false; |
| } |
| |
| const auto buffer_format = |
| VideoPixelFormatToGfxBufferFormat(video_frame->format()); |
| if (!buffer_format) { |
| OnError(FROM_HERE, "Unsupported format: " + |
| VideoPixelFormatToString(video_frame->format())); |
| return false; |
| } |
| |
| auto gpu_memory_buffer_handle = CreateGpuMemoryBufferHandle(video_frame); |
| DCHECK(!gpu_memory_buffer_handle.is_null()); |
| DCHECK_EQ(gpu_memory_buffer_handle.type, gfx::NATIVE_PIXMAP); |
| |
| gpu::Mailbox mailbox = gpu::Mailbox::GenerateForSharedImage(); |
| |
| if (!gpu_channel_) { |
| OnError(FROM_HERE, "GpuChannel is gone!"); |
| return false; |
| } |
| gpu::SharedImageStub* shared_image_stub = gpu_channel_->shared_image_stub(); |
| DCHECK(shared_image_stub); |
| |
| // Destination VideoFrames should have visible rectangles stating at the |
| // origin. |
| DCHECK(destination_visible_rect.origin().IsOrigin()); |
| |
| // The allocated SharedImages should be usable for the (Display) compositor |
| // and, potentially, for overlays (Scanout). |
| const uint32_t shared_image_usage = |
| gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT; |
| const bool success = shared_image_stub->CreateSharedImage( |
| mailbox, gpu::kPlatformVideoFramePoolClientId, |
| std::move(gpu_memory_buffer_handle), *buffer_format, |
| gpu::kNullSurfaceHandle, destination_visible_rect.size(), |
| video_frame->ColorSpace(), shared_image_usage); |
| if (!success) { |
| OnError(FROM_HERE, "Failed to create shared image."); |
| return false; |
| } |
| // There's no need to UpdateSharedImage() after CreateSharedImage(). |
| |
| shared_image->Reset( |
| mailbox, destination_visible_rect, |
| shared_image_stub->GetSharedImageDestructionCallback(mailbox)); |
| return true; |
| } |
| |
| void MailboxVideoFrameConverter::RegisterSharedImage( |
| VideoFrame* origin_frame, |
| std::unique_ptr<ScopedSharedImage> scoped_shared_image) { |
| DVLOGF(4) << "frame: " << origin_frame->unique_id(); |
| DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(scoped_shared_image); |
| DCHECK(!scoped_shared_image->mailbox().IsZero()); |
| DCHECK(!base::Contains(shared_images_, origin_frame->unique_id())); |
| |
| shared_images_[origin_frame->unique_id()] = scoped_shared_image.get(); |
| origin_frame->AddDestructionObserver(base::BindOnce( |
| [](std::unique_ptr<ScopedSharedImage> shared_image, |
| scoped_refptr<base::SequencedTaskRunner> parent_task_runner, |
| base::WeakPtr<MailboxVideoFrameConverter> parent_weak_ptr, |
| UniqueID origin_frame_id) { |
| if (parent_task_runner->RunsTasksInCurrentSequence()) { |
| if (parent_weak_ptr) |
| parent_weak_ptr->UnregisterSharedImage(origin_frame_id, |
| std::move(shared_image)); |
| return; |
| } |
| parent_task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MailboxVideoFrameConverter::UnregisterSharedImage, |
| parent_weak_ptr, origin_frame_id, |
| std::move(shared_image))); |
| }, |
| std::move(scoped_shared_image), parent_task_runner_, parent_weak_this_, |
| origin_frame->unique_id())); |
| } |
| |
| bool MailboxVideoFrameConverter::UpdateSharedImageOnGPUThread( |
| const gpu::Mailbox& mailbox) { |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| if (!gpu_channel_) { |
| OnError(FROM_HERE, "GpuChannel is gone!"); |
| return false; |
| } |
| gpu::SharedImageStub* shared_image_stub = gpu_channel_->shared_image_stub(); |
| DCHECK(shared_image_stub); |
| if (!shared_image_stub->UpdateSharedImage(mailbox, gfx::GpuFenceHandle())) { |
| OnError(FROM_HERE, "Could not update shared image"); |
| return false; |
| } |
| return true; |
| } |
| |
| void MailboxVideoFrameConverter::WaitOnSyncTokenAndReleaseFrameOnGPUThread( |
| scoped_refptr<VideoFrame> frame, |
| const gpu::SyncToken& sync_token) { |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| |
| if (!gpu_channel_) |
| return OnError(FROM_HERE, "GpuChannel is gone!"); |
| gpu::SharedImageStub* shared_image_stub = gpu_channel_->shared_image_stub(); |
| DCHECK(shared_image_stub); |
| |
| auto keep_video_frame_alive = base::BindOnce( |
| base::DoNothing::Once<scoped_refptr<VideoFrame>>(), std::move(frame)); |
| auto* scheduler = gpu_channel_->scheduler(); |
| DCHECK(scheduler); |
| scheduler->ScheduleTask(gpu::Scheduler::Task( |
| shared_image_stub->sequence(), std::move(keep_video_frame_alive), |
| std::vector<gpu::SyncToken>({sync_token}))); |
| } |
| |
| void MailboxVideoFrameConverter::UnregisterSharedImage( |
| UniqueID origin_frame_id, |
| std::unique_ptr<ScopedSharedImage> scoped_shared_image) { |
| DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); |
| DVLOGF(4); |
| |
| auto it = shared_images_.find(origin_frame_id); |
| DCHECK(it != shared_images_.end()); |
| DCHECK(it->second == scoped_shared_image.get()); |
| shared_images_.erase(it); |
| } |
| |
| void MailboxVideoFrameConverter::AbortPendingFrames() { |
| DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); |
| DVLOGF(4) << "Number of pending frames: " << input_frame_queue_.size(); |
| |
| input_frame_queue_ = {}; |
| } |
| |
| bool MailboxVideoFrameConverter::HasPendingFrames() const { |
| DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); |
| DVLOGF(4) << "Number of pending frames: " << input_frame_queue_.size(); |
| |
| return !input_frame_queue_.empty(); |
| } |
| |
| void MailboxVideoFrameConverter::OnError(const base::Location& location, |
| const std::string& msg) { |
| VLOGF(1) << "(" << location.ToString() << ") " << msg; |
| |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MailboxVideoFrameConverter::AbortPendingFrames, |
| parent_weak_this_)); |
| // Currently we don't have a dedicated callback to notify client that error |
| // occurs. Output a null frame to indicate any error occurs. |
| // TODO(akahuang): Create an error notification callback. |
| parent_task_runner_->PostTask(FROM_HERE, base::BindOnce(output_cb_, nullptr)); |
| } |
| |
| } // namespace media |