blob: e50dc1b44d29229d39db558a938aebc07d7fe19b [file] [log] [blame]
// 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/android/direct_shared_image_video_provider.h"
#include <memory>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/task_runner_util.h"
#include "gpu/command_buffer/service/abstract_texture.h"
#include "gpu/command_buffer/service/mailbox_manager.h"
#include "gpu/command_buffer/service/shared_image_factory.h"
#include "gpu/command_buffer/service/texture_manager.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/bind_to_current_loop.h"
#include "media/base/media_switches.h"
#include "media/gpu/android/shared_image_video.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/scoped_make_current.h"
namespace media {
namespace {
bool MakeContextCurrent(gpu::CommandBufferStub* stub) {
return stub && stub->decoder_context()->MakeCurrent();
}
scoped_refptr<gpu::SharedContextState> GetSharedContext(
gpu::CommandBufferStub* stub,
gpu::ContextResult* result) {
auto shared_context =
stub->channel()->gpu_channel_manager()->GetSharedContextState(result);
return (*result == gpu::ContextResult::kSuccess) ? shared_context : nullptr;
}
void ContextStateResultUMA(gpu::ContextResult result) {
base::UmaHistogramEnumeration(
"Media.GpuSharedImageVideoFactory.SharedContextStateResult", result);
}
} // namespace
using gpu::gles2::AbstractTexture;
DirectSharedImageVideoProvider::DirectSharedImageVideoProvider(
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
GetStubCB get_stub_cb)
: gpu_factory_(gpu_task_runner, std::move(get_stub_cb)),
gpu_task_runner_(std::move(gpu_task_runner)) {}
DirectSharedImageVideoProvider::~DirectSharedImageVideoProvider() = default;
// TODO(liberato): add a thread hop to create the default texture owner, but
// not as part of this class. just post something from VideoFrameFactory.
void DirectSharedImageVideoProvider::Initialize(GpuInitCB gpu_init_cb) {
// Note that we do not BindToCurrentLoop |gpu_init_cb|, since it is supposed
// to be called on the gpu main thread, which is somewhat hacky.
gpu_factory_.Post(FROM_HERE, &GpuSharedImageVideoFactory::Initialize,
std::move(gpu_init_cb));
}
void DirectSharedImageVideoProvider::RequestImage(
ImageReadyCB cb,
const ImageSpec& spec,
scoped_refptr<TextureOwner> texture_owner) {
// It's unclear that we should handle the image group, but since CodecImages
// have to be registered on it, we do. If the CodecImage is ever re-used,
// then part of that re-use would be to call the (then mis-named)
// destruction cb to remove it from the group.
//
// Also note that CodecImage shouldn't be the thing that's added to the
// group anyway. The thing that owns buffer management is all we really
// care about, and that doesn't have anything to do with GLImage.
gpu_factory_.Post(FROM_HERE, &GpuSharedImageVideoFactory::CreateImage,
BindToCurrentLoop(std::move(cb)), spec,
std::move(texture_owner));
}
GpuSharedImageVideoFactory::GpuSharedImageVideoFactory(
SharedImageVideoProvider::GetStubCB get_stub_cb)
: weak_factory_(this) {
DETACH_FROM_THREAD(thread_checker_);
stub_ = get_stub_cb.Run();
if (stub_)
stub_->AddDestructionObserver(this);
}
GpuSharedImageVideoFactory::~GpuSharedImageVideoFactory() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (stub_)
stub_->RemoveDestructionObserver(this);
}
void GpuSharedImageVideoFactory::Initialize(
SharedImageVideoProvider::GpuInitCB gpu_init_cb) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!MakeContextCurrent(stub_)) {
std::move(gpu_init_cb).Run(nullptr);
return;
}
decoder_helper_ = GLES2DecoderHelper::Create(stub_->decoder_context());
gpu::ContextResult result;
auto shared_context = GetSharedContext(stub_, &result);
if (!shared_context) {
DLOG(ERROR)
<< "GpuSharedImageVideoFactory: Unable to get a shared context.";
ContextStateResultUMA(result);
std::move(gpu_init_cb).Run(nullptr);
return;
}
// Make the shared context current.
auto scoped_current = std::make_unique<ui::ScopedMakeCurrent>(
shared_context->context(), shared_context->surface());
if (!shared_context->IsCurrent(nullptr)) {
result = gpu::ContextResult::kTransientFailure;
DLOG(ERROR)
<< "GpuSharedImageVideoFactory: Unable to make shared context current.";
ContextStateResultUMA(result);
std::move(gpu_init_cb).Run(nullptr);
return;
}
// Note that if |gpu_init_cb| posts, then the ScopedMakeCurrent won't help.
std::move(gpu_init_cb).Run(std::move(shared_context));
}
void GpuSharedImageVideoFactory::CreateImage(
FactoryImageReadyCB image_ready_cb,
const SharedImageVideoProvider::ImageSpec& spec,
scoped_refptr<TextureOwner> texture_owner) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Generate a shared image mailbox.
auto mailbox = gpu::Mailbox::GenerateForSharedImage();
auto codec_image = base::MakeRefCounted<CodecImage>();
TRACE_EVENT0("media", "GpuSharedImageVideoFactory::CreateVideoFrame");
if (!CreateImageInternal(spec, std::move(texture_owner), mailbox,
codec_image)) {
return;
}
// This callback destroys the shared image when video frame is
// released/destroyed. This callback has a weak pointer to the shared image
// stub because shared image stub could be destroyed before video frame. In
// those cases there is no need to destroy the shared image as the shared
// image stub destruction will cause all the shared images to be destroyed.
auto destroy_shared_image =
stub_->channel()->shared_image_stub()->GetSharedImageDestructionCallback(
mailbox);
// Guarantee that the SharedImage is destroyed even if the VideoFrame is
// dropped. Otherwise we could keep shared images we don't need alive.
auto release_cb = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
BindToCurrentLoop(std::move(destroy_shared_image)), gpu::SyncToken());
SharedImageVideoProvider::ImageRecord record;
record.mailbox = mailbox;
record.release_cb = std::move(release_cb);
record.ycbcr_info = ycbcr_info_;
// Since |codec_image|'s ref holders can be destroyed by stub destruction, we
// create a ref to it for the MaybeRenderEarlyManager. This is a hack; we
// should not be sending the CodecImage at all. The MaybeRenderEarlyManager
// should work with some other object that happens to be used by CodecImage,
// and non-GL things, to hold the output buffer, etc.
record.codec_image_holder = base::MakeRefCounted<CodecImageHolder>(
base::SequencedTaskRunnerHandle::Get(), std::move(codec_image));
std::move(image_ready_cb).Run(std::move(record));
}
bool GpuSharedImageVideoFactory::CreateImageInternal(
const SharedImageVideoProvider::ImageSpec& spec,
scoped_refptr<TextureOwner> texture_owner,
gpu::Mailbox mailbox,
scoped_refptr<CodecImage> image) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!MakeContextCurrent(stub_))
return false;
gpu::gles2::ContextGroup* group = stub_->decoder_context()->GetContextGroup();
if (!group)
return false;
gpu::gles2::TextureManager* texture_manager = group->texture_manager();
if (!texture_manager)
return false;
const auto& size = spec.size;
// Create a Texture and a CodecImage to back it.
// TODO(liberato): Once legacy mailbox support is removed, we don't need to
// create this texture. So, we won't need |texture_owner| either.
std::unique_ptr<AbstractTexture> texture = decoder_helper_->CreateTexture(
GL_TEXTURE_EXTERNAL_OES, GL_RGBA, size.width(), size.height(), GL_RGBA,
GL_UNSIGNED_BYTE);
// Attach the image to the texture.
// Either way, we expect this to be UNBOUND (i.e., decoder-managed). For
// overlays, BindTexImage will return true, causing it to transition to the
// BOUND state, and thus receive ScheduleOverlayPlane calls. For TextureOwner
// backed images, BindTexImage will return false, and CopyTexImage will be
// tried next.
// TODO(liberato): consider not binding this as a StreamTextureImage if we're
// using an overlay. There's no advantage. We'd likely want to create (and
// initialize to a 1x1 texture) a 2D texture above in that case, in case
// somebody tries to sample from it. Be sure that promotion hints still
// work properly, though -- they might require a stream texture image.
GLuint texture_owner_service_id =
texture_owner ? texture_owner->GetTextureId() : 0;
texture->BindStreamTextureImage(image.get(), texture_owner_service_id);
gpu::ContextResult result;
auto shared_context = GetSharedContext(stub_, &result);
if (!shared_context) {
DLOG(ERROR)
<< "GpuSharedImageVideoFactory: Unable to get a shared context.";
ContextStateResultUMA(result);
return false;
}
// Create a shared image.
// TODO(vikassoni): Hardcoding colorspace to SRGB. Figure how if media has a
// colorspace and wire it here.
// TODO(vikassoni): This shared image need to be thread safe eventually for
// webview to work with shared images.
auto shared_image = std::make_unique<SharedImageVideo>(
mailbox, gfx::ColorSpace::CreateSRGB(), std::move(image),
std::move(texture), std::move(shared_context),
false /* is_thread_safe */);
if (!ycbcr_info_)
ycbcr_info_ = shared_image->GetYcbcrInfo();
// Register it with shared image mailbox as well as legacy mailbox. This
// keeps |shared_image| around until its destruction cb is called.
// NOTE: Currently none of the video mailbox consumer uses shared image
// mailbox.
DCHECK(stub_->channel()->gpu_channel_manager()->shared_image_manager());
stub_->channel()->shared_image_stub()->factory()->RegisterBacking(
std::move(shared_image), /* legacy_mailbox */ true);
return true;
}
void GpuSharedImageVideoFactory::OnWillDestroyStub(bool have_context) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(stub_);
stub_ = nullptr;
decoder_helper_ = nullptr;
}
} // namespace media