blob: e2f040122103e356855ba97136d04c34fea44bd8 [file] [log] [blame]
// Copyright 2020 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/renderers/video_frame_yuv_converter.h"
#include <GLES3/gl3.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/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;
}
bool 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();
return true;
}
bool YUVPixmapsToSkSurface(GrDirectContext* gr_context,
const VideoFrame* video_frame,
const SkYUVAPixmaps yuva_pixmaps,
sk_sp<SkSurface> surface,
bool use_visible_rect) {
auto image =
SkImage::MakeFromYUVAPixmaps(gr_context, yuva_pixmaps, GrMipmapped::kNo,
false, SkColorSpace::MakeSRGB());
if (!image) {
return false;
}
return DrawYUVImageToSkSurface(video_frame, image, surface, use_visible_rect);
}
} // 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) {
VideoFrameYUVConverter converter;
return converter.ConvertYUVVideoFrame(video_frame, raster_context_provider,
dest_mailbox_holder);
}
bool VideoFrameYUVConverter::ConvertYUVVideoFrame(
const VideoFrame* video_frame,
viz::RasterContextProvider* raster_context_provider,
const gpu::MailboxHolder& dest_mailbox_holder,
unsigned int internal_format,
unsigned int type,
bool flip_y,
bool use_visible_rect,
bool use_sk_pixmap) {
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()) {
// Only SW VideoFrame direct uploading path use SkPixmap.
return ConvertFromVideoFrameYUVWithGrContext(
video_frame, raster_context_provider, dest_mailbox_holder,
internal_format, type, flip_y, use_visible_rect, use_sk_pixmap);
}
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(),
holder_->yuva_info().planeConfig(),
holder_->yuva_info().subsampling(), mailboxes);
return true;
}
bool VideoFrameYUVConverter::ConvertYUVVideoFrameToDstTextureNoCaching(
const VideoFrame* video_frame,
viz::RasterContextProvider* raster_context_provider,
const gpu::MailboxHolder& dest_mailbox_holder,
unsigned int internal_format,
unsigned int type,
bool flip_y,
bool use_visible_rect,
bool use_sk_pixmap) {
VideoFrameYUVConverter converter;
return converter.ConvertYUVVideoFrame(
video_frame, raster_context_provider, dest_mailbox_holder,
internal_format, type, flip_y, use_visible_rect, use_sk_pixmap);
}
void VideoFrameYUVConverter::ReleaseCachedData() {
holder_.reset();
}
bool VideoFrameYUVConverter::ConvertFromVideoFrameYUVWithGrContext(
const VideoFrame* video_frame,
viz::RasterContextProvider* raster_context_provider,
const gpu::MailboxHolder& dest_mailbox_holder,
unsigned int internal_format,
unsigned int type,
bool flip_y,
bool use_visible_rect,
bool use_sk_pixmap) {
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);
}
bool result = ConvertFromVideoFrameYUVSkia(
video_frame, raster_context_provider, dest_mailbox_holder.texture_target,
dest_tex_id, internal_format, type, flip_y, use_visible_rect,
use_sk_pixmap);
if (dest_mailbox_holder.mailbox.IsSharedImage())
ri->EndSharedImageAccessDirectCHROMIUM(dest_tex_id);
ri->DeleteGpuRasterTexture(dest_tex_id);
return result;
}
bool VideoFrameYUVConverter::ConvertFromVideoFrameYUVSkia(
const VideoFrame* video_frame,
viz::RasterContextProvider* raster_context_provider,
unsigned int texture_target,
unsigned int texture_id,
unsigned int internal_format,
unsigned int type,
bool flip_y,
bool use_visible_rect,
bool use_sk_pixmap) {
// 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 = texture_id;
result_gl_texture_info.fTarget = texture_target;
result_gl_texture_info.fFormat = GetSurfaceColorFormat(internal_format, type);
int result_width = use_visible_rect ? video_frame->visible_rect().width()
: video_frame->coded_size().width();
int result_height = 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 dst texture as SkSurface back resource.
auto surface = SkSurface::MakeFromBackendTexture(
gr_context, result_texture,
flip_y ? kBottomLeft_GrSurfaceOrigin : kTopLeft_GrSurfaceOrigin, 1,
GetCompatibleSurfaceColorType(result_gl_texture_info.fFormat),
SkColorSpace::MakeSRGB(), nullptr);
// Terminate if surface cannot be created.
if (!surface) {
return false;
}
bool result = false;
if (use_sk_pixmap) {
SkYUVAPixmaps yuva_pixmaps = holder_->VideoFrameToSkiaPixmaps(video_frame);
DCHECK(yuva_pixmaps.isValid());
result = YUVPixmapsToSkSurface(gr_context, video_frame, yuva_pixmaps,
surface, use_visible_rect);
} else {
auto image =
holder_->VideoFrameToSkImage(video_frame, raster_context_provider);
result =
DrawYUVImageToSkSurface(video_frame, image, surface, use_visible_rect);
// Release textures to guarantee |holder_| doesn't hold read access on
// textures it doesn't own.
holder_->ReleaseTextures();
}
return result;
}
} // namespace media