// 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/frame_info_helper.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "gpu/command_buffer/service/shared_image/android_video_image_backing.h"
#include "gpu/ipc/service/command_buffer_stub.h"
#include "gpu/ipc/service/gpu_channel.h"
#include "gpu/ipc/service/gpu_channel_manager.h"
#include "media/base/media_switches.h"
#include "media/gpu/android/codec_output_buffer_renderer.h"
namespace media {
FrameInfoHelper::FrameInfo::FrameInfo() = default;
FrameInfoHelper::FrameInfo::~FrameInfo() = default;
FrameInfoHelper::FrameInfo::FrameInfo(FrameInfo&&) = default;
FrameInfoHelper::FrameInfo::FrameInfo(const FrameInfoHelper::FrameInfo&) =
default;
FrameInfoHelper::FrameInfo& FrameInfoHelper::FrameInfo::operator=(
const FrameInfoHelper::FrameInfo&) = default;
// Concrete implementation of FrameInfoHelper that renders output buffers and
// gets the FrameInfo they need.
class FrameInfoHelperImpl : public FrameInfoHelper,
public gpu::RefCountedLockHelperDrDc {
public:
FrameInfoHelperImpl(scoped_refptr<base::SequencedTaskRunner> gpu_task_runner,
SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock)
: gpu::RefCountedLockHelperDrDc(drdc_lock) {
on_gpu_ = base::SequenceBound<OnGpu>(std::move(gpu_task_runner),
std::move(get_stub_cb),
std::move(drdc_lock));
}
~FrameInfoHelperImpl() override = default;
void GetFrameInfo(std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
FrameInfoReadyCB callback) override {
Request request = {.buffer_renderer = std::move(buffer_renderer),
.callback = std::move(callback)};
requests_.push(std::move(request));
// If there were no pending requests start processing queue now.
if (requests_.size() == 1)
ProcessRequestsQueue();
}
bool IsStalled() const override { return waiting_for_real_frame_info_; }
private:
struct Request {
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer;
FrameInfoReadyCB callback;
};
class OnGpu : public gpu::RefCountedLockHelperDrDc {
public:
OnGpu(SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock)
: gpu::RefCountedLockHelperDrDc(std::move(drdc_lock)),
frame_info_helper_holder_(
base::MakeRefCounted<FrameInfoHelperHolder>(this)) {
auto* stub = get_stub_cb.Run();
if (stub) {
gpu::ContextResult result;
shared_context_ =
stub->channel()->gpu_channel_manager()->GetSharedContextState(
&result);
if (result == gpu::ContextResult::kSuccess) {
DCHECK(shared_context_);
if (shared_context_->GrContextIsVulkan()) {
vulkan_context_provider_ = shared_context_->vk_context_provider();
} else if (shared_context_->IsGraphiteDawnVulkan()) {
dawn_context_provider_ = shared_context_->dawn_context_provider();
}
}
}
}
~OnGpu() {
DCHECK(frame_info_helper_holder_);
frame_info_helper_holder_->SetFrameInfoHelperOnGpuToNull();
}
void GetFrameInfoImpl(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
AssertAcquiredDrDcLock();
DCHECK(buffer_renderer);
auto texture_owner = buffer_renderer->texture_owner();
DCHECK(texture_owner);
std::optional<FrameInfo> info;
if (buffer_renderer->RenderToTextureOwnerFrontBuffer()) {
gfx::Size coded_size;
gfx::Rect visible_rect;
if (texture_owner->GetCodedSizeAndVisibleRect(
buffer_renderer->size(), &coded_size, &visible_rect)) {
info.emplace();
info->coded_size = coded_size;
info->visible_rect = visible_rect;
info->ycbcr_info = gpu::AndroidVideoImageBacking::GetYcbcrInfo(
texture_owner.get(), vulkan_context_provider_,
dawn_context_provider_);
}
}
std::move(cb).Run(std::move(buffer_renderer), info);
}
void GetFrameInfo(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
// Note that we need to ensure that no other thread renders another buffer
// in between while we are getting frame info here. Otherwise we will get
// wrong frame info. This is ensured by holding |drdc_lock| from all the
// places from where GetFrameInfoImpl() call can be triggered. It can be
// called from here via |texture_owner->RunWhenBufferIsAvailable()| below
// or from |ImageReaderGLOwner::ReleaseRefOnImageLocked()| when the
// buffer_vailable_cb is cached and triggered. So we lock here as well as
// ensure lock is held during
// |ImageReaderGLOwner::ReleaseRefOnImageLocked|.
base::AutoLockMaybe auto_lock(GetDrDcLockPtr());
DCHECK(buffer_renderer);
auto texture_owner = buffer_renderer->texture_owner();
DCHECK(texture_owner);
auto buffer_available_cb =
base::BindOnce(&FrameInfoHelperHolder::GetFrameInfoImpl,
base::RetainedRef(frame_info_helper_holder_),
std::move(buffer_renderer), std::move(cb));
texture_owner->RunWhenBufferIsAvailable(std::move(buffer_available_cb));
}
private:
// OnGpu::GetFrameInfoImpl can be called from any gpu thread (gpu main or
// DrDc), hence we can not use WeakPtr to it in |buffer_available_cb|.
// FrameInfoHelperHolder is used instead to mimic this weakPtr behavior of
// OnGpu. FrameInfoHelperHolder is RefCountedThreadSafe, and has a pointer
// to the OnGpu. OnGpu owns the FrameInfoHelperHolder and sets this pointer
// to null in its destructor so that it can't be used once OnGpu is
// destroyed. Note that since OnGpu::GetFrameInfoImpl needed to be called
// from any gpu thread, we could not use WeakPtr to it.
class FrameInfoHelperHolder
: public base::RefCountedThreadSafe<FrameInfoHelperHolder> {
public:
explicit FrameInfoHelperHolder(raw_ptr<OnGpu> frame_info_helper_on_gpu)
: frame_info_helper_on_gpu_(frame_info_helper_on_gpu) {
DCHECK(frame_info_helper_on_gpu_);
}
void GetFrameInfoImpl(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
base::AutoLock l(lock_);
if (frame_info_helper_on_gpu_) {
frame_info_helper_on_gpu_->GetFrameInfoImpl(
std::move(buffer_renderer), std::move(cb));
}
}
void SetFrameInfoHelperOnGpuToNull() {
base::AutoLock l(lock_);
frame_info_helper_on_gpu_ = nullptr;
}
private:
friend class base::RefCountedThreadSafe<FrameInfoHelperHolder>;
~FrameInfoHelperHolder() = default;
// |lock_| for thread safe access to |frame_info_helper_on_gpu_|.
base::Lock lock_;
raw_ptr<OnGpu> frame_info_helper_on_gpu_ GUARDED_BY(lock_) = nullptr;
};
// Note that |shared_context_| is to just keep ref on it until
// context provider raw_ptrs are being used.
scoped_refptr<gpu::SharedContextState> shared_context_;
raw_ptr<viz::VulkanContextProvider> vulkan_context_provider_ = nullptr;
raw_ptr<gpu::DawnContextProvider> dawn_context_provider_ = nullptr;
scoped_refptr<FrameInfoHelperHolder> frame_info_helper_holder_;
};
bool CanGuessCodedSize(
const CodecOutputBufferRenderer& buffer_renderer) const {
// We never guess on the first frame since we can always render instead.
if (!frame_info_) {
return false;
}
// Coded size guessing will be disabled if the feature isn't enabled or we
// didn't correctly guess the initial coded size or failed to guess a coded
// size during a size change.
if (disable_coded_size_guessing_) {
return false;
}
// We can't guess non-origin visible rects.
if (!frame_info_->visible_rect.origin().IsOrigin()) {
return false;
}
return buffer_renderer.CanGuessCodedSize();
}
FrameInfo GuessFrameInfo(
const CodecOutputBufferRenderer& buffer_renderer) const {
FrameInfo info;
info.coded_size = CanGuessCodedSize(buffer_renderer)
? buffer_renderer.GuessCodedSize()
: buffer_renderer.size();
info.visible_rect = gfx::Rect(buffer_renderer.size());
return info;
}
void DisableCodedSizeGuessing(const gfx::Size& guessed_coded_size,
const gfx::Size& actual_coded_size) {
disable_coded_size_guessing_ = true;
LOG(ERROR) << "Guessed coded size incorrectly. Expected "
<< guessed_coded_size.ToString() << ", got "
<< actual_coded_size.ToString();
}
void OnRealFrameInfoAvailable(gfx::Size visible_size,
gfx::Size guessed_coded_size,
std::optional<gfx::Size> coded_size,
std::optional<gfx::Rect> visible_rect) {
DVLOG(1) << __func__
<< ": coded_size=" << (coded_size ? coded_size->ToString() : "")
<< ", visible_rect="
<< (visible_rect ? visible_rect->ToString() : "");
DCHECK(waiting_for_real_frame_info_);
waiting_for_real_frame_info_ = false;
if (coded_size && visible_rect) {
const bool guessed_coded_size_correctly =
guessed_coded_size == *coded_size;
base::UmaHistogramBoolean("Media.FrameInfo.GuessedCodedSizeChangeSuccess",
guessed_coded_size_correctly);
if (!guessed_coded_size_correctly) {
DisableCodedSizeGuessing(guessed_coded_size, frame_info_->coded_size);
}
frame_info_->coded_size = *coded_size;
frame_info_->visible_rect = *visible_rect;
visible_size_ = visible_size;
// There's no way to get ycbr_info at this point. The TextureOwner can't
// provide it since it doesn't have a vulkan context and we can't get it
// here since there may not be a TextureOwner anymore. We're not even on
// the right thread anymore.
} else {
// This means the buffer was destroyed without being rendered to the front
// buffer, so we must emit another frame to try and get real size info.
}
ProcessRequestsQueue();
}
void OnFrameInfoReady(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
std::optional<FrameInfo> frame_info) {
DCHECK(buffer_renderer);
DCHECK(!requests_.empty());
auto& request = requests_.front();
if (frame_info) {
const bool is_first_frame = !frame_info_;
visible_size_ = buffer_renderer->size();
frame_info_ = *frame_info;
// We always render the first frame, so we compare the real coded size
// against what we would have guessed. We then turn off guessing for
// future coded size changes if we guessed wrong.
if (is_first_frame && CanGuessCodedSize(*buffer_renderer)) {
const auto guessed_coded_size = buffer_renderer->GuessCodedSize();
const bool guessed_coded_size_correctly =
frame_info_->coded_size == guessed_coded_size;
base::UmaHistogramBoolean(
"Media.FrameInfo.GuessedInitialCodedSizeSuccess",
guessed_coded_size_correctly);
if (!guessed_coded_size_correctly) {
DisableCodedSizeGuessing(guessed_coded_size, frame_info_->coded_size);
}
}
std::move(request.callback).Run(std::move(buffer_renderer), *frame_info_);
} else {
// It's possible that we will fail to render frame and so weren't able to
// obtain FrameInfo. In this case we don't cache new values and complete
// current request with guessed values.
auto info = GuessFrameInfo(*buffer_renderer);
std::move(request.callback)
.Run(std::move(buffer_renderer), std::move(info));
}
requests_.pop();
ProcessRequestsQueue();
}
void ProcessRequestsQueue() {
while (!requests_.empty()) {
auto& request = requests_.front();
if (!request.buffer_renderer) {
// If we don't have buffer_renderer we can Run callback immediately.
std::move(request.callback).Run(nullptr, FrameInfo());
} else if (!request.buffer_renderer->texture_owner()) {
// If there is no texture_owner (SurfaceView case), we can't render
// frame and get proper size. But as Display Compositor won't render
// this frame the actual size is not important, so just guess.
auto info = GuessFrameInfo(*request.buffer_renderer);
std::move(request.callback)
.Run(std::move(request.buffer_renderer), std::move(info));
} else if (waiting_for_real_frame_info_) {
// Only allow emitting one frame at a time when we're waiting for frame
// information so that any glitches caused by a wrong guess are limited
// to a single visible frame.
break;
} else if (visible_size_ == request.buffer_renderer->size()) {
// We have cached the results of last frame info request with the same
// size. We assume that coded_size doesn't change if the visible_size
// stays the same.
std::move(request.callback)
.Run(std::move(request.buffer_renderer), *frame_info_);
} else if (CanGuessCodedSize(*request.buffer_renderer)) {
DCHECK(frame_info_); // We never guess on the first frame.
// To avoid glitches during size changes, guess a likely coded size.
auto info = GuessFrameInfo(*request.buffer_renderer);
waiting_for_real_frame_info_ = true;
// Ensure we get the real coded size for the next frame.
request.buffer_renderer->set_frame_info_callback(
base::BindPostTaskToCurrentDefault(base::BindOnce(
&FrameInfoHelperImpl::OnRealFrameInfoAvailable,
weak_factory_.GetWeakPtr(), request.buffer_renderer->size(),
info.coded_size)));
std::move(request.callback)
.Run(std::move(request.buffer_renderer), info);
} else {
// We have texture_owner and we don't have cached value, so we need to
// hop to GPU thread and render the frame to get proper size.
auto cb = base::BindPostTaskToCurrentDefault(
base::BindOnce(&FrameInfoHelperImpl::OnFrameInfoReady,
weak_factory_.GetWeakPtr()));
on_gpu_.AsyncCall(&OnGpu::GetFrameInfo)
.WithArgs(std::move(request.buffer_renderer), std::move(cb));
// We didn't complete this request quite yet, so we can't process queue
// any further.
break;
}
requests_.pop();
}
}
base::SequenceBound<OnGpu> on_gpu_;
base::queue<Request> requests_;
// Cached values.
std::optional<FrameInfo> frame_info_;
gfx::Size visible_size_;
bool waiting_for_real_frame_info_ = false;
bool disable_coded_size_guessing_ =
!base::FeatureList::IsEnabled(kMediaCodecCodedSizeGuessing);
base::WeakPtrFactory<FrameInfoHelperImpl> weak_factory_{this};
};
// static
std::unique_ptr<FrameInfoHelper> FrameInfoHelper::Create(
scoped_refptr<base::SequencedTaskRunner> gpu_task_runner,
SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock) {
return std::make_unique<FrameInfoHelperImpl>(
std::move(gpu_task_runner), std::move(get_stub_cb), std::move(drdc_lock));
}
} // namespace media