| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/gpu/android/pooled_shared_image_video_provider.h" |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "gpu/command_buffer/common/sync_token.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| |
| namespace media { |
| |
| // static |
| std::unique_ptr<PooledSharedImageVideoProvider> |
| PooledSharedImageVideoProvider::Create( |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| GetStubCB get_stub_cb, |
| std::unique_ptr<SharedImageVideoProvider> provider, |
| scoped_refptr<gpu::RefCountedLock> drdc_lock) { |
| return base::WrapUnique(new PooledSharedImageVideoProvider( |
| base::SequenceBound<GpuHelperImpl>(std::move(gpu_task_runner), |
| std::move(get_stub_cb)), |
| std::move(provider), std::move(drdc_lock))); |
| } |
| |
| PooledSharedImageVideoProvider::PooledImage::PooledImage(const ImageSpec& spec, |
| ImageRecord record) |
| : spec(spec), record(std::move(record)) {} |
| |
| PooledSharedImageVideoProvider::PooledImage::~PooledImage() = default; |
| |
| PooledSharedImageVideoProvider::PendingRequest::PendingRequest( |
| const ImageSpec& spec, |
| ImageReadyCB cb) |
| : spec(spec), cb(std::move(cb)) {} |
| |
| PooledSharedImageVideoProvider::PendingRequest::~PendingRequest() = default; |
| |
| PooledSharedImageVideoProvider::PooledSharedImageVideoProvider( |
| base::SequenceBound<GpuHelper> gpu_helper, |
| std::unique_ptr<SharedImageVideoProvider> provider, |
| scoped_refptr<gpu::RefCountedLock> drdc_lock) |
| : gpu::RefCountedLockHelperDrDc(std::move(drdc_lock)), |
| provider_(std::move(provider)), |
| gpu_helper_(std::move(gpu_helper)), |
| weak_factory_(this) {} |
| |
| // Note that this will drop everything in |pool_|, which will call all the |
| // release callbacks for the underlying byffer. |
| PooledSharedImageVideoProvider::~PooledSharedImageVideoProvider() = default; |
| |
| // SharedImageVideoProvider |
| void PooledSharedImageVideoProvider::Initialize(GpuInitCB gpu_init_cb) { |
| provider_->Initialize(std::move(gpu_init_cb)); |
| } |
| |
| void PooledSharedImageVideoProvider::RequestImage(ImageReadyCB cb, |
| const ImageSpec& spec) { |
| // See if the pool matches the requested spec. |
| if (pool_spec_ != spec) { |
| // Nope -- mark any outstanding images for destruction and start a new pool. |
| // Note that this calls all the release callbacks. |
| pool_.clear(); |
| |
| // Any images added to the pool should match |spec|. |
| pool_spec_ = spec; |
| } |
| |
| // Push this onto the pending requests. |
| // IMPORTANT BUT SUBTLE NOTE: |spec| doesn't mention the TextureOwner, but it |
| // is sent to the provider so it must also match the one that was used with |
| // |spec|. We assume that the generation id will be updated by our caller |
| // whenever the TextureOwner changes. While this is fragile, it's also just |
| // a temporary thing. Keeping a strong ref to |texture_owner| would probably |
| // work, but it's not good to keep refs to those around longer than needed. |
| // It might be okay to do that directly, since the request (if any) that's |
| // pending for it would have the strong ref, so maybe we could just add it |
| // here too. |
| pending_requests_.emplace_back(spec, std::move(cb)); |
| |
| // Are there any free images in the pool? If so, then pop one and use it to |
| // process the request we just pushed, assuming that it's the most recent. We |
| // could optimize this call out if |pending_requensts_| wasn't empty before, |
| // since we know it doesn't match the pool spec if the pool's not empty. As |
| // it is, it will just pop and re-push the pooled buffer in the (rare) case |
| // that the pool doesn't match. |
| if (!pool_.empty()) { |
| auto front = std::move(pool_.front()); |
| pool_.pop_front(); |
| ProcessFreePooledImage(front); |
| // TODO(liberato): See if skipping the return if |pool_| is now empty is |
| // helpful, especially during start-up. Alternatively, just request some |
| // constant number of images (~5) when the pool spec changes, then add them |
| // one at a time if needed. |
| return; |
| } |
| |
| // Request a new image, since we don't have enough. There might be some |
| // outstanding that will be returned, but we'd like to have enough not to wait |
| // on them. This has the nice property that everything in |pending_requests_| |
| // will have an image delivered in order for it. Note that we might not |
| // exactly match up returned (new) images to the requests; there might be |
| // intervening returns of existing images from the client that happen to match |
| // if we switch from spec A => spec B => spec A, but that's okay. We can be |
| // sure that there are at least as many that will arrive as we need. |
| auto ready_cb = |
| base::BindOnce(&PooledSharedImageVideoProvider::OnImageCreated, |
| weak_factory_.GetWeakPtr(), spec); |
| provider_->RequestImage(std::move(ready_cb), spec); |
| } |
| |
| void PooledSharedImageVideoProvider::OnImageCreated(ImageSpec spec, |
| ImageRecord record) { |
| // Wrap |record| up for the pool, and process it. |
| scoped_refptr<PooledImage> pooled_image = |
| base::MakeRefCounted<PooledImage>(std::move(spec), std::move(record)); |
| ProcessFreePooledImage(pooled_image); |
| } |
| |
| void PooledSharedImageVideoProvider::OnImageReturned( |
| scoped_refptr<PooledImage> pooled_image, |
| const gpu::SyncToken& sync_token) { |
| // An image has been returned to us. Wait for |sync_token| and then send it |
| // to ProcessFreePooledImage to re-use / pool / delete. |
| gpu_helper_.AsyncCall(&GpuHelper::OnImageReturned) |
| .WithArgs(sync_token, pooled_image->record.codec_image_holder, |
| base::BindPostTaskToCurrentDefault(base::BindOnce( |
| &PooledSharedImageVideoProvider::ProcessFreePooledImage, |
| weak_factory_.GetWeakPtr(), pooled_image)), |
| GetDrDcLock()); |
| } |
| |
| void PooledSharedImageVideoProvider::ProcessFreePooledImage( |
| scoped_refptr<PooledImage> pooled_image) { |
| // Are there any requests pending? |
| if (pending_requests_.size()) { |
| // See if |record| matches the top request. If so, fulfill it, else drop |
| // |record| since we don't need it. Note that it's possible to have pending |
| // requests that don't match the pool spec; the pool spec is the most recent |
| // request. There might be other ones that were made before that which we |
| // didn't fulfill yet. |
| auto& front = pending_requests_.front(); |
| if (pooled_image->spec == front.spec) { |
| // Construct a record that notifies us when the image is released. |
| // TODO(liberato): Don't copy fields this way. |
| ImageRecord record; |
| record.mailbox = pooled_image->record.mailbox; |
| record.is_vulkan = pooled_image->record.is_vulkan; |
| record.codec_image_holder = pooled_image->record.codec_image_holder; |
| // The release CB notifies us instead of |provider_|. |
| record.release_cb = mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| base::BindOnce(&PooledSharedImageVideoProvider::OnImageReturned, |
| weak_factory_.GetWeakPtr(), std::move(pooled_image)), |
| gpu::SyncToken()); |
| |
| // Save the callback and remove the request, in case |cb| calls us back. |
| auto cb = std::move(front.cb); |
| pending_requests_.pop_front(); |
| |
| std::move(cb).Run(std::move(record)); |
| return; |
| } |
| |
| // Can't fulfill the topmost request. Discard |pooled_image|, even if it |
| // matches the pool. The reason is that any pending requests will have |
| // images created for them, which we'll use when they arrive. It would be |
| // okay to store |pooled_image| in the pool if it matches, but then we'd |
| // have more pooled images than we expect. |
| return; |
| } |
| |
| // There are no outstanding image requests, or the top one doesn't match |
| // |pooled_image|. If this image is compatible with the pool, then pool it. |
| // Otherwise, discard it. |
| |
| // See if |record| matches |pool_spec_|. If not, then drop it. Otherwise, |
| // pool it for later. Note that we don't explicitly call the release cb, |
| // since dropping the image is sufficient to notify |provider_|. Note that |
| // we've already waited for any sync token at this point, so it's okay if we |
| // don't provide one to the underlying release cb. |
| if (pool_spec_ != pooled_image->spec) |
| return; |
| |
| // Add it to the pool. |
| pool_.push_front(std::move(pooled_image)); |
| } |
| |
| PooledSharedImageVideoProvider::GpuHelperImpl::GpuHelperImpl( |
| GetStubCB get_stub_cb) |
| : weak_factory_(this) { |
| gpu::CommandBufferStub* stub = get_stub_cb.Run(); |
| if (stub) { |
| command_buffer_helper_ = CommandBufferHelper::Create(stub); |
| } |
| } |
| |
| PooledSharedImageVideoProvider::GpuHelperImpl::~GpuHelperImpl() = default; |
| |
| void PooledSharedImageVideoProvider::GpuHelperImpl::OnImageReturned( |
| const gpu::SyncToken& sync_token, |
| scoped_refptr<CodecImageHolder> codec_image_holder, |
| base::OnceClosure cb, |
| scoped_refptr<gpu::RefCountedLock> drdc_lock) { |
| auto on_sync_token_cleared_cb = base::BindOnce( |
| &GpuHelperImpl::OnSyncTokenCleared, weak_factory_.GetWeakPtr(), |
| std::move(codec_image_holder), std::move(cb), std::move(drdc_lock)); |
| command_buffer_helper_->WaitForSyncToken(sync_token, |
| std::move(on_sync_token_cleared_cb)); |
| } |
| |
| void PooledSharedImageVideoProvider::GpuHelperImpl::OnSyncTokenCleared( |
| scoped_refptr<CodecImageHolder> codec_image_holder, |
| base::OnceClosure cb, |
| scoped_refptr<gpu::RefCountedLock> drdc_lock) { |
| { |
| base::AutoLockMaybe auto_lock(drdc_lock ? drdc_lock->GetDrDcLockPtr() |
| : nullptr); |
| codec_image_holder->codec_image_raw()->NotifyUnused(); |
| } |
| |
| // Do this last, since |cb| might post to some other thread. |
| std::move(cb).Run(); |
| } |
| |
| } // namespace media |