| // Copyright 2018 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/ipc/service/picture_buffer_manager.h" |
| |
| #include <map> |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/synchronization/lock.h" |
| #include "gpu/command_buffer/common/mailbox_holder.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Generates nonnegative picture buffer IDs, which are assumed to be unique. |
| int32_t NextID(int32_t* counter) { |
| int32_t value = *counter; |
| *counter = (*counter + 1) & 0x3FFFFFFF; |
| return value; |
| } |
| |
| class PictureBufferManagerImpl : public PictureBufferManager { |
| public: |
| explicit PictureBufferManagerImpl( |
| ReusePictureBufferCB reuse_picture_buffer_cb) |
| : reuse_picture_buffer_cb_(std::move(reuse_picture_buffer_cb)) { |
| DVLOG(1) << __func__; |
| } |
| |
| void Initialize( |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| scoped_refptr<CommandBufferHelper> command_buffer_helper) override { |
| DVLOG(1) << __func__; |
| DCHECK(!gpu_task_runner_); |
| |
| gpu_task_runner_ = std::move(gpu_task_runner); |
| command_buffer_helper_ = std::move(command_buffer_helper); |
| } |
| |
| bool CanReadWithoutStalling() override { |
| DVLOG(3) << __func__; |
| |
| base::AutoLock lock(picture_buffers_lock_); |
| |
| // If there are no assigned picture buffers, predict that the VDA will |
| // request some. |
| if (picture_buffers_.empty()) |
| return true; |
| |
| // Predict that the VDA can output a picture if at least one picture buffer |
| // is not in use as an output. |
| for (const auto& it : picture_buffers_) { |
| if (it.second.state != PictureBufferState::OUTPUT) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| std::vector<PictureBuffer> CreatePictureBuffers( |
| uint32_t count, |
| VideoPixelFormat pixel_format, |
| uint32_t planes, |
| gfx::Size texture_size, |
| uint32_t texture_target) override { |
| DVLOG(2) << __func__; |
| DCHECK(gpu_task_runner_); |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(count); |
| DCHECK(planes); |
| DCHECK_LE(planes, VideoFrame::kMaxPlanes); |
| |
| // TODO(sandersd): Consider requiring that CreatePictureBuffers() is called |
| // with the context current. |
| if (!command_buffer_helper_->MakeContextCurrent()) { |
| DVLOG(1) << "Failed to make context current"; |
| return std::vector<PictureBuffer>(); |
| } |
| |
| std::vector<PictureBuffer> picture_buffers; |
| for (uint32_t i = 0; i < count; i++) { |
| PictureBuffer::TextureIds service_ids; |
| PictureBufferData picture_data = {PictureBufferState::AVAILABLE, |
| pixel_format, texture_size}; |
| |
| for (uint32_t j = 0; j < planes; j++) { |
| // Create a texture for this plane. |
| GLuint service_id = command_buffer_helper_->CreateTexture( |
| texture_target, GL_RGBA, texture_size.width(), |
| texture_size.height(), GL_RGBA, GL_UNSIGNED_BYTE); |
| DCHECK(service_id); |
| service_ids.push_back(service_id); |
| |
| // The texture is not cleared yet, but it will be before the VDA outputs |
| // it. Rather than requiring output to happen on the GPU thread, mark |
| // the texture as cleared immediately. |
| command_buffer_helper_->SetCleared(service_id); |
| |
| // Generate a mailbox while we are still on the GPU thread. |
| picture_data.mailbox_holders[j] = gpu::MailboxHolder( |
| command_buffer_helper_->CreateMailbox(service_id), gpu::SyncToken(), |
| texture_target); |
| } |
| |
| // Generate a picture buffer ID and record the picture buffer. |
| int32_t picture_buffer_id = NextID(&picture_buffer_id_); |
| { |
| base::AutoLock lock(picture_buffers_lock_); |
| DCHECK(!picture_buffers_.count(picture_buffer_id)); |
| picture_buffers_[picture_buffer_id] = picture_data; |
| } |
| |
| // Since our textures have no client IDs, we reuse the service IDs as |
| // convenient unique identifiers. |
| // |
| // TODO(sandersd): Refactor the bind image callback to use service IDs so |
| // that we can get rid of the client IDs altogether. |
| picture_buffers.emplace_back(picture_buffer_id, texture_size, service_ids, |
| service_ids, texture_target, pixel_format); |
| |
| // Record the textures used by the picture buffer. |
| picture_buffer_textures_[picture_buffer_id] = std::move(service_ids); |
| } |
| return picture_buffers; |
| } |
| |
| bool DismissPictureBuffer(int32_t picture_buffer_id) override { |
| DVLOG(2) << __func__ << "(" << picture_buffer_id << ")"; |
| DCHECK(gpu_task_runner_); |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| |
| base::AutoLock lock(picture_buffers_lock_); |
| |
| // Check the state of the picture buffer. |
| const auto& it = picture_buffers_.find(picture_buffer_id); |
| if (it == picture_buffers_.end()) { |
| DVLOG(1) << "Unknown picture buffer " << picture_buffer_id; |
| return false; |
| } |
| |
| bool is_available = it->second.state == PictureBufferState::AVAILABLE; |
| |
| // Destroy the picture buffer data. |
| picture_buffers_.erase(it); |
| |
| // If the picture was available, we can destroy its textures immediately. |
| if (is_available) { |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &PictureBufferManagerImpl::DestroyPictureBufferTextures, this, |
| picture_buffer_id)); |
| } |
| |
| return true; |
| } |
| |
| scoped_refptr<VideoFrame> CreateVideoFrame(Picture picture, |
| base::TimeDelta timestamp, |
| gfx::Rect visible_rect, |
| gfx::Size natural_size) override { |
| DVLOG(2) << __func__ << "(" << picture.picture_buffer_id() << ")"; |
| DCHECK(!picture.size_changed()); |
| DCHECK(!picture.surface_texture()); |
| DCHECK(!picture.wants_promotion_hint()); |
| |
| base::AutoLock lock(picture_buffers_lock_); |
| |
| int32_t picture_buffer_id = picture.picture_buffer_id(); |
| |
| // Verify that the picture buffer is available. |
| const auto& it = picture_buffers_.find(picture_buffer_id); |
| if (it == picture_buffers_.end()) { |
| DVLOG(1) << "Unknown picture buffer " << picture_buffer_id; |
| return nullptr; |
| } |
| |
| PictureBufferData& picture_buffer_data = it->second; |
| if (picture_buffer_data.state != PictureBufferState::AVAILABLE) { |
| DLOG(ERROR) << "Picture buffer " << picture_buffer_id |
| << " is not available"; |
| return nullptr; |
| } |
| |
| // Verify that the picture buffer is large enough. |
| if (!gfx::Rect(picture_buffer_data.texture_size).Contains(visible_rect)) { |
| DLOG(ERROR) << "visible_rect " << visible_rect.ToString() |
| << " exceeds coded_size " |
| << picture_buffer_data.texture_size.ToString(); |
| return nullptr; |
| } |
| |
| // Mark the picture as an output. |
| picture_buffer_data.state = PictureBufferState::OUTPUT; |
| |
| // Create and return a VideoFrame for the picture buffer. |
| scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures( |
| picture_buffer_data.pixel_format, picture_buffer_data.mailbox_holders, |
| base::BindRepeating(&PictureBufferManagerImpl::OnVideoFrameDestroyed, |
| this, picture_buffer_id), |
| picture_buffer_data.texture_size, visible_rect, natural_size, |
| timestamp); |
| |
| frame->set_color_space(picture.color_space()); |
| |
| if (picture.allow_overlay()) |
| frame->metadata()->SetBoolean(VideoFrameMetadata::ALLOW_OVERLAY, true); |
| |
| // TODO(sandersd): Provide an API for VDAs to control this. |
| frame->metadata()->SetBoolean(VideoFrameMetadata::POWER_EFFICIENT, true); |
| |
| return frame; |
| } |
| |
| private: |
| ~PictureBufferManagerImpl() override { DVLOG(1) << __func__; } |
| |
| void OnVideoFrameDestroyed(int32_t picture_buffer_id, |
| const gpu::SyncToken& sync_token) { |
| DVLOG(3) << __func__ << "(" << picture_buffer_id << ")"; |
| |
| base::AutoLock lock(picture_buffers_lock_); |
| |
| // If the picture buffer is still assigned, mark it as unreleased. |
| const auto& it = picture_buffers_.find(picture_buffer_id); |
| if (it != picture_buffers_.end()) { |
| DCHECK_EQ(it->second.state, PictureBufferState::OUTPUT); |
| it->second.state = PictureBufferState::WAITING_FOR_SYNCTOKEN; |
| } |
| |
| // Wait for the SyncToken release. |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CommandBufferHelper::WaitForSyncToken, command_buffer_helper_, |
| sync_token, |
| base::BindOnce(&PictureBufferManagerImpl::OnSyncTokenReleased, this, |
| picture_buffer_id))); |
| } |
| |
| void OnSyncTokenReleased(int32_t picture_buffer_id) { |
| DVLOG(3) << __func__ << "(" << picture_buffer_id << ")"; |
| DCHECK(gpu_task_runner_); |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| |
| // If the picture buffer is still assigned, mark it as available. |
| bool is_assigned = false; |
| { |
| base::AutoLock lock(picture_buffers_lock_); |
| const auto& it = picture_buffers_.find(picture_buffer_id); |
| if (it != picture_buffers_.end()) { |
| DCHECK_EQ(it->second.state, PictureBufferState::WAITING_FOR_SYNCTOKEN); |
| it->second.state = PictureBufferState::AVAILABLE; |
| is_assigned = true; |
| } |
| } |
| |
| // If the picture buffer is still assigned, it is ready to be reused. |
| // Otherwise it has been dismissed and we can now delete its textures. |
| // Neither of these operations should be done while holding the lock. |
| if (is_assigned) { |
| reuse_picture_buffer_cb_.Run(picture_buffer_id); |
| } else { |
| DestroyPictureBufferTextures(picture_buffer_id); |
| } |
| } |
| |
| void DestroyPictureBufferTextures(int32_t picture_buffer_id) { |
| DVLOG(3) << __func__ << "(" << picture_buffer_id << ")"; |
| DCHECK(gpu_task_runner_); |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| |
| if (!command_buffer_helper_->MakeContextCurrent()) |
| return; |
| |
| const auto& it = picture_buffer_textures_.find(picture_buffer_id); |
| DCHECK(it != picture_buffer_textures_.end()); |
| for (GLuint service_id : it->second) |
| command_buffer_helper_->DestroyTexture(service_id); |
| picture_buffer_textures_.erase(it); |
| } |
| |
| ReusePictureBufferCB reuse_picture_buffer_cb_; |
| |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_; |
| scoped_refptr<CommandBufferHelper> command_buffer_helper_; |
| |
| int32_t picture_buffer_id_ = 0; |
| // Includes picture puffers that have been dismissed if their textures have |
| // not been deleted yet. |
| std::map<int32_t, std::vector<GLuint>> picture_buffer_textures_; |
| |
| base::Lock picture_buffers_lock_; |
| enum class PictureBufferState { |
| // Available for use by the VDA. |
| AVAILABLE, |
| // Output by the VDA, still bound to a VideoFrame. |
| OUTPUT, |
| // Waiting on a SyncToken before being reused. |
| WAITING_FOR_SYNCTOKEN, |
| }; |
| struct PictureBufferData { |
| PictureBufferState state; |
| VideoPixelFormat pixel_format; |
| gfx::Size texture_size; |
| gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes]; |
| }; |
| // Pictures buffers that are assigned to the VDA. |
| std::map<int32_t, PictureBufferData> picture_buffers_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PictureBufferManagerImpl); |
| }; |
| |
| } // namespace |
| |
| // static |
| scoped_refptr<PictureBufferManager> PictureBufferManager::Create( |
| ReusePictureBufferCB reuse_picture_buffer_cb) { |
| return base::MakeRefCounted<PictureBufferManagerImpl>( |
| std::move(reuse_picture_buffer_cb)); |
| } |
| |
| } // namespace media |