summaryrefslogtreecommitdiff
path: root/chromium/media/renderers/video_frame_yuv_mailboxes_holder.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/media/renderers/video_frame_yuv_mailboxes_holder.cc')
-rw-r--r--chromium/media/renderers/video_frame_yuv_mailboxes_holder.cc346
1 files changed, 346 insertions, 0 deletions
diff --git a/chromium/media/renderers/video_frame_yuv_mailboxes_holder.cc b/chromium/media/renderers/video_frame_yuv_mailboxes_holder.cc
new file mode 100644
index 00000000000..495a96081be
--- /dev/null
+++ b/chromium/media/renderers/video_frame_yuv_mailboxes_holder.cc
@@ -0,0 +1,346 @@
+// Copyright 2021 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_mailboxes_holder.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/GLES2/gl2extchromium.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 "third_party/skia/include/core/SkImage.h"
+#include "third_party/skia/include/core/SkSurface.h"
+#include "third_party/skia/include/core/SkYUVAPixmaps.h"
+#include "third_party/skia/include/gpu/GrDirectContext.h"
+#include "third_party/skia/include/gpu/gl/GrGLTypes.h"
+
+namespace media {
+
+namespace {
+
+viz::ResourceFormat PlaneResourceFormat(int num_channels, bool for_surface) {
+ switch (num_channels) {
+ case 1:
+ return for_surface ? viz::RED_8 : viz::LUMINANCE_8;
+ case 2:
+ return viz::RG_88;
+ case 3:
+ return viz::RGBX_8888;
+ case 4:
+ return viz::RGBA_8888;
+ }
+ NOTREACHED();
+ return viz::RGBA_8888;
+}
+
+GLenum PlaneGLFormat(int num_channels, bool for_surface) {
+ return viz::TextureStorageFormat(
+ PlaneResourceFormat(num_channels, for_surface));
+}
+
+} // namespace
+
+VideoFrameYUVMailboxesHolder::VideoFrameYUVMailboxesHolder() = default;
+
+VideoFrameYUVMailboxesHolder::~VideoFrameYUVMailboxesHolder() {
+ ReleaseCachedData();
+}
+
+void VideoFrameYUVMailboxesHolder::ReleaseCachedData() {
+ if (holders_[0].mailbox.IsZero())
+ return;
+
+ ReleaseTextures();
+
+ // Don't destroy shared images we don't own.
+ if (!created_shared_images_)
+ return;
+
+ auto* ri = provider_->RasterInterface();
+ DCHECK(ri);
+ gpu::SyncToken token;
+ ri->GenUnverifiedSyncTokenCHROMIUM(token.GetData());
+
+ auto* sii = provider_->SharedImageInterface();
+ DCHECK(sii);
+ for (auto& mailbox_holder : holders_) {
+ if (!mailbox_holder.mailbox.IsZero())
+ sii->DestroySharedImage(token, mailbox_holder.mailbox);
+ mailbox_holder.mailbox.SetZero();
+ }
+
+ created_shared_images_ = false;
+}
+
+void VideoFrameYUVMailboxesHolder::VideoFrameToMailboxes(
+ const VideoFrame* video_frame,
+ viz::RasterContextProvider* raster_context_provider,
+ gpu::Mailbox mailboxes[SkYUVAInfo::kMaxPlanes]) {
+ yuva_info_ = VideoFrameGetSkYUVAInfo(video_frame);
+ num_planes_ = yuva_info_.planeDimensions(plane_sizes_);
+
+ // If we have cached shared images but the provider or video has changed we
+ // need to release shared images created on the old context and recreate them.
+ if (created_shared_images_ &&
+ (provider_.get() != raster_context_provider ||
+ video_frame->coded_size() != cached_video_size_ ||
+ video_frame->ColorSpace() != cached_video_color_space_)) {
+ ReleaseCachedData();
+ }
+ provider_ = raster_context_provider;
+ DCHECK(provider_);
+ auto* ri = provider_->RasterInterface();
+ DCHECK(ri);
+
+ if (video_frame->HasTextures()) {
+ DCHECK_EQ(num_planes_, video_frame->NumTextures());
+ for (size_t plane = 0; plane < video_frame->NumTextures(); ++plane) {
+ holders_[plane] = video_frame->mailbox_holder(plane);
+ DCHECK(holders_[plane].texture_target == GL_TEXTURE_2D ||
+ holders_[plane].texture_target == GL_TEXTURE_EXTERNAL_OES ||
+ holders_[plane].texture_target == GL_TEXTURE_RECTANGLE_ARB)
+ << "Unsupported texture target " << std::hex << std::showbase
+ << holders_[plane].texture_target;
+ ri->WaitSyncTokenCHROMIUM(holders_[plane].sync_token.GetConstData());
+ mailboxes[plane] = holders_[plane].mailbox;
+ }
+ return;
+ }
+
+ // Create a shared image to upload the data to, if one doesn't exist already.
+ if (!created_shared_images_) {
+ auto* sii = provider_->SharedImageInterface();
+ DCHECK(sii);
+ uint32_t mailbox_usage;
+ if (provider_->ContextCapabilities().supports_oop_raster) {
+ mailbox_usage = gpu::SHARED_IMAGE_USAGE_RASTER |
+ gpu::SHARED_IMAGE_USAGE_OOP_RASTERIZATION;
+ } else {
+ mailbox_usage = gpu::SHARED_IMAGE_USAGE_GLES2;
+ }
+ for (size_t plane = 0; plane < num_planes_; ++plane) {
+ gfx::Size tex_size = {plane_sizes_[plane].width(),
+ plane_sizes_[plane].height()};
+ int num_channels = yuva_info_.numChannelsInPlane(plane);
+ viz::ResourceFormat format = PlaneResourceFormat(num_channels, false);
+ holders_[plane].mailbox = sii->CreateSharedImage(
+ format, tex_size, video_frame->ColorSpace(), kTopLeft_GrSurfaceOrigin,
+ kPremul_SkAlphaType, mailbox_usage, gpu::kNullSurfaceHandle);
+ holders_[plane].texture_target = GL_TEXTURE_2D;
+ }
+
+ // Split up shared image creation from upload so we only have to wait on
+ // one sync token.
+ ri->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
+
+ cached_video_size_ = video_frame->coded_size();
+ cached_video_color_space_ = video_frame->ColorSpace();
+ created_shared_images_ = true;
+ }
+
+ // If we have cached shared images that have been imported release them to
+ // prevent writing to a shared image for which we're holding read access.
+ ReleaseTextures();
+
+ for (size_t plane = 0; plane < num_planes_; ++plane) {
+ int num_channels = yuva_info_.numChannelsInPlane(plane);
+ SkColorType color_type = SkYUVAPixmapInfo::DefaultColorTypeForDataType(
+ SkYUVAPixmaps::DataType::kUnorm8, num_channels);
+ SkImageInfo info = SkImageInfo::Make(plane_sizes_[plane], color_type,
+ kUnknown_SkAlphaType);
+ ri->WritePixels(holders_[plane].mailbox, 0, 0, GL_TEXTURE_2D,
+ video_frame->stride(plane), info, video_frame->data(plane));
+ mailboxes[plane] = holders_[plane].mailbox;
+ }
+}
+
+GrYUVABackendTextures VideoFrameYUVMailboxesHolder::VideoFrameToSkiaTextures(
+ const VideoFrame* video_frame,
+ viz::RasterContextProvider* raster_context_provider,
+ bool for_surface) {
+ gpu::Mailbox mailboxes[kMaxPlanes];
+ VideoFrameToMailboxes(video_frame, raster_context_provider, mailboxes);
+ ImportTextures(for_surface);
+ GrBackendTexture backend_textures[SkYUVAInfo::kMaxPlanes];
+ for (size_t plane = 0; plane < num_planes_; ++plane) {
+ backend_textures[plane] = {plane_sizes_[plane].width(),
+ plane_sizes_[plane].height(), GrMipmapped::kNo,
+ textures_[plane].texture};
+ }
+ return GrYUVABackendTextures(yuva_info_, backend_textures,
+ kTopLeft_GrSurfaceOrigin);
+}
+
+sk_sp<SkImage> VideoFrameYUVMailboxesHolder::VideoFrameToSkImage(
+ const VideoFrame* video_frame,
+ viz::RasterContextProvider* raster_context_provider) {
+ GrDirectContext* gr_context = raster_context_provider->GrContext();
+ DCHECK(gr_context);
+
+ GrYUVABackendTextures yuva_backend_textures = VideoFrameToSkiaTextures(
+ video_frame, raster_context_provider, /*for_surface=*/false);
+
+ DCHECK(yuva_backend_textures.isValid());
+ auto result = SkImage::MakeFromYUVATextures(gr_context, yuva_backend_textures,
+ SkColorSpace::MakeSRGB());
+ DCHECK(result);
+ return result;
+}
+
+bool VideoFrameYUVMailboxesHolder::VideoFrameToPlaneSkSurfaces(
+ const VideoFrame* video_frame,
+ viz::RasterContextProvider* raster_context_provider,
+ sk_sp<SkSurface> surfaces[SkYUVAInfo::kMaxPlanes]) {
+ for (size_t plane = 0; plane < SkYUVAInfo::kMaxPlanes; ++plane)
+ surfaces[plane] = nullptr;
+
+ if (!video_frame->HasTextures()) {
+ // The below call to VideoFrameToSkiaTextures would blit |video_frame| into
+ // a temporary SharedImage, which would be exposed as a SkSurface. That is
+ // probably undesirable (it has no current use cases), so just return an
+ // error.
+ DLOG(ERROR) << "VideoFrameToPlaneSkSurfaces requires texture backing.";
+ return false;
+ }
+
+ GrDirectContext* gr_context = raster_context_provider->GrContext();
+ DCHECK(gr_context);
+ GrYUVABackendTextures yuva_backend_textures = VideoFrameToSkiaTextures(
+ video_frame, raster_context_provider, /*for_surface=*/true);
+
+ bool result = true;
+ for (size_t plane = 0; plane < num_planes_; ++plane) {
+ const int num_channels = yuva_info_.numChannelsInPlane(plane);
+ SkColorType color_type = SkYUVAPixmapInfo::DefaultColorTypeForDataType(
+ SkYUVAPixmaps::DataType::kUnorm8, num_channels);
+ // Gray is not renderable.
+ if (color_type == kGray_8_SkColorType)
+ color_type = kAlpha_8_SkColorType;
+
+ auto surface = SkSurface::MakeFromBackendTexture(
+ gr_context, yuva_backend_textures.texture(plane),
+ kTopLeft_GrSurfaceOrigin, /*sampleCnt=*/1, color_type,
+ SkColorSpace::MakeSRGB(), nullptr);
+ if (!surface) {
+ DLOG(ERROR)
+ << "VideoFrameToPlaneSkSurfaces failed to make surface for plane "
+ << plane << " of " << num_planes_ << ".";
+ result = false;
+ }
+ surfaces[plane] = surface;
+ }
+ return result;
+}
+
+SkYUVAPixmaps VideoFrameYUVMailboxesHolder::VideoFrameToSkiaPixmaps(
+ const VideoFrame* video_frame) {
+ yuva_info_ = VideoFrameGetSkYUVAInfo(video_frame);
+ num_planes_ = yuva_info_.planeDimensions(plane_sizes_);
+
+ // Create SkImageInfos with the appropriate color types for 8 bit unorm data
+ // based on plane config.
+ size_t row_bytes[kMaxPlanes];
+ for (size_t plane = 0; plane < num_planes_; ++plane) {
+ row_bytes[plane] = VideoFrame::RowBytes(plane, video_frame->format(),
+ plane_sizes_[plane].width());
+ }
+
+ SkYUVAPixmapInfo pixmaps_infos(yuva_info_, SkYUVAPixmaps::DataType::kUnorm8,
+ row_bytes);
+ SkPixmap pixmaps[SkYUVAInfo::kMaxPlanes];
+ for (size_t plane = 0; plane < num_planes_; ++plane) {
+ pixmaps[plane].reset(pixmaps_infos.planeInfo(plane),
+ video_frame->data(plane),
+ pixmaps_infos.rowBytes(plane));
+ }
+ return SkYUVAPixmaps::FromExternalPixmaps(yuva_info_, pixmaps);
+}
+
+void VideoFrameYUVMailboxesHolder::ImportTextures(bool for_surface) {
+ DCHECK(!imported_textures_)
+ << "Textures should always be released after converting video frame. "
+ "Call ReleaseTextures() for each call to VideoFrameToSkiaTextures()";
+
+ auto* ri = provider_->RasterInterface();
+ for (size_t plane = 0; plane < num_planes_; ++plane) {
+ textures_[plane].texture.fID =
+ ri->CreateAndConsumeForGpuRaster(holders_[plane].mailbox);
+ if (holders_[plane].mailbox.IsSharedImage()) {
+ textures_[plane].is_shared_image = true;
+ ri->BeginSharedImageAccessDirectCHROMIUM(
+ textures_[plane].texture.fID,
+ for_surface ? GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM
+ : GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
+ } else {
+ textures_[plane].is_shared_image = false;
+ }
+
+ int num_channels = yuva_info_.numChannelsInPlane(plane);
+ textures_[plane].texture.fTarget = holders_[plane].texture_target;
+ textures_[plane].texture.fFormat = PlaneGLFormat(num_channels, for_surface);
+ }
+
+ imported_textures_ = true;
+}
+
+void VideoFrameYUVMailboxesHolder::ReleaseTextures() {
+ if (!imported_textures_)
+ return;
+
+ auto* ri = provider_->RasterInterface();
+ DCHECK(ri);
+ for (auto& tex_info : textures_) {
+ if (!tex_info.texture.fID)
+ continue;
+
+ if (tex_info.is_shared_image)
+ ri->EndSharedImageAccessDirectCHROMIUM(tex_info.texture.fID);
+ ri->DeleteGpuRasterTexture(tex_info.texture.fID);
+
+ tex_info.texture.fID = 0;
+ }
+
+ imported_textures_ = false;
+}
+
+// static
+std::tuple<SkYUVAInfo::PlaneConfig, SkYUVAInfo::Subsampling>
+VideoFrameYUVMailboxesHolder::VideoPixelFormatToSkiaValues(
+ VideoPixelFormat video_format) {
+ // To expand support for additional VideoFormats expand this switch. Note that
+ // we do assume 8 bit formats. With that exception, anything else should work.
+ switch (video_format) {
+ case PIXEL_FORMAT_NV12:
+ return {SkYUVAInfo::PlaneConfig::kY_UV, SkYUVAInfo::Subsampling::k420};
+ case PIXEL_FORMAT_I420:
+ return {SkYUVAInfo::PlaneConfig::kY_U_V, SkYUVAInfo::Subsampling::k420};
+ case PIXEL_FORMAT_I420A:
+ return {SkYUVAInfo::PlaneConfig::kY_U_V_A, SkYUVAInfo::Subsampling::k420};
+ default:
+ return {SkYUVAInfo::PlaneConfig::kUnknown,
+ SkYUVAInfo::Subsampling::kUnknown};
+ }
+}
+
+// static
+SkYUVAInfo VideoFrameYUVMailboxesHolder::VideoFrameGetSkYUVAInfo(
+ const VideoFrame* video_frame) {
+ SkISize video_size{video_frame->coded_size().width(),
+ video_frame->coded_size().height()};
+ auto plane_config = SkYUVAInfo::PlaneConfig::kUnknown;
+ auto subsampling = SkYUVAInfo::Subsampling::kUnknown;
+ std::tie(plane_config, subsampling) =
+ VideoPixelFormatToSkiaValues(video_frame->format());
+
+ // TODO(crbug.com/828599): This should really default to rec709.
+ SkYUVColorSpace color_space = kRec601_SkYUVColorSpace;
+ video_frame->ColorSpace().ToSkYUVColorSpace(&color_space);
+ return SkYUVAInfo(video_size, plane_config, subsampling, color_space);
+}
+
+} // namespace media