blob: e609f7a5383b507e1fa35e1f34784d8c5cde2356 [file] [log] [blame]
// Copyright 2013 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 "cc/resources/video_resource_updater.h"
#include <algorithm>
#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "cc/output/gl_renderer.h"
#include "cc/resources/resource_provider.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "media/base/video_frame.h"
#include "media/filters/skcanvas_video_renderer.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/khronos/GLES2/gl2ext.h"
#include "ui/gfx/geometry/size_conversions.h"
namespace cc {
namespace {
const ResourceFormat kYUVResourceFormat = LUMINANCE_8;
const ResourceFormat kRGBResourceFormat = RGBA_8888;
class SyncPointClientImpl : public media::VideoFrame::SyncPointClient {
public:
explicit SyncPointClientImpl(gpu::gles2::GLES2Interface* gl) : gl_(gl) {}
~SyncPointClientImpl() override {}
uint32 InsertSyncPoint() override {
return GLC(gl_, gl_->InsertSyncPointCHROMIUM());
}
void WaitSyncPoint(uint32 sync_point) override {
GLC(gl_, gl_->WaitSyncPointCHROMIUM(sync_point));
}
private:
gpu::gles2::GLES2Interface* gl_;
};
} // namespace
VideoResourceUpdater::PlaneResource::PlaneResource(
unsigned int resource_id,
const gfx::Size& resource_size,
ResourceFormat resource_format,
gpu::Mailbox mailbox)
: resource_id(resource_id),
resource_size(resource_size),
resource_format(resource_format),
mailbox(mailbox),
ref_count(0),
frame_ptr(nullptr),
plane_index(0) {
}
bool VideoResourceUpdater::PlaneResourceMatchesUniqueID(
const PlaneResource& plane_resource,
const media::VideoFrame* video_frame,
int plane_index) {
return plane_resource.frame_ptr == video_frame &&
plane_resource.plane_index == plane_index &&
plane_resource.timestamp == video_frame->timestamp();
}
void VideoResourceUpdater::SetPlaneResourceUniqueId(
const media::VideoFrame* video_frame,
int plane_index,
PlaneResource* plane_resource) {
plane_resource->frame_ptr = video_frame;
plane_resource->plane_index = plane_index;
plane_resource->timestamp = video_frame->timestamp();
}
VideoFrameExternalResources::VideoFrameExternalResources() : type(NONE) {}
VideoFrameExternalResources::~VideoFrameExternalResources() {}
VideoResourceUpdater::VideoResourceUpdater(ContextProvider* context_provider,
ResourceProvider* resource_provider)
: context_provider_(context_provider),
resource_provider_(resource_provider) {
}
VideoResourceUpdater::~VideoResourceUpdater() {
for (const PlaneResource& plane_resource : all_resources_)
resource_provider_->DeleteResource(plane_resource.resource_id);
}
VideoResourceUpdater::ResourceList::iterator
VideoResourceUpdater::AllocateResource(const gfx::Size& plane_size,
ResourceFormat format,
bool has_mailbox) {
// TODO(danakj): Abstract out hw/sw resource create/delete from
// ResourceProvider and stop using ResourceProvider in this class.
const ResourceProvider::ResourceId resource_id =
resource_provider_->CreateResource(plane_size, GL_CLAMP_TO_EDGE,
ResourceProvider::TextureHintImmutable,
format);
if (resource_id == 0)
return all_resources_.end();
gpu::Mailbox mailbox;
if (has_mailbox) {
DCHECK(context_provider_);
gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL();
GLC(gl, gl->GenMailboxCHROMIUM(mailbox.name));
ResourceProvider::ScopedWriteLockGL lock(resource_provider_, resource_id);
GLC(gl, gl->ProduceTextureDirectCHROMIUM(lock.texture_id(), GL_TEXTURE_2D,
mailbox.name));
}
all_resources_.push_front(
PlaneResource(resource_id, plane_size, format, mailbox));
return all_resources_.begin();
}
void VideoResourceUpdater::DeleteResource(ResourceList::iterator resource_it) {
DCHECK_EQ(resource_it->ref_count, 0);
resource_provider_->DeleteResource(resource_it->resource_id);
all_resources_.erase(resource_it);
}
VideoFrameExternalResources VideoResourceUpdater::
CreateExternalResourcesFromVideoFrame(
const scoped_refptr<media::VideoFrame>& video_frame) {
if (!VerifyFrame(video_frame))
return VideoFrameExternalResources();
if (video_frame->format() == media::VideoFrame::NATIVE_TEXTURE)
return CreateForHardwarePlanes(video_frame);
else
return CreateForSoftwarePlanes(video_frame);
}
bool VideoResourceUpdater::VerifyFrame(
const scoped_refptr<media::VideoFrame>& video_frame) {
switch (video_frame->format()) {
// Acceptable inputs.
case media::VideoFrame::YV12:
case media::VideoFrame::I420:
case media::VideoFrame::YV12A:
case media::VideoFrame::YV16:
case media::VideoFrame::YV12J:
case media::VideoFrame::YV24:
case media::VideoFrame::NATIVE_TEXTURE:
#if defined(VIDEO_HOLE)
case media::VideoFrame::HOLE:
#endif // defined(VIDEO_HOLE)
return true;
// Unacceptable inputs. ¯\(°_o)/¯
case media::VideoFrame::UNKNOWN:
case media::VideoFrame::NV12:
break;
}
return false;
}
// For frames that we receive in software format, determine the dimensions of
// each plane in the frame.
static gfx::Size SoftwarePlaneDimension(
const scoped_refptr<media::VideoFrame>& input_frame,
ResourceFormat output_resource_format,
size_t plane_index) {
if (output_resource_format == kYUVResourceFormat) {
return media::VideoFrame::PlaneSize(
input_frame->format(), plane_index, input_frame->coded_size());
}
DCHECK_EQ(output_resource_format, kRGBResourceFormat);
return input_frame->coded_size();
}
VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes(
const scoped_refptr<media::VideoFrame>& video_frame) {
TRACE_EVENT0("cc", "VideoResourceUpdater::CreateForSoftwarePlanes");
media::VideoFrame::Format input_frame_format = video_frame->format();
#if defined(VIDEO_HOLE)
if (input_frame_format == media::VideoFrame::HOLE) {
VideoFrameExternalResources external_resources;
external_resources.type = VideoFrameExternalResources::HOLE;
return external_resources;
}
#endif // defined(VIDEO_HOLE)
// Only YUV software video frames are supported.
if (input_frame_format != media::VideoFrame::YV12 &&
input_frame_format != media::VideoFrame::I420 &&
input_frame_format != media::VideoFrame::YV12A &&
input_frame_format != media::VideoFrame::YV12J &&
input_frame_format != media::VideoFrame::YV16 &&
input_frame_format != media::VideoFrame::YV24) {
NOTREACHED() << input_frame_format;
return VideoFrameExternalResources();
}
bool software_compositor = context_provider_ == NULL;
ResourceFormat output_resource_format = kYUVResourceFormat;
size_t output_plane_count = media::VideoFrame::NumPlanes(input_frame_format);
// TODO(skaslev): If we're in software compositing mode, we do the YUV -> RGB
// conversion here. That involves an extra copy of each frame to a bitmap.
// Obviously, this is suboptimal and should be addressed once ubercompositor
// starts shaping up.
if (software_compositor) {
output_resource_format = kRGBResourceFormat;
output_plane_count = 1;
}
// Drop recycled resources that are the wrong format.
for (auto it = all_resources_.begin(); it != all_resources_.end();) {
if (it->ref_count == 0 && it->resource_format != output_resource_format)
DeleteResource(it++);
else
++it;
}
const int max_resource_size = resource_provider_->max_texture_size();
std::vector<ResourceList::iterator> plane_resources;
for (size_t i = 0; i < output_plane_count; ++i) {
gfx::Size output_plane_resource_size =
SoftwarePlaneDimension(video_frame, output_resource_format, i);
if (output_plane_resource_size.IsEmpty() ||
output_plane_resource_size.width() > max_resource_size ||
output_plane_resource_size.height() > max_resource_size) {
break;
}
// Try recycle a previously-allocated resource.
ResourceList::iterator resource_it = all_resources_.end();
for (auto it = all_resources_.begin(); it != all_resources_.end(); ++it) {
if (it->resource_size == output_plane_resource_size &&
it->resource_format == output_resource_format) {
if (PlaneResourceMatchesUniqueID(*it, video_frame.get(), i)) {
// Bingo, we found a resource that already contains the data we are
// planning to put in it. It's safe to reuse it even if
// resource_provider_ holds some references to it, because those
// references are read-only.
resource_it = it;
break;
}
// This extra check is needed because resources backed by SharedMemory
// are not ref-counted, unlike mailboxes. Full discussion in
// codereview.chromium.org/145273021.
const bool in_use =
software_compositor &&
resource_provider_->InUseByConsumer(it->resource_id);
if (it->ref_count == 0 && !in_use) {
// We found a resource with the correct size that we can overwrite.
resource_it = it;
}
}
}
// Check if we need to allocate a new resource.
if (resource_it == all_resources_.end()) {
resource_it =
AllocateResource(output_plane_resource_size, output_resource_format,
!software_compositor);
}
if (resource_it == all_resources_.end())
break;
++resource_it->ref_count;
plane_resources.push_back(resource_it);
}
if (plane_resources.size() != output_plane_count) {
// Allocation failed, nothing will be returned so restore reference counts.
for (ResourceList::iterator resource_it : plane_resources)
--resource_it->ref_count;
return VideoFrameExternalResources();
}
VideoFrameExternalResources external_resources;
if (software_compositor) {
DCHECK_EQ(plane_resources.size(), 1u);
PlaneResource& plane_resource = *plane_resources[0];
DCHECK_EQ(plane_resource.resource_format, kRGBResourceFormat);
DCHECK(plane_resource.mailbox.IsZero());
if (!PlaneResourceMatchesUniqueID(plane_resource, video_frame.get(), 0)) {
// We need to transfer data from |video_frame| to the plane resource.
if (!video_renderer_)
video_renderer_.reset(new media::SkCanvasVideoRenderer);
ResourceProvider::ScopedWriteLockSoftware lock(
resource_provider_, plane_resource.resource_id);
video_renderer_->Copy(video_frame, lock.sk_canvas());
SetPlaneResourceUniqueId(video_frame.get(), 0, &plane_resource);
}
external_resources.software_resources.push_back(plane_resource.resource_id);
external_resources.software_release_callback =
base::Bind(&RecycleResource, AsWeakPtr(), plane_resource.resource_id);
external_resources.type = VideoFrameExternalResources::SOFTWARE_RESOURCE;
return external_resources;
}
for (size_t i = 0; i < plane_resources.size(); ++i) {
PlaneResource& plane_resource = *plane_resources[i];
// Update each plane's resource id with its content.
DCHECK_EQ(plane_resource.resource_format, kYUVResourceFormat);
if (!PlaneResourceMatchesUniqueID(plane_resource, video_frame.get(), i)) {
// We need to transfer data from |video_frame| to the plane resource.
const uint8_t* input_plane_pixels = video_frame->data(i);
gfx::Rect image_rect(0, 0, video_frame->stride(i),
plane_resource.resource_size.height());
gfx::Rect source_rect(plane_resource.resource_size);
resource_provider_->SetPixels(plane_resource.resource_id,
input_plane_pixels, image_rect, source_rect,
gfx::Vector2d());
SetPlaneResourceUniqueId(video_frame.get(), i, &plane_resource);
}
external_resources.mailboxes.push_back(
TextureMailbox(plane_resource.mailbox, GL_TEXTURE_2D, 0));
external_resources.release_callbacks.push_back(
base::Bind(&RecycleResource, AsWeakPtr(), plane_resource.resource_id));
}
external_resources.type = VideoFrameExternalResources::YUV_RESOURCE;
return external_resources;
}
// static
void VideoResourceUpdater::ReturnTexture(
base::WeakPtr<VideoResourceUpdater> updater,
const scoped_refptr<media::VideoFrame>& video_frame,
uint32 sync_point,
bool lost_resource,
BlockingTaskRunner* main_thread_task_runner) {
// TODO(dshwang) this case should be forwarded to the decoder as lost
// resource.
if (lost_resource || !updater.get())
return;
// VideoFrame::UpdateReleaseSyncPoint() creates new sync point using the same
// GL context which created the given |sync_point|, so discard the
// |sync_point|.
SyncPointClientImpl client(updater->context_provider_->ContextGL());
video_frame->UpdateReleaseSyncPoint(&client);
}
VideoFrameExternalResources VideoResourceUpdater::CreateForHardwarePlanes(
const scoped_refptr<media::VideoFrame>& video_frame) {
TRACE_EVENT0("cc", "VideoResourceUpdater::CreateForHardwarePlanes");
media::VideoFrame::Format frame_format = video_frame->format();
DCHECK_EQ(frame_format, media::VideoFrame::NATIVE_TEXTURE);
if (frame_format != media::VideoFrame::NATIVE_TEXTURE)
return VideoFrameExternalResources();
if (!context_provider_)
return VideoFrameExternalResources();
const gpu::MailboxHolder* mailbox_holder = video_frame->mailbox_holder();
VideoFrameExternalResources external_resources;
switch (mailbox_holder->texture_target) {
case GL_TEXTURE_2D:
external_resources.type = VideoFrameExternalResources::RGB_RESOURCE;
break;
case GL_TEXTURE_EXTERNAL_OES:
external_resources.type =
VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE;
break;
case GL_TEXTURE_RECTANGLE_ARB:
external_resources.type = VideoFrameExternalResources::IO_SURFACE;
break;
default:
NOTREACHED();
return VideoFrameExternalResources();
}
external_resources.mailboxes.push_back(
TextureMailbox(mailbox_holder->mailbox,
mailbox_holder->texture_target,
mailbox_holder->sync_point));
external_resources.release_callbacks.push_back(
base::Bind(&ReturnTexture, AsWeakPtr(), video_frame));
return external_resources;
}
// static
void VideoResourceUpdater::RecycleResource(
base::WeakPtr<VideoResourceUpdater> updater,
ResourceProvider::ResourceId resource_id,
uint32 sync_point,
bool lost_resource,
BlockingTaskRunner* main_thread_task_runner) {
if (!updater.get()) {
// Resource was already deleted.
return;
}
const ResourceList::iterator resource_it = std::find_if(
updater->all_resources_.begin(), updater->all_resources_.end(),
[resource_id](const PlaneResource& plane_resource) {
return plane_resource.resource_id == resource_id;
});
if (resource_it == updater->all_resources_.end())
return;
ContextProvider* context_provider = updater->context_provider_;
if (context_provider && sync_point) {
GLC(context_provider->ContextGL(),
context_provider->ContextGL()->WaitSyncPointCHROMIUM(sync_point));
}
if (lost_resource) {
resource_it->ref_count = 0;
updater->DeleteResource(resource_it);
return;
}
--resource_it->ref_count;
DCHECK_GE(resource_it->ref_count, 0);
}
} // namespace cc