#include "media/renderers/video_resource_updater.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <array>
#include <string>
#include <vector>
#include "base/atomic_sequence_num.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "cc/base/math_util.h"
#include "cc/paint/skia_paint_canvas.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/features.h"
#include "components/viz/common/gpu/raster_context_provider.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/quads/video_hole_draw_quad.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/context_support.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_capabilities.h"
#include "gpu/command_buffer/common/shared_image_trace_utils.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "media/base/media_switches.h"
#include "media/base/video_util.h"
#include "media/base/wait_and_replace_sync_token_client.h"
#include "media/renderers/paint_canvas_video_renderer.h"
#include "media/renderers/resource_sync_token_client.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/khronos/GLES2/gl2ext.h"
#include "third_party/khronos/GLES3/gl3.h"
#include "third_party/libyuv/include/libyuv.h"
#include "third_party/skia/include/core/SkYUVAInfo.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/video_types.h"
#include "ui/gl/gl_enums.h"
#include "ui/gl/trace_util.h"
#if BUILDFLAG(ARKWEB_SAME_LAYER)
#include "base/ohos/sys_info_utils_ext.h"
#endif
namespace media {
namespace {
base::AtomicSequenceNumber g_next_video_resource_updater_id;
gfx::ProtectedVideoType ProtectedVideoTypeFromMetadata(
const VideoFrameMetadata& metadata) {
if (metadata.dcomp_surface) {
return gfx::ProtectedVideoType::kHardwareProtected;
}
if (!metadata.protected_video) {
return gfx::ProtectedVideoType::kClear;
}
return metadata.hw_protected ? gfx::ProtectedVideoType::kHardwareProtected
: gfx::ProtectedVideoType::kSoftwareProtected;
}
VideoFrameResourceType ExternalResourceTypeForHardware(
const VideoFrame& frame) {
bool si_prefers_external_sampler =
frame.shared_image()->format().PrefersExternalSampler();
if (si_prefers_external_sampler) {
return VideoFrameResourceType::RGB;
}
#if BUILDFLAG(IS_ANDROID)
if (frame.shared_image()->GetTextureTarget() == GL_TEXTURE_EXTERNAL_OES &&
frame.shared_image()->format() == viz::SinglePlaneFormat::kRGBA_8888) {
return VideoFrameResourceType::RGB;
}
#endif
const VideoPixelFormat format = frame.format();
switch (format) {
case PIXEL_FORMAT_XRGB:
return VideoFrameResourceType::RGB;
case PIXEL_FORMAT_ARGB:
case PIXEL_FORMAT_ABGR:
case PIXEL_FORMAT_XBGR:
case PIXEL_FORMAT_BGRA:
return VideoFrameResourceType::RGBA_PREMULTIPLIED;
case PIXEL_FORMAT_XR30:
case PIXEL_FORMAT_XB30:
case PIXEL_FORMAT_I420:
case PIXEL_FORMAT_YV12:
case PIXEL_FORMAT_NV12:
case PIXEL_FORMAT_NV16:
case PIXEL_FORMAT_NV24:
case PIXEL_FORMAT_NV12A:
case PIXEL_FORMAT_P010LE:
case PIXEL_FORMAT_P210LE:
case PIXEL_FORMAT_P410LE:
case PIXEL_FORMAT_RGBAF16:
return VideoFrameResourceType::RGB;
case PIXEL_FORMAT_UYVY:
NOTREACHED();
case PIXEL_FORMAT_I422:
case PIXEL_FORMAT_I444:
case PIXEL_FORMAT_I420A:
case PIXEL_FORMAT_NV21:
case PIXEL_FORMAT_YUY2:
case PIXEL_FORMAT_RGB24:
case PIXEL_FORMAT_MJPEG:
case PIXEL_FORMAT_YUV420P10:
case PIXEL_FORMAT_YUV422P10:
case PIXEL_FORMAT_YUV444P10:
case PIXEL_FORMAT_YUV420P12:
case PIXEL_FORMAT_YUV422P12:
case PIXEL_FORMAT_YUV444P12:
case PIXEL_FORMAT_Y16:
case PIXEL_FORMAT_I422A:
case PIXEL_FORMAT_I444A:
case PIXEL_FORMAT_YUV420AP10:
case PIXEL_FORMAT_YUV422AP10:
case PIXEL_FORMAT_YUV444AP10:
case PIXEL_FORMAT_UNKNOWN:
break;
}
return VideoFrameResourceType::NONE;
}
viz::SharedImageFormat GetRGBSharedImageFormat(VideoPixelFormat format) {
#if BUILDFLAG(IS_MAC)
return PaintCanvasVideoRenderer::GetRGBPixelsOutputFormat();
#else
switch (format) {
case PIXEL_FORMAT_XBGR:
case PIXEL_FORMAT_ABGR:
return viz::SinglePlaneFormat::kRGBA_8888;
case PIXEL_FORMAT_XRGB:
case PIXEL_FORMAT_ARGB:
return viz::SinglePlaneFormat::kBGRA_8888;
default:
NOTREACHED();
}
#endif
}
bool HasCompatibleRGBFormat(VideoPixelFormat input_format,
viz::SharedImageFormat output_format) {
switch (input_format) {
case PIXEL_FORMAT_XBGR:
return output_format == viz::SinglePlaneFormat::kRGBA_8888 ||
output_format == viz::SinglePlaneFormat::kRGBX_8888;
case PIXEL_FORMAT_ABGR:
return output_format == viz::SinglePlaneFormat::kRGBA_8888;
case PIXEL_FORMAT_XRGB:
return output_format == viz::SinglePlaneFormat::kBGRA_8888 ||
output_format == viz::SinglePlaneFormat::kBGRX_8888;
case PIXEL_FORMAT_ARGB:
return output_format == viz::SinglePlaneFormat::kBGRA_8888;
case PIXEL_FORMAT_RGBAF16:
return output_format == viz::SinglePlaneFormat::kRGBA_F16;
default:
return false;
}
}
bool IsFrameFormat32BitRGB(VideoPixelFormat frame_format) {
return frame_format == PIXEL_FORMAT_XBGR ||
frame_format == PIXEL_FORMAT_XRGB ||
frame_format == PIXEL_FORMAT_ABGR || frame_format == PIXEL_FORMAT_ARGB;
}
viz::SharedImageFormat::ChannelFormat SupportedMultiPlaneChannelFormat(
const gpu::Capabilities& caps,
const gpu::SharedImageCapabilities& shared_image_caps,
int bits_per_channel) {
if (bits_per_channel <= 8) {
DCHECK(shared_image_caps.supports_luminance_shared_images ||
caps.texture_rg);
return viz::SharedImageFormat::ChannelFormat::k8;
}
if (caps.texture_norm16 && shared_image_caps.supports_r16_shared_images) {
return viz::SharedImageFormat::ChannelFormat::k16;
}
if (shared_image_caps.is_r16f_supported ||
(caps.texture_half_float_linear &&
shared_image_caps.supports_luminance_shared_images)) {
return viz::SharedImageFormat::ChannelFormat::k16F;
}
return viz::SharedImageFormat::ChannelFormat::k8;
}
viz::SharedImageFormat VideoPixelFormatToMultiPlanarSharedImageFormat(
VideoPixelFormat input_format) {
using PlaneConfig = viz::SharedImageFormat::PlaneConfig;
using Subsampling = viz::SharedImageFormat::Subsampling;
using ChannelFormat = viz::SharedImageFormat::ChannelFormat;
switch (input_format) {
case PIXEL_FORMAT_I420:
return viz::MultiPlaneFormat::kI420;
case PIXEL_FORMAT_YV12:
return viz::MultiPlaneFormat::kYV12;
case PIXEL_FORMAT_I422:
return viz::SharedImageFormat::MultiPlane(
PlaneConfig::kY_U_V, Subsampling::k422, ChannelFormat::k8);
case PIXEL_FORMAT_I444:
return viz::SharedImageFormat::MultiPlane(
PlaneConfig::kY_U_V, Subsampling::k444, ChannelFormat::k8);
case PIXEL_FORMAT_NV12:
return viz::MultiPlaneFormat::kNV12;
case PIXEL_FORMAT_YUV420P10:
return viz::SharedImageFormat::MultiPlane(
PlaneConfig::kY_U_V, Subsampling::k420, ChannelFormat::k10);
case PIXEL_FORMAT_YUV422P10:
return viz::SharedImageFormat::MultiPlane(
PlaneConfig::kY_U_V, Subsampling::k422, ChannelFormat::k10);
case PIXEL_FORMAT_YUV444P10:
return viz::SharedImageFormat::MultiPlane(
PlaneConfig::kY_U_V, Subsampling::k444, ChannelFormat::k10);
case PIXEL_FORMAT_YUV420P12:
return viz::SharedImageFormat::MultiPlane(
PlaneConfig::kY_U_V, Subsampling::k420, ChannelFormat::k16);
case PIXEL_FORMAT_YUV422P12:
return viz::SharedImageFormat::MultiPlane(
PlaneConfig::kY_U_V, Subsampling::k422, ChannelFormat::k16);
case PIXEL_FORMAT_YUV444P12:
return viz::SharedImageFormat::MultiPlane(
PlaneConfig::kY_U_V, Subsampling::k444, ChannelFormat::k16);
case PIXEL_FORMAT_NV12A:
return viz::MultiPlaneFormat::kNV12A;
case PIXEL_FORMAT_I420A:
return viz::MultiPlaneFormat::kI420A;
case PIXEL_FORMAT_NV16:
case PIXEL_FORMAT_NV24:
case PIXEL_FORMAT_P010LE:
case PIXEL_FORMAT_P210LE:
case PIXEL_FORMAT_P410LE:
case PIXEL_FORMAT_ARGB:
case PIXEL_FORMAT_XRGB:
case PIXEL_FORMAT_ABGR:
case PIXEL_FORMAT_XBGR:
case PIXEL_FORMAT_NV21:
case PIXEL_FORMAT_UYVY:
case PIXEL_FORMAT_YUY2:
case PIXEL_FORMAT_RGB24:
case PIXEL_FORMAT_MJPEG:
case PIXEL_FORMAT_Y16:
case PIXEL_FORMAT_XR30:
case PIXEL_FORMAT_XB30:
case PIXEL_FORMAT_BGRA:
case PIXEL_FORMAT_RGBAF16:
case PIXEL_FORMAT_I422A:
case PIXEL_FORMAT_I444A:
case PIXEL_FORMAT_YUV420AP10:
case PIXEL_FORMAT_YUV422AP10:
case PIXEL_FORMAT_YUV444AP10:
case PIXEL_FORMAT_UNKNOWN:
NOTREACHED();
}
}
std::vector<VideoFrame::Plane> GetVideoFramePlanes(
viz::SharedImageFormat format) {
CHECK(format.is_multi_plane());
switch (format.plane_config()) {
case viz::SharedImageFormat::PlaneConfig::kY_U_V:
return {VideoFrame::Plane::kY, VideoFrame::Plane::kU,
VideoFrame::Plane::kV};
case viz::SharedImageFormat::PlaneConfig::kY_V_U:
return {VideoFrame::Plane::kY, VideoFrame::Plane::kV,
VideoFrame::Plane::kU};
case viz::SharedImageFormat::PlaneConfig::kY_UV:
return {VideoFrame::Plane::kY, VideoFrame::Plane::kUV};
case viz::SharedImageFormat::PlaneConfig::kY_UV_A:
return {VideoFrame::Plane::kY, VideoFrame::Plane::kUV,
VideoFrame::Plane::kATriPlanar};
case viz::SharedImageFormat::PlaneConfig::kY_U_V_A:
return {VideoFrame::Plane::kY, VideoFrame::Plane::kU,
VideoFrame::Plane::kV, VideoFrame::Plane::kA};
}
NOTREACHED();
}
class CopyingSyncTokenClient : public VideoFrame::SyncTokenClient {
public:
CopyingSyncTokenClient() = default;
CopyingSyncTokenClient(const CopyingSyncTokenClient&) = delete;
CopyingSyncTokenClient& operator=(const CopyingSyncTokenClient&) = delete;
~CopyingSyncTokenClient() override = default;
void GenerateSyncToken(gpu::SyncToken* sync_token) override {
*sync_token = sync_token_;
}
void WaitSyncToken(const gpu::SyncToken& sync_token) override {
sync_token_ = sync_token;
}
private:
gpu::SyncToken sync_token_;
};
}
VideoFrameExternalResource::VideoFrameExternalResource() = default;
VideoFrameExternalResource::~VideoFrameExternalResource() = default;
VideoFrameExternalResource::VideoFrameExternalResource(
VideoFrameExternalResource&& other) = default;
VideoFrameExternalResource& VideoFrameExternalResource::operator=(
VideoFrameExternalResource&& other) = default;
class VideoResourceUpdater::FrameResource {
public:
FrameResource(uint32_t frame_resource_id,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
gpu::SharedImageInterface* shared_image_interface)
: id_(frame_resource_id), is_software_(true) {
DCHECK(shared_image_interface);
shared_image_ =
shared_image_interface->CreateSharedImageForSoftwareCompositor(
{viz::SinglePlaneFormat::kBGRA_8888, size, color_space,
gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "VideoResourceUpdater"});
mapping_ = shared_image_->Map();
sync_token_ = shared_image_interface->GenVerifiedSyncToken();
}
FrameResource(uint32_t frame_resource_id,
const gfx::Size& size,
viz::SharedImageFormat format,
const gfx::ColorSpace& color_space,
SkAlphaType alpha_type,
bool use_gpu_memory_buffer_resources,
gpu::SharedImageInterface* shared_image_interface)
: id_(frame_resource_id), is_software_(false) {
DCHECK(shared_image_interface);
const bool overlay_candidate =
format.is_single_plane() && use_gpu_memory_buffer_resources &&
shared_image_interface->GetCapabilities()
.supports_scanout_shared_images &&
CanCreateGpuMemoryBufferForSinglePlaneSharedImageFormat(format);
gpu::SharedImageUsageSet shared_image_usage =
gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
gpu::SHARED_IMAGE_USAGE_RASTER_WRITE;
if (overlay_candidate) {
shared_image_usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
}
shared_image_ = shared_image_interface->CreateSharedImage(
{format, size, color_space, kTopLeft_GrSurfaceOrigin, alpha_type,
shared_image_usage, "VideoResourceUpdater"},
gpu::kNullSurfaceHandle);
CHECK(shared_image_);
sync_token_ = shared_image_->creation_sync_token();
}
FrameResource(const FrameResource&) = delete;
FrameResource& operator=(const FrameResource&) = delete;
~FrameResource() {
DCHECK(shared_image_);
shared_image_->UpdateDestructionSyncToken(sync_token_);
}
bool Equals(const gfx::Size& size,
viz::SharedImageFormat format,
const gfx::ColorSpace& color_space,
SkAlphaType alpha_type) {
return size == shared_image_->size() && format == shared_image_->format() &&
color_space == shared_image_->color_space() &&
alpha_type == shared_image_->alpha_type();
}
bool Matches(VideoFrame::ID unique_frame_id) {
CHECK(!unique_frame_id.is_null());
return unique_frame_id_ == unique_frame_id;
}
void SetUniqueId(VideoFrame::ID unique_frame_id) {
DCHECK_EQ(ref_count_, 1);
unique_frame_id_ = unique_frame_id;
}
void UpdateSyncToken(const gpu::SyncToken& sync_token) {
sync_token_ = sync_token;
}
SkPixmap pixmap() {
CHECK(is_software());
return mapping_->GetSkPixmapForPlane(
0,
SkImageInfo::MakeN32Premul(gfx::SizeToSkISize(size())));
}
uint32_t id() const { return id_; }
gfx::Size size() const { return shared_image_->size(); }
viz::SharedImageFormat format() const { return shared_image_->format(); }
bool is_software() const { return is_software_; }
const scoped_refptr<gpu::ClientSharedImage>& shared_image() const {
return shared_image_;
}
const gpu::SyncToken& sync_token() { return sync_token_; }
void add_ref() { ++ref_count_; }
void remove_ref() { --ref_count_; }
bool has_refs() const { return ref_count_ != 0; }
private:
const uint32_t id_;
const bool is_software_;
int ref_count_ = 0;
VideoFrame::ID unique_frame_id_;
gpu::SyncToken sync_token_;
scoped_refptr<gpu::ClientSharedImage> shared_image_;
std::unique_ptr<gpu::ClientSharedImage::ScopedMapping> mapping_;
};
VideoResourceUpdater::VideoResourceUpdater(
viz::RasterContextProvider* context_provider,
viz::ClientResourceProvider* resource_provider,
scoped_refptr<gpu::SharedImageInterface> shared_image_interface,
bool use_gpu_memory_buffer_resources,
int max_resource_size)
: context_provider_(context_provider),
shared_image_interface_(std::move(shared_image_interface)),
resource_provider_(resource_provider),
use_gpu_memory_buffer_resources_(use_gpu_memory_buffer_resources),
max_resource_size_(max_resource_size),
tracing_id_(g_next_video_resource_updater_id.GetNext()) {
DCHECK(context_provider_ || shared_image_interface_);
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "media::VideoResourceUpdater",
base::SingleThreadTaskRunner::GetCurrentDefault());
}
VideoResourceUpdater::~VideoResourceUpdater() {
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this);
}
void VideoResourceUpdater::ObtainFrameResource(
scoped_refptr<VideoFrame> video_frame) {
UMA_HISTOGRAM_ENUMERATION("Media.VideoResourceUpdater.FrameFormat",
video_frame->format(), PIXEL_FORMAT_MAX + 1);
if (video_frame->storage_type() == VideoFrame::STORAGE_OPAQUE &&
video_frame->format() == VideoPixelFormat::PIXEL_FORMAT_UNKNOWN &&
video_frame->metadata().tracking_token.has_value()) {
overlay_plane_id_ = *video_frame->metadata().tracking_token;
frame_resource_type_ = VideoFrameResourceType::VIDEO_HOLE;
return;
}
VideoFrameExternalResource external_resource =
CreateExternalResourceFromVideoFrame(video_frame);
frame_resource_type_ = external_resource.type;
if (external_resource.resource.is_empty()) {
DLOG(ERROR) << "external_resource is empty.";
frame_resource_id_ = viz::kInvalidResourceId;
return;
}
frame_resource_id_ = resource_provider_->ImportResource(
external_resource.resource,
std::move(external_resource.release_callback));
TRACE_EVENT_INSTANT1("media", "VideoResourceUpdater::ObtainFrameResource",
TRACE_EVENT_SCOPE_THREAD, "Timestamp",
video_frame->timestamp().InMicroseconds());
}
void VideoResourceUpdater::ReleaseFrameResource() {
if (!frame_resource_id_.is_null()) {
resource_provider_->RemoveImportedResource(frame_resource_id_);
}
frame_resource_id_ = viz::ResourceId();
}
void VideoResourceUpdater::AppendQuad(
viz::CompositorRenderPass* render_pass,
scoped_refptr<VideoFrame> frame,
gfx::Transform transform,
gfx::Rect quad_rect,
gfx::Rect visible_quad_rect,
const gfx::MaskFilterInfo& mask_filter_info,
std::optional<gfx::Rect> clip_rect,
bool contents_opaque,
float draw_opacity,
int sorting_context_id) {
DCHECK(frame.get());
viz::SharedQuadState* shared_quad_state =
render_pass->CreateAndAppendSharedQuadState();
shared_quad_state->SetAll(
transform, quad_rect, visible_quad_rect, mask_filter_info, clip_rect,
contents_opaque, draw_opacity, SkBlendMode::kSrcOver, sorting_context_id,
0u, false);
bool needs_blending = !contents_opaque;
gfx::Rect visible_rect = frame->visible_rect();
gfx::Size coded_size = frame->coded_size();
const gfx::PointF uv_top_left(
static_cast<float>(visible_rect.x()) / coded_size.width(),
static_cast<float>(visible_rect.y()) / coded_size.height());
const gfx::PointF uv_bottom_right(
static_cast<float>(visible_rect.right()) / coded_size.width(),
static_cast<float>(visible_rect.bottom()) / coded_size.height());
switch (frame_resource_type_) {
case VideoFrameResourceType::VIDEO_HOLE: {
auto* video_hole_quad =
render_pass->CreateAndAppendDrawQuad<viz::VideoHoleDrawQuad>();
video_hole_quad->SetNew(shared_quad_state, quad_rect, visible_quad_rect,
overlay_plane_id_);
break;
}
case VideoFrameResourceType::RGBA_PREMULTIPLIED:
case VideoFrameResourceType::RGB: {
if (frame_resource_id_.is_null()) {
break;
}
bool nearest_neighbor = false;
gfx::ProtectedVideoType protected_video_type =
ProtectedVideoTypeFromMetadata(frame->metadata());
auto* texture_quad =
render_pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>();
texture_quad->SetNew(shared_quad_state, quad_rect, visible_quad_rect,
needs_blending, frame_resource_id_, uv_top_left,
uv_bottom_right, SkColors::kTransparent,
nearest_neighbor, false, protected_video_type);
#if BUILDFLAG(IS_WIN)
if (frame->metadata().dcomp_surface) {
texture_quad->overlay_priority_hint = viz::OverlayPriority::kRequired;
}
#endif
texture_quad->is_video_frame = true;
resource_provider_->ValidateResource(texture_quad->resource_id);
break;
}
case VideoFrameResourceType::NONE:
NOTIMPLEMENTED();
break;
}
}
void VideoResourceUpdater::ClearFrameResources() {
std::erase_if(all_resources_,
[](const std::unique_ptr<FrameResource>& resource) {
return !resource->has_refs();
});
}
VideoFrameExternalResource
VideoResourceUpdater::CreateExternalResourceFromVideoFrame(
scoped_refptr<VideoFrame> video_frame) {
if (video_frame->format() == PIXEL_FORMAT_UNKNOWN)
return VideoFrameExternalResource();
DCHECK(video_frame->HasSharedImage() || video_frame->IsMappable());
if (video_frame->HasSharedImage()) {
return CreateForHardwareFrame(std::move(video_frame));
} else {
return CreateForSoftwareFrame(std::move(video_frame));
}
}
bool VideoResourceUpdater::ReallocateUploadPixels(size_t needed_size,
size_t plane) {
upload_pixels_[plane] = PlaneData();
uint8_t* pixel_mem = nullptr;
if (!base::UncheckedMalloc(needed_size,
reinterpret_cast<void**>(&pixel_mem))) {
DLOG(ERROR) << "Unable to allocate enough memory required to "
"upload pixels";
return false;
}
upload_pixels_[plane] =
UNSAFE_BUFFERS(PlaneData::FromOwningPointer(pixel_mem, needed_size));
return true;
}
VideoResourceUpdater::FrameResource*
VideoResourceUpdater::RecycleOrAllocateResource(
const gfx::Size& resource_size,
viz::SharedImageFormat si_format,
const gfx::ColorSpace& color_space,
SkAlphaType alpha_type,
VideoFrame::ID unique_id) {
FrameResource* recyclable_resource = nullptr;
for (auto& resource : all_resources_) {
if (!unique_id.is_null() && resource->Matches(unique_id)) {
DCHECK(
resource->Equals(resource_size, si_format, color_space, alpha_type));
return resource.get();
}
const bool in_use = resource->has_refs();
if (!in_use &&
resource->Equals(resource_size, si_format, color_space, alpha_type)) {
recyclable_resource = resource.get();
}
}
if (recyclable_resource) {
return recyclable_resource;
}
return AllocateResource(resource_size, si_format, color_space, alpha_type);
}
VideoResourceUpdater::FrameResource* VideoResourceUpdater::AllocateResource(
const gfx::Size& size,
viz::SharedImageFormat format,
const gfx::ColorSpace& color_space,
SkAlphaType alpha_type) {
const uint32_t resource_id = next_plane_resource_id_++;
if (software_compositor()) {
DCHECK_EQ(format, viz::SinglePlaneFormat::kBGRA_8888);
DCHECK_EQ(alpha_type, kPremul_SkAlphaType);
all_resources_.push_back(std::make_unique<FrameResource>(
resource_id, size, color_space, shared_image_interface()));
} else {
all_resources_.push_back(std::make_unique<FrameResource>(
resource_id, size, format, color_space, alpha_type,
use_gpu_memory_buffer_resources_,
context_provider_->SharedImageInterface()));
}
return all_resources_.back().get();
}
VideoFrameExternalResource VideoResourceUpdater::CopyHardwareResource(
VideoFrame* video_frame) {
#if BUILDFLAG(IS_ARKWEB)
TRACE_EVENT0("cc", "VideoResourceUpdater::CopyHardwareResource");
#endif
VideoFrameExternalResource external_resource;
external_resource.type = VideoFrameResourceType::RGBA_PREMULTIPLIED;
const gfx::Size output_resource_size = video_frame->coded_size();
auto shared_image = video_frame->shared_image();
constexpr viz::SharedImageFormat copy_si_format =
viz::SinglePlaneFormat::kRGBA_8888;
const auto copy_color_space = video_frame->ColorSpace().GetAsFullRangeRGB();
const SkAlphaType copy_alpha_type = kPremul_SkAlphaType;
const VideoFrame::ID no_unique_id;
FrameResource* hardware_resource = RecycleOrAllocateResource(
output_resource_size, copy_si_format, copy_color_space, copy_alpha_type,
no_unique_id);
CHECK(!hardware_resource->is_software());
hardware_resource->add_ref();
auto* ri = RasterInterface();
std::unique_ptr<gpu::RasterScopedAccess> src_ri_access =
shared_image->BeginRasterAccess(ri, video_frame->acquire_sync_token(),
true);
std::unique_ptr<gpu::RasterScopedAccess> dst_ri_access =
hardware_resource->shared_image()->BeginRasterAccess(
ri, hardware_resource->sync_token(),
false);
ri->CopySharedImage(
shared_image->mailbox(), hardware_resource->shared_image()->mailbox(),
0, 0, 0, 0,
output_resource_size.width(), output_resource_size.height());
WaitAndReplaceSyncTokenClient client(ri, std::move(src_ri_access));
gpu::SyncToken sync_token = video_frame->UpdateReleaseSyncToken(&client);
hardware_resource->UpdateSyncToken(sync_token);
gpu::RasterScopedAccess::EndAccess(std::move(dst_ri_access));
viz::TransferableResource::MetadataOverride overrides = {
.is_overlay_candidate = false,
};
auto transferable_resource = viz::TransferableResource::Make(
hardware_resource->shared_image(),
viz::TransferableResource::ResourceSource::kVideo,
hardware_resource->sync_token(), overrides);
transferable_resource.hdr_metadata =
video_frame->hdr_metadata().value_or(gfx::HDRMetadata());
transferable_resource.needs_detiling = video_frame->metadata().needs_detiling;
external_resource.resource = std::move(transferable_resource);
external_resource.release_callback =
base::BindOnce(&VideoResourceUpdater::RecycleResource,
weak_ptr_factory_.GetWeakPtr(), hardware_resource->id());
return external_resource;
}
VideoFrameExternalResource VideoResourceUpdater::CreateForHardwareFrame(
scoped_refptr<VideoFrame> video_frame) {
TRACE_EVENT0("media", "VideoResourceUpdater::CreateForHardwareFrame");
if (!context_provider_) {
return VideoFrameExternalResource();
}
TRACE_EVENT2("media", "VideoResourceUpdater::CreateForHardwarePlanes",
"copy_required", video_frame->metadata().copy_required, "format", VideoPixelFormatToString(video_frame->format()));
if (video_frame->metadata().copy_required) {
return CopyHardwareResource(video_frame.get());
}
VideoFrameExternalResource external_resource;
auto shared_image = video_frame->shared_image();
external_resource.type = ExternalResourceTypeForHardware(*video_frame);
if (external_resource.type == VideoFrameResourceType::NONE) {
DLOG(ERROR) << "Unsupported Texture format"
<< VideoPixelFormatToString(video_frame->format());
return external_resource;
}
CopyingSyncTokenClient client;
auto original_release_token = video_frame->UpdateReleaseSyncToken(&client);
SkAlphaType alpha_type =
(external_resource.type == VideoFrameResourceType::RGBA_PREMULTIPLIED)
? kPremul_SkAlphaType
: kUnpremul_SkAlphaType;
viz::TransferableResource::MetadataOverride overrides = {
.is_overlay_candidate = video_frame->metadata().allow_overlay,
.color_space = video_frame->ColorSpace(),
.alpha_type = alpha_type,
};
auto transfer_resource = viz::TransferableResource::Make(
shared_image, viz::TransferableResource::ResourceSource::kVideo,
video_frame->acquire_sync_token(), overrides);
transfer_resource.hdr_metadata =
video_frame->hdr_metadata().value_or(gfx::HDRMetadata());
transfer_resource.needs_detiling = video_frame->metadata().needs_detiling;
if (video_frame->metadata().read_lock_fences_enabled) {
transfer_resource.synchronization_type =
viz::TransferableResource::SynchronizationType::kGpuCommandsCompleted;
}
#if BUILDFLAG(IS_ANDROID)
transfer_resource.ycbcr_info = video_frame->ycbcr_info();
transfer_resource.is_backed_by_surface_view =
video_frame->metadata().in_surface_view;
#endif
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
transfer_resource.wants_promotion_hint =
video_frame->metadata().wants_promotion_hint;
#endif
external_resource.resource = std::move(transfer_resource);
external_resource.release_callback = base::BindOnce(
&VideoResourceUpdater::ReturnTexture, weak_ptr_factory_.GetWeakPtr(),
video_frame, original_release_token);
return external_resource;
}
viz::SharedImageFormat VideoResourceUpdater::GetSoftwareOutputFormat(
VideoPixelFormat input_frame_format,
int bits_per_channel) {
if (software_compositor()) {
return viz::SinglePlaneFormat::kBGRA_8888;
}
if (IsFrameFormat32BitRGB(input_frame_format)) {
return GetRGBSharedImageFormat(input_frame_format);
}
if (input_frame_format == PIXEL_FORMAT_RGBAF16) {
return viz::SinglePlaneFormat::kRGBA_F16;
}
if (input_frame_format == PIXEL_FORMAT_Y16) {
return PaintCanvasVideoRenderer::GetRGBPixelsOutputFormat();
}
const auto& shared_image_caps =
context_provider_->SharedImageInterface()->GetCapabilities();
if (shared_image_caps.disable_one_component_textures) {
return PaintCanvasVideoRenderer::GetRGBPixelsOutputFormat();
}
const auto& caps = context_provider_->ContextCapabilities();
auto yuv_si_format =
VideoPixelFormatToMultiPlanarSharedImageFormat(input_frame_format);
if (yuv_si_format.plane_config() ==
viz::SharedImageFormat::PlaneConfig::kY_UV) {
CHECK_EQ(yuv_si_format.channel_format(),
viz::SharedImageFormat::ChannelFormat::k8);
if (!caps.texture_rg || shared_image_caps.disable_r8_shared_images) {
return PaintCanvasVideoRenderer::GetRGBPixelsOutputFormat();
}
}
auto channel_format = SupportedMultiPlaneChannelFormat(
caps, shared_image_caps, bits_per_channel);
if (yuv_si_format.channel_format() != channel_format) {
yuv_si_format = viz::SharedImageFormat::MultiPlane(
yuv_si_format.plane_config(), yuv_si_format.subsampling(),
channel_format);
}
return yuv_si_format;
}
void VideoResourceUpdater::TransferRGBPixelsToPaintCanvas(
scoped_refptr<VideoFrame> video_frame,
FrameResource* software_resource) {
if (!video_renderer_) {
video_renderer_ = std::make_unique<PaintCanvasVideoRenderer>();
}
CHECK(software_resource->is_software());
DCHECK_EQ(software_resource->format(), viz::SinglePlaneFormat::kBGRA_8888);
SkBitmap sk_bitmap;
sk_bitmap.installPixels(software_resource->pixmap());
cc::SkiaPaintCanvas canvas(sk_bitmap);
cc::PaintFlags flags;
flags.setBlendMode(SkBlendMode::kSrc);
flags.setFilterQuality(cc::PaintFlags::FilterQuality::kLow);
PaintCanvasVideoRenderer::PaintParams paint_params;
paint_params.dest_rect = gfx::RectF(video_frame->visible_rect());
video_renderer_->Paint(video_frame, &canvas, flags, paint_params,
nullptr);
}
bool VideoResourceUpdater::WriteRGBPixelsToTexture(
scoped_refptr<VideoFrame> video_frame,
FrameResource* hardware_resource) {
CHECK(!hardware_resource->is_software());
viz::SharedImageFormat resource_format = hardware_resource->format();
size_t bytes_per_row =
viz::SharedMemoryRowSizeForSharedImageFormat(
resource_format, 0, video_frame->coded_size().width())
.value();
const int stride = video_frame->stride(VideoFrame::Plane::kARGB);
const bool has_compatible_stride =
stride > 0 && static_cast<size_t>(stride) == bytes_per_row;
const uint8_t* source_pixels = nullptr;
if (HasCompatibleRGBFormat(video_frame->format(), resource_format) &&
has_compatible_stride) {
source_pixels = video_frame->data(VideoFrame::Plane::kARGB);
} else {
size_t needed_size = bytes_per_row * video_frame->coded_size().height();
if (upload_pixels_[0].size() < needed_size) {
if (!ReallocateUploadPixels(needed_size, 0)) {
return false;
}
}
auto* dest_ptr =
upload_pixels_[0]
.subspan(video_frame->visible_rect().y() * bytes_per_row +
video_frame->visible_rect().x() * sizeof(uint32_t))
.data();
bool premultiply_alpha =
hardware_resource->shared_image()->alpha_type() == kPremul_SkAlphaType
? true
: false;
PaintCanvasVideoRenderer::ConvertVideoFrameToRGBPixels(
video_frame.get(), dest_ptr, bytes_per_row,
resource_format == viz::SinglePlaneFormat::kRGBA_F16
? kRGBA_F16_SkColorType
: kN32_SkColorType,
premultiply_alpha);
source_pixels = upload_pixels_[0].data();
}
auto* ri = RasterInterface();
std::unique_ptr<gpu::RasterScopedAccess> ri_access =
hardware_resource->shared_image()->BeginRasterAccess(
ri, hardware_resource->sync_token(),
false);
auto color_type =
viz::ToClosestSkColorType(resource_format, 0);
auto info = SkImageInfo::Make(
gfx::SizeToSkISize(hardware_resource->size()), color_type,
hardware_resource->shared_image()->alpha_type(),
hardware_resource->shared_image()->color_space().ToSkColorSpace());
SkPixmap pixmap(info, source_pixels, bytes_per_row);
ri->WritePixels(
hardware_resource->shared_image()->mailbox(), 0,
0, hardware_resource->shared_image()->GetTextureTarget(),
pixmap);
gpu::SyncToken ri_sync_token =
gpu::RasterScopedAccess::EndAccess(std::move(ri_access));
hardware_resource->UpdateSyncToken(ri_sync_token);
return true;
}
bool VideoResourceUpdater::WriteYUVPixelsForAllPlanesToTexture(
scoped_refptr<VideoFrame> video_frame,
FrameResource* resource,
size_t bits_per_channel) {
if (resource->Matches(video_frame->unique_id())) {
return true;
}
CHECK(!resource->is_software());
auto yuv_si_format = resource->format();
std::array<SkPixmap, SkYUVAInfo::kMaxPlanes> pixmaps = {};
for (int plane_index = 0; plane_index < yuv_si_format.NumberOfPlanes();
++plane_index) {
std::vector<VideoFrame::Plane> frame_planes =
GetVideoFramePlanes(yuv_si_format);
const int video_stride_bytes =
video_frame->stride(frame_planes[plane_index]);
const gfx::Size resource_size_pixels =
yuv_si_format.GetPlaneSize(plane_index, resource->size());
const size_t plane_size_in_bytes =
yuv_si_format
.MaybeEstimatedPlaneSizeInBytes(plane_index, resource->size())
.value();
const size_t bytes_per_row = static_cast<size_t>(
plane_size_in_bytes / resource_size_pixels.height());
constexpr size_t kDefaultUnpackAlignment = 4;
const size_t upload_image_stride = cc::MathUtil::CheckedRoundUp<size_t>(
bytes_per_row, kDefaultUnpackAlignment);
size_t resource_bit_depth = yuv_si_format.MultiplanarBitDepth();
if (resource_bit_depth == 10) {
resource_bit_depth = 16;
}
const bool needs_bit_downshifting = bits_per_channel > resource_bit_depth;
const bool needs_bit_upshifting =
bits_per_channel > 8 && bits_per_channel < resource_bit_depth;
const bool is_16bit_float = yuv_si_format.channel_format() ==
viz::SharedImageFormat::ChannelFormat::k16F;
const bool needs_conversion =
is_16bit_float || needs_bit_downshifting || needs_bit_upshifting;
const uint8_t* pixels;
int pixels_stride_in_bytes;
if (!needs_conversion) {
pixels = video_frame->data(frame_planes[plane_index]);
pixels_stride_in_bytes = video_stride_bytes;
} else {
const size_t needed_size =
upload_image_stride * resource_size_pixels.height();
if (upload_pixels_[plane_index].size() < needed_size) {
if (!ReallocateUploadPixels(needed_size, plane_index)) {
return false;
}
}
if (is_16bit_float) {
int max_value = 1 << bits_per_channel;
float libyuv_multiplier = 1.f / max_value;
libyuv::HalfFloatPlane(
reinterpret_cast<const uint16_t*>(
video_frame->data(frame_planes[plane_index])),
video_stride_bytes,
reinterpret_cast<uint16_t*>(upload_pixels_[plane_index].data()),
upload_image_stride, libyuv_multiplier,
resource_size_pixels.width(), resource_size_pixels.height());
} else if (needs_bit_downshifting) {
DCHECK(yuv_si_format.channel_format() ==
viz::SharedImageFormat::ChannelFormat::k8);
const int scale = 0x10000 >> (bits_per_channel - 8);
libyuv::Convert16To8Plane(
reinterpret_cast<const uint16_t*>(
video_frame->data(frame_planes[plane_index])),
video_stride_bytes / 2, upload_pixels_[plane_index].data(),
upload_image_stride, scale, bytes_per_row,
resource_size_pixels.height());
} else if (needs_bit_upshifting) {
CHECK_EQ(resource_bit_depth, 16u);
libyuv::ConvertToMSBPlane_16(
reinterpret_cast<const uint16_t*>(
video_frame->data(frame_planes[plane_index])),
video_stride_bytes / 2,
reinterpret_cast<uint16_t*>(upload_pixels_[plane_index].data()),
upload_image_stride / 2, resource_size_pixels.width(),
resource_size_pixels.height(), bits_per_channel);
} else {
NOTREACHED();
}
pixels = upload_pixels_[plane_index].data();
pixels_stride_in_bytes = upload_image_stride;
}
auto color_type = viz::ToClosestSkColorType(yuv_si_format, plane_index);
SkImageInfo info = SkImageInfo::Make(
resource_size_pixels.width(), resource_size_pixels.height(), color_type,
resource->shared_image()->alpha_type());
pixmaps[plane_index] = SkPixmap(info, pixels, pixels_stride_in_bytes);
}
resource->SetUniqueId(video_frame->unique_id());
SkYUVColorSpace color_space = kRec601_SkYUVColorSpace;
gfx::ColorSpace video_color_space = video_frame->ColorSpace();
CHECK(!video_color_space.IsValid() ||
(video_color_space.GetMatrixID() != gfx::ColorSpace::MatrixID::RGB),
base::NotFatalUntil::M139);
if (video_color_space.IsValid()) {
CHECK(video_color_space.ToSkYUVColorSpace(video_frame->BitDepth(),
&color_space));
}
auto resource_size = gfx::SizeToSkISize(resource->size());
SkYUVAInfo::PlaneConfig plane_config = ToSkYUVAPlaneConfig(yuv_si_format);
SkYUVAInfo::Subsampling subsampling = ToSkYUVASubsampling(yuv_si_format);
SkYUVAInfo info(resource_size, plane_config, subsampling, color_space);
auto yuv_pixmap = SkYUVAPixmaps::FromExternalPixmaps(info, pixmaps.data());
auto* ri = RasterInterface();
std::unique_ptr<gpu::RasterScopedAccess> ri_access =
resource->shared_image()->BeginRasterAccess(ri, resource->sync_token(),
false);
ri->WritePixelsYUV(resource->shared_image()->mailbox(), yuv_pixmap);
gpu::SyncToken ri_sync_token =
gpu::RasterScopedAccess::EndAccess(std::move(ri_access));
resource->UpdateSyncToken(ri_sync_token);
return true;
}
VideoFrameExternalResource VideoResourceUpdater::CreateForSoftwareFrame(
scoped_refptr<VideoFrame> video_frame) {
TRACE_EVENT0("media", "VideoResourceUpdater::CreateForSoftwareFrame");
const VideoPixelFormat input_frame_format = video_frame->format();
size_t bits_per_channel = video_frame->BitDepth();
DCHECK(IsYuvPlanar(input_frame_format) ||
input_frame_format == PIXEL_FORMAT_Y16 ||
input_frame_format == PIXEL_FORMAT_RGBAF16 ||
IsFrameFormat32BitRGB(input_frame_format));
viz::SharedImageFormat output_si_format =
GetSoftwareOutputFormat(input_frame_format, bits_per_channel);
gfx::ColorSpace output_color_space = video_frame->ColorSpace();
SkAlphaType output_alpha_type =
software_compositor() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
if (!software_compositor() && use_gpu_memory_buffer_resources_ &&
context_provider_->SharedImageInterface()
->GetCapabilities()
.supports_scanout_shared_images) {
output_alpha_type = kPremul_SkAlphaType;
}
if (output_si_format.is_single_plane()) {
DCHECK(output_si_format == viz::SinglePlaneFormat::kBGRA_8888 ||
output_si_format == viz::SinglePlaneFormat::kRGBA_8888 ||
output_si_format == viz::SinglePlaneFormat::kRGBA_F16);
output_color_space = output_color_space.GetAsFullRangeRGB();
}
gfx::Size output_resource_size = video_frame->coded_size();
if (output_resource_size.IsEmpty() ||
output_resource_size.width() > max_resource_size_ ||
output_resource_size.height() > max_resource_size_) {
DLOG(ERROR)
<< "Video resource is too large to upload. Maximum dimension is "
<< max_resource_size_ << " and resource is "
<< output_resource_size.ToString();
return VideoFrameExternalResource();
}
auto can_delete_resource_fn =
[output_si_format, output_resource_size, output_color_space,
output_alpha_type](const std::unique_ptr<FrameResource>& resource) {
if (resource->has_refs()) {
return false;
}
return !resource->Equals(output_resource_size, output_si_format,
output_color_space, output_alpha_type);
};
std::erase_if(all_resources_, can_delete_resource_fn);
FrameResource* frame_resource = RecycleOrAllocateResource(
output_resource_size, output_si_format, output_color_space,
output_alpha_type, video_frame->unique_id());
frame_resource->add_ref();
CHECK_EQ(output_si_format, frame_resource->format());
CHECK_EQ(output_color_space, frame_resource->shared_image()->color_space());
VideoFrameExternalResource external_resource;
if (output_si_format.is_single_plane()) {
if (!frame_resource->Matches(video_frame->unique_id())) {
if (software_compositor()) {
TransferRGBPixelsToPaintCanvas(video_frame, frame_resource);
} else {
if (!WriteRGBPixelsToTexture(video_frame, frame_resource)) {
return VideoFrameExternalResource();
}
}
frame_resource->SetUniqueId(video_frame->unique_id());
}
auto transferable_resource = viz::TransferableResource::Make(
frame_resource->shared_image(),
viz::TransferableResource::ResourceSource::kVideo,
frame_resource->sync_token());
transferable_resource.hdr_metadata =
video_frame->hdr_metadata().value_or(gfx::HDRMetadata());
transferable_resource.needs_detiling =
video_frame->metadata().needs_detiling;
external_resource.resource = std::move(transferable_resource);
external_resource.type = software_compositor()
? VideoFrameResourceType::RGBA_PREMULTIPLIED
: VideoFrameResourceType::RGB;
external_resource.release_callback =
base::BindOnce(&VideoResourceUpdater::RecycleResource,
weak_ptr_factory_.GetWeakPtr(), frame_resource->id());
return external_resource;
}
CHECK(output_si_format.is_multi_plane());
if (!WriteYUVPixelsForAllPlanesToTexture(video_frame, frame_resource,
bits_per_channel)) {
return VideoFrameExternalResource();
}
auto transferable_resource = viz::TransferableResource::Make(
frame_resource->shared_image(),
viz::TransferableResource::ResourceSource::kVideo,
frame_resource->sync_token());
transferable_resource.hdr_metadata =
video_frame->hdr_metadata().value_or(gfx::HDRMetadata());
external_resource.resource = std::move(transferable_resource);
external_resource.release_callback =
base::BindOnce(&VideoResourceUpdater::RecycleResource,
weak_ptr_factory_.GetWeakPtr(), frame_resource->id());
external_resource.type = VideoFrameResourceType::RGB;
return external_resource;
}
gpu::raster::RasterInterface* VideoResourceUpdater::RasterInterface() {
auto* ri = context_provider_->RasterInterface();
CHECK(ri);
return ri;
}
void VideoResourceUpdater::ReturnTexture(
scoped_refptr<VideoFrame> video_frame,
const gpu::SyncToken& original_release_token,
const gpu::SyncToken& new_release_token,
bool lost_resource) {
if (lost_resource) {
return;
}
if (!new_release_token.HasData()) {
return;
}
ResourceSyncTokenClient client(RasterInterface(), original_release_token,
new_release_token);
video_frame->UpdateReleaseSyncToken(&client);
}
void VideoResourceUpdater::RecycleResource(uint32_t resource_id,
const gpu::SyncToken& sync_token,
bool lost_resource) {
auto resource_it =
std::ranges::find(all_resources_, resource_id, &FrameResource::id);
if (resource_it == all_resources_.end())
return;
if (context_provider_ && sync_token.HasData()) {
(*resource_it)->UpdateSyncToken(sync_token);
}
if (lost_resource) {
all_resources_.erase(resource_it);
} else {
(*resource_it)->remove_ref();
}
}
bool VideoResourceUpdater::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
for (auto& resource : all_resources_) {
std::string dump_name = base::StringPrintf(
"cc/video_memory/updater_%d/resource_%d", tracing_id_, resource->id());
base::trace_event::MemoryAllocatorDump* dump =
pmd->CreateAllocatorDump(dump_name);
const uint64_t total_bytes =
resource->format().EstimatedSizeInBytes(resource->size());
dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
base::trace_event::MemoryAllocatorDump::kUnitsBytes,
total_bytes);
constexpr int kImportance = 2;
resource->shared_image()->OnMemoryDump(pmd, dump->guid(), kImportance);
}
return true;
}
gpu::SharedImageInterface* VideoResourceUpdater::shared_image_interface()
const {
return shared_image_interface_.get();
}
viz::ResourceId VideoResourceUpdater::GetFrameResourceIdForTesting() const {
return frame_resource_id_;
}
}