| // Copyright (c) 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/yuv_util.h" |
| |
| #include <GLES3/gl3.h> |
| |
| #include "components/viz/common/gpu/raster_context_provider.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/mailbox_holder.h" |
| #include "gpu/command_buffer/common/shared_image_usage.h" |
| #include "media/base/video_frame.h" |
| #include "third_party/skia/include/core/SkImage.h" |
| #include "third_party/skia/include/gpu/GrContext.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| enum YUVIndex : size_t { |
| kYIndex = 0, |
| kUIndex = 1, |
| kVIndex = 2, |
| }; |
| static constexpr size_t kNumNV12Planes = kUIndex + 1; |
| static constexpr size_t kNumYUVPlanes = kVIndex + 1; |
| |
| using YUVMailboxes = std::array<gpu::MailboxHolder, kNumYUVPlanes>; |
| struct YUVPlaneTextureInfo { |
| GrGLTextureInfo texture = {0, 0}; |
| bool is_shared_image = false; |
| }; |
| using YUVTexturesInfo = std::array<YUVPlaneTextureInfo, kNumYUVPlanes>; |
| |
| class VideoFrameYUVMailboxesHolder { |
| public: |
| VideoFrameYUVMailboxesHolder(const VideoFrame* video_frame, |
| viz::RasterContextProvider* provider, |
| bool import_textures) |
| : provider_(provider) { |
| DCHECK(video_frame); |
| DCHECK(video_frame->HasTextures() || video_frame->IsMappable()); |
| DCHECK(video_frame->format() == PIXEL_FORMAT_I420 | |
| video_frame->format() == PIXEL_FORMAT_NV12) |
| << "VideoFrame has an unsupported YUV format " << video_frame->format(); |
| is_nv12_ = video_frame->format() == PIXEL_FORMAT_NV12; |
| |
| DCHECK(provider_); |
| auto* ri = provider_->RasterInterface(); |
| DCHECK(ri); |
| |
| if (video_frame->HasTextures()) { |
| video_frame_owns_holders_ = true; |
| 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()); |
| } |
| } else { |
| DCHECK(!is_nv12_) << "NV12 CPU backed VideoFrames aren't supported."; |
| video_frame_owns_holders_ = false; |
| gfx::Size y_size = video_frame->coded_size(); |
| gfx::Size uv_size = gfx::Size(y_size.width() / 2, y_size.height() / 2); |
| |
| 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 < kNumYUVPlanes; ++plane) { |
| gfx::Size tex_size = plane == kYIndex ? y_size : uv_size; |
| holders_[plane].mailbox = |
| sii->CreateSharedImage(viz::ResourceFormat::LUMINANCE_8, tex_size, |
| video_frame->ColorSpace(), mailbox_usage); |
| 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()); |
| for (size_t plane = 0; plane < kNumYUVPlanes; ++plane) { |
| gfx::Size tex_size = plane == kYIndex ? y_size : uv_size; |
| SkImageInfo info = |
| SkImageInfo::Make(tex_size.width(), tex_size.height(), |
| kGray_8_SkColorType, kUnknown_SkAlphaType); |
| ri->WritePixels(holders_[plane].mailbox, 0, 0, GL_TEXTURE_2D, |
| video_frame->stride(plane), info, |
| video_frame->data(plane)); |
| } |
| } |
| |
| if (import_textures) { |
| ImportTextures(); |
| } |
| } |
| |
| ~VideoFrameYUVMailboxesHolder() { |
| auto* ri = provider_->RasterInterface(); |
| DCHECK(ri); |
| if (imported_textures_) { |
| 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); |
| } |
| } |
| |
| // Don't destroy shared images we don't own. |
| if (video_frame_owns_holders_) |
| return; |
| |
| 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(); |
| } |
| } |
| |
| bool is_nv12() { return is_nv12_; } |
| |
| const gpu::Mailbox& mailbox(size_t plane) { |
| DCHECK_LE(plane, is_nv12_ ? kNumNV12Planes : kNumYUVPlanes); |
| return holders_[plane].mailbox; |
| } |
| |
| const GrGLTextureInfo& texture(size_t plane) { |
| DCHECK_LE(plane, is_nv12_ ? kNumNV12Planes : kNumYUVPlanes); |
| DCHECK(imported_textures_); |
| return textures_[plane].texture; |
| } |
| |
| private: |
| void ImportTextures() { |
| auto* ri = provider_->RasterInterface(); |
| GrGLenum skia_texture_format = is_nv12_ ? GL_RGB8 : GL_LUMINANCE8_EXT; |
| for (size_t plane = 0; plane < NumPlanes(); ++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, |
| GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM); |
| } |
| |
| textures_[plane].texture.fTarget = holders_[plane].texture_target; |
| textures_[plane].texture.fFormat = skia_texture_format; |
| } |
| |
| imported_textures_ = true; |
| } |
| |
| size_t NumPlanes() { return is_nv12_ ? kNumNV12Planes : kNumYUVPlanes; } |
| |
| viz::RasterContextProvider* provider_ = nullptr; |
| bool imported_textures_ = false; |
| bool video_frame_owns_holders_ = false; |
| bool is_nv12_ = false; |
| |
| YUVMailboxes holders_; |
| YUVTexturesInfo textures_; |
| }; |
| |
| void ConvertFromVideoFrameYUVWithGrContext( |
| const VideoFrame* video_frame, |
| viz::RasterContextProvider* raster_context_provider, |
| const gpu::MailboxHolder& dest_mailbox_holder) { |
| 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); |
| } |
| // Let the SkImage fall out of scope and track the result using dest_tex_id |
| NewSkImageFromVideoFrameYUV(video_frame, raster_context_provider, |
| dest_mailbox_holder.texture_target, dest_tex_id); |
| if (dest_mailbox_holder.mailbox.IsSharedImage()) |
| ri->EndSharedImageAccessDirectCHROMIUM(dest_tex_id); |
| ri->DeleteGpuRasterTexture(dest_tex_id); |
| } |
| |
| SkYUVColorSpace ColorSpaceToSkYUVColorSpace( |
| const gfx::ColorSpace& color_space) { |
| // TODO(hubbe): This should really default to rec709. |
| // https://crbug.com/828599 |
| SkYUVColorSpace sk_color_space = kRec601_SkYUVColorSpace; |
| color_space.ToSkYUVColorSpace(&sk_color_space); |
| return sk_color_space; |
| } |
| |
| } // namespace |
| |
| void ConvertFromVideoFrameYUV( |
| const VideoFrame* video_frame, |
| viz::RasterContextProvider* raster_context_provider, |
| const gpu::MailboxHolder& dest_mailbox_holder) { |
| DCHECK(raster_context_provider); |
| if (raster_context_provider->GrContext()) { |
| ConvertFromVideoFrameYUVWithGrContext(video_frame, raster_context_provider, |
| dest_mailbox_holder); |
| return; |
| } |
| |
| auto* ri = raster_context_provider->RasterInterface(); |
| DCHECK(ri); |
| ri->WaitSyncTokenCHROMIUM(dest_mailbox_holder.sync_token.GetConstData()); |
| SkYUVColorSpace color_space = |
| ColorSpaceToSkYUVColorSpace(video_frame->ColorSpace()); |
| |
| VideoFrameYUVMailboxesHolder yuv_mailboxes(video_frame, |
| raster_context_provider, false); |
| |
| if (yuv_mailboxes.is_nv12()) { |
| ri->ConvertNV12MailboxesToRGB(dest_mailbox_holder.mailbox, color_space, |
| yuv_mailboxes.mailbox(kYIndex), |
| yuv_mailboxes.mailbox(kUIndex)); |
| } else { |
| DCHECK_EQ(video_frame->NumTextures(), kNumYUVPlanes); |
| ri->ConvertYUVMailboxesToRGB(dest_mailbox_holder.mailbox, color_space, |
| yuv_mailboxes.mailbox(kYIndex), |
| yuv_mailboxes.mailbox(kUIndex), |
| yuv_mailboxes.mailbox(kVIndex)); |
| } |
| } |
| |
| sk_sp<SkImage> NewSkImageFromVideoFrameYUV( |
| const VideoFrame* video_frame, |
| viz::RasterContextProvider* raster_context_provider, |
| unsigned int texture_target, |
| unsigned int texture_id) { |
| DCHECK(video_frame->HasTextures() || |
| (video_frame->IsMappable() && |
| video_frame->format() == PIXEL_FORMAT_I420)); |
| GrContext* gr_context = raster_context_provider->GrContext(); |
| DCHECK(gr_context); |
| // TODO: We should compare the DCHECK vs when UpdateLastImage calls this |
| // function. (https://crbug.com/674185) |
| DCHECK(video_frame->format() == PIXEL_FORMAT_I420 || |
| video_frame->format() == PIXEL_FORMAT_NV12); |
| |
| gfx::Size ya_tex_size = video_frame->coded_size(); |
| gfx::Size uv_tex_size((ya_tex_size.width() + 1) / 2, |
| (ya_tex_size.height() + 1) / 2); |
| |
| GrGLTextureInfo backend_texture{}; |
| |
| VideoFrameYUVMailboxesHolder yuv_textures_info(video_frame, |
| raster_context_provider, true); |
| |
| GrBackendTexture yuv_textures[3] = { |
| GrBackendTexture(ya_tex_size.width(), ya_tex_size.height(), |
| GrMipMapped::kNo, yuv_textures_info.texture(kYIndex)), |
| GrBackendTexture(uv_tex_size.width(), uv_tex_size.height(), |
| GrMipMapped::kNo, yuv_textures_info.texture(kUIndex)), |
| GrBackendTexture(uv_tex_size.width(), uv_tex_size.height(), |
| GrMipMapped::kNo, yuv_textures_info.texture(kVIndex)), |
| }; |
| backend_texture.fID = texture_id; |
| backend_texture.fTarget = texture_target; |
| backend_texture.fFormat = GL_RGBA8; |
| GrBackendTexture result_texture(video_frame->coded_size().width(), |
| video_frame->coded_size().height(), |
| GrMipMapped::kNo, backend_texture); |
| |
| sk_sp<SkImage> img = YUVGrBackendTexturesToSkImage( |
| gr_context, video_frame->ColorSpace(), video_frame->format(), |
| yuv_textures, result_texture); |
| gr_context->flushAndSubmit(); |
| |
| return img; |
| } |
| |
| sk_sp<SkImage> YUVGrBackendTexturesToSkImage( |
| GrContext* gr_context, |
| gfx::ColorSpace video_color_space, |
| VideoPixelFormat video_format, |
| GrBackendTexture* yuv_textures, |
| const GrBackendTexture& result_texture) { |
| SkYUVColorSpace color_space = ColorSpaceToSkYUVColorSpace(video_color_space); |
| |
| switch (video_format) { |
| case PIXEL_FORMAT_NV12: |
| return SkImage::MakeFromNV12TexturesCopyWithExternalBackend( |
| gr_context, color_space, yuv_textures, kTopLeft_GrSurfaceOrigin, |
| result_texture); |
| case PIXEL_FORMAT_I420: |
| return SkImage::MakeFromYUVTexturesCopyWithExternalBackend( |
| gr_context, color_space, yuv_textures, kTopLeft_GrSurfaceOrigin, |
| result_texture); |
| default: |
| NOTREACHED(); |
| return nullptr; |
| } |
| } |
| |
| } // namespace media |