blob: 177ecfffcfa2862da587001b763d3f72efd22436 [file] [log] [blame]
// Copyright 2020 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/renderers/video_frame_yuv_converter.h"
#include <GLES3/gl3.h>
#include "base/logging.h"
#include "components/viz/common/gpu/raster_context_provider.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "media/base/video_frame.h"
#include "media/renderers/video_frame_yuv_mailboxes_holder.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkColorType.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkYUVAInfo.h"
#include "third_party/skia/include/core/SkYUVAPixmaps.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/gpu/GrYUVABackendTextures.h"
#include "third_party/skia/include/gpu/gl/GrGLTypes.h"
namespace media {
namespace {
SkColorType GetCompatibleSurfaceColorType(GrGLenum format) {
switch (format) {
case GL_RGBA8:
return kRGBA_8888_SkColorType;
case GL_RGB565:
return kRGB_565_SkColorType;
case GL_RGBA16F:
return kRGBA_F16_SkColorType;
case GL_RGB8:
return kRGB_888x_SkColorType;
case GL_RGB10_A2:
return kRGBA_1010102_SkColorType;
case GL_RGBA4:
return kARGB_4444_SkColorType;
case GL_SRGB8_ALPHA8:
return kRGBA_8888_SkColorType;
default:
NOTREACHED();
return kUnknown_SkColorType;
}
}
GrGLenum GetSurfaceColorFormat(GrGLenum format, GrGLenum type) {
if (format == GL_RGBA) {
if (type == GL_UNSIGNED_BYTE)
return GL_RGBA8;
if (type == GL_UNSIGNED_SHORT_4_4_4_4)
return GL_RGBA4;
}
if (format == GL_RGB) {
if (type == GL_UNSIGNED_BYTE)
return GL_RGB8;
if (type == GL_UNSIGNED_SHORT_5_6_5)
return GL_RGB565;
}
return format;
}
void DrawYUVImageToSkSurface(const VideoFrame* video_frame,
sk_sp<SkImage> image,
sk_sp<SkSurface> surface,
bool use_visible_rect) {
if (!use_visible_rect) {
surface->getCanvas()->drawImage(image, 0, 0);
} else {
// Draw the planar SkImage to the SkSurface wrapping the WebGL texture.
// Using drawImageRect to draw visible rect from video frame to dst texture.
const gfx::Rect& visible_rect = video_frame->visible_rect();
const SkRect src_rect =
SkRect::MakeXYWH(visible_rect.x(), visible_rect.y(),
visible_rect.width(), visible_rect.height());
const SkRect dst_rect =
SkRect::MakeWH(visible_rect.width(), visible_rect.height());
surface->getCanvas()->drawImageRect(image, src_rect, dst_rect,
SkSamplingOptions(), nullptr,
SkCanvas::kStrict_SrcRectConstraint);
}
surface->flushAndSubmit();
}
} // namespace
VideoFrameYUVConverter::VideoFrameYUVConverter() = default;
VideoFrameYUVConverter::~VideoFrameYUVConverter() = default;
bool VideoFrameYUVConverter::IsVideoFrameFormatSupported(
const VideoFrame& video_frame) {
return std::get<0>(VideoFrameYUVMailboxesHolder::VideoPixelFormatToSkiaValues(
video_frame.format())) != SkYUVAInfo::PlaneConfig::kUnknown;
}
bool VideoFrameYUVConverter::ConvertYUVVideoFrameNoCaching(
const VideoFrame* video_frame,
viz::RasterContextProvider* raster_context_provider,
const gpu::MailboxHolder& dest_mailbox_holder,
absl::optional<GrParams> gr_params) {
VideoFrameYUVConverter converter;
return converter.ConvertYUVVideoFrame(video_frame, raster_context_provider,
dest_mailbox_holder, gr_params);
}
bool VideoFrameYUVConverter::ConvertYUVVideoFrame(
const VideoFrame* video_frame,
viz::RasterContextProvider* raster_context_provider,
const gpu::MailboxHolder& dest_mailbox_holder,
absl::optional<GrParams> gr_params) {
DCHECK(video_frame);
DCHECK(IsVideoFrameFormatSupported(*video_frame))
<< "VideoFrame has an unsupported YUV format " << video_frame->format();
DCHECK(!video_frame->coded_size().IsEmpty())
<< "|video_frame| must have an area > 0";
DCHECK(raster_context_provider);
if (!holder_)
holder_ = std::make_unique<VideoFrameYUVMailboxesHolder>();
if (raster_context_provider->GrContext()) {
return ConvertFromVideoFrameYUVWithGrContext(
video_frame, raster_context_provider, dest_mailbox_holder,
gr_params.value_or(GrParams()));
}
// The RasterInterface path does not support flip_y or use_visible_rect.
if (gr_params) {
DCHECK(!gr_params->flip_y);
DCHECK(!gr_params->use_visible_rect);
}
auto* ri = raster_context_provider->RasterInterface();
DCHECK(ri);
ri->WaitSyncTokenCHROMIUM(dest_mailbox_holder.sync_token.GetConstData());
gpu::Mailbox mailboxes[SkYUVAInfo::kMaxPlanes]{};
holder_->VideoFrameToMailboxes(video_frame, raster_context_provider,
mailboxes);
ri->ConvertYUVAMailboxesToRGB(dest_mailbox_holder.mailbox,
holder_->yuva_info().yuvColorSpace(), nullptr,
holder_->yuva_info().planeConfig(),
holder_->yuva_info().subsampling(), mailboxes);
return true;
}
void VideoFrameYUVConverter::ReleaseCachedData() {
holder_.reset();
}
bool VideoFrameYUVConverter::ConvertFromVideoFrameYUVWithGrContext(
const VideoFrame* video_frame,
viz::RasterContextProvider* raster_context_provider,
const gpu::MailboxHolder& dest_mailbox_holder,
const GrParams& gr_params) {
gpu::raster::RasterInterface* ri = raster_context_provider->RasterInterface();
DCHECK(ri);
ri->WaitSyncTokenCHROMIUM(dest_mailbox_holder.sync_token.GetConstData());
GLuint dest_tex_id =
ri->CreateAndConsumeForGpuRaster(dest_mailbox_holder.mailbox);
if (dest_mailbox_holder.mailbox.IsSharedImage()) {
ri->BeginSharedImageAccessDirectCHROMIUM(
dest_tex_id, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
}
// Rendering YUV textures to SkSurface by dst texture
GrDirectContext* gr_context = raster_context_provider->GrContext();
DCHECK(gr_context);
// TODO(crbug.com/674185): We should compare the DCHECK vs when
// UpdateLastImage calls this function.
DCHECK(IsVideoFrameFormatSupported(*video_frame));
// Create SkSurface with dst texture.
GrGLTextureInfo result_gl_texture_info{};
result_gl_texture_info.fID = dest_tex_id;
result_gl_texture_info.fTarget = dest_mailbox_holder.texture_target;
result_gl_texture_info.fFormat =
GetSurfaceColorFormat(gr_params.internal_format, gr_params.type);
int result_width = gr_params.use_visible_rect
? video_frame->visible_rect().width()
: video_frame->coded_size().width();
int result_height = gr_params.use_visible_rect
? video_frame->visible_rect().height()
: video_frame->coded_size().height();
GrBackendTexture result_texture(result_width, result_height, GrMipMapped::kNo,
result_gl_texture_info);
// Use the same SkColorSpace for the surface and image, so that no color space
// conversion is performed.
auto source_and_dest_color_space = SkColorSpace::MakeSRGB();
// Use dst texture as SkSurface back resource.
auto surface = SkSurface::MakeFromBackendTexture(
gr_context, result_texture,
gr_params.flip_y ? kBottomLeft_GrSurfaceOrigin : kTopLeft_GrSurfaceOrigin,
1, GetCompatibleSurfaceColorType(result_gl_texture_info.fFormat),
source_and_dest_color_space, nullptr);
// Terminate if surface cannot be created.
bool result = false;
if (surface) {
auto image = holder_->VideoFrameToSkImage(
video_frame, raster_context_provider, source_and_dest_color_space);
if (image) {
result = true;
DrawYUVImageToSkSurface(video_frame, image, surface,
gr_params.use_visible_rect);
} else {
DLOG(ERROR) << "Failed to create YUV SkImage";
}
} else {
DLOG(ERROR) << "Failed to create SkSurface";
}
// Release textures to guarantee |holder_| doesn't hold read access on
// textures it doesn't own.
holder_->ReleaseTextures();
if (dest_mailbox_holder.mailbox.IsSharedImage())
ri->EndSharedImageAccessDirectCHROMIUM(dest_tex_id);
ri->DeleteGpuRasterTexture(dest_tex_id);
return result;
}
} // namespace media