blob: b99157272ee2e9dd7ff73d4cffc3018ff0c94db4 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "media/renderers/shared_image_video_frame_test_utils.h"
#include "base/logging.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/capabilities.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkYUVAInfo.h"
#include "third_party/skia/include/core/SkYUVAPixmaps.h"
namespace media {
namespace {
static constexpr const uint8_t kYuvColors[8][3] = {
{0x00, 0x80, 0x80}, // Black
{0x4c, 0x54, 0xff}, // Red
{0x95, 0x2b, 0x15}, // Green
{0xe1, 0x00, 0x94}, // Yellow
{0x1d, 0xff, 0x6b}, // Blue
{0x69, 0xd3, 0xec}, // Magenta
{0xb3, 0xaa, 0x00}, // Cyan
{0xff, 0x80, 0x80}, // White
};
// Destroys a list of shared images after a sync token is passed. Also runs
// |callback|.
void DestroySharedImage(scoped_refptr<gpu::ClientSharedImage> shared_image,
base::OnceClosure callback,
const gpu::SyncToken& sync_token) {
shared_image->UpdateDestructionSyncToken(sync_token);
std::move(callback).Run();
}
scoped_refptr<VideoFrame> CreateSharedImageFrame(
VideoPixelFormat format,
scoped_refptr<gpu::ClientSharedImage> shared_image,
const gpu::SyncToken& sync_token,
GLenum texture_target,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp,
base::OnceClosure destroyed_callback) {
auto callback = base::BindOnce(&DestroySharedImage, shared_image,
std::move(destroyed_callback));
auto frame = VideoFrame::WrapSharedImage(
format, std::move(shared_image), sync_token, std::move(callback),
coded_size, visible_rect, natural_size, timestamp);
return frame;
}
} // namespace
scoped_refptr<VideoFrame> CreateSharedImageRGBAFrame(
scoped_refptr<viz::RasterContextProvider> context_provider,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
base::OnceClosure destroyed_callback) {
DCHECK_EQ(coded_size.width() % 4, 0);
DCHECK_EQ(coded_size.height() % 2, 0);
size_t pixels_size = coded_size.GetArea() * 4;
std::vector<uint8_t> pixels(pixels_size);
size_t i = 0;
for (size_t block_y = 0; block_y < 2u; ++block_y) {
for (int y = 0; y < coded_size.height() / 2; ++y) {
for (size_t block_x = 0; block_x < 4u; ++block_x) {
for (int x = 0; x < coded_size.width() / 4; ++x) {
pixels[i++] = 0xffu * (block_x % 2); // R
pixels[i++] = 0xffu * (block_x / 2); // G
pixels[i++] = 0xffu * block_y; // B
pixels[i++] = 0xffu; // A
}
}
}
}
DCHECK_EQ(i, pixels_size);
// This SharedImage will be read by the raster interface to create
// intermediate copies in copy to canvas and 2-copy upload to WebGL. It may
// also be read by the GLES2 interface if the code creating the intermediate
// SharedImage decides that the VideoFrame can be wrapped directly as a GL
// texture and/or if raster is going over GLES2 in the context of the test.
constexpr auto kUsages =
gpu::SHARED_IMAGE_USAGE_RASTER_READ | gpu::SHARED_IMAGE_USAGE_GLES2_READ;
auto* sii = context_provider->SharedImageInterface();
auto shared_image =
sii->CreateSharedImage({viz::SinglePlaneFormat::kRGBA_8888, coded_size,
gfx::ColorSpace(), kUsages, "RGBAVideoFrame"},
pixels);
return CreateSharedImageFrame(
VideoPixelFormat::PIXEL_FORMAT_ABGR, shared_image, {}, GL_TEXTURE_2D,
coded_size, visible_rect, visible_rect.size(), base::Seconds(1),
std::move(destroyed_callback));
}
scoped_refptr<VideoFrame> CreateSharedImageI420Frame(
scoped_refptr<viz::RasterContextProvider> context_provider,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
base::OnceClosure destroyed_callback) {
DCHECK_EQ(coded_size.width() % 8, 0);
DCHECK_EQ(coded_size.height() % 4, 0);
gfx::Size uv_size(coded_size.width() / 2, coded_size.height() / 2);
size_t y_pixels_size = coded_size.GetArea();
size_t uv_pixels_size = uv_size.GetArea();
std::vector<uint8_t> y_pixels(y_pixels_size);
std::vector<uint8_t> u_pixels(uv_pixels_size);
std::vector<uint8_t> v_pixels(uv_pixels_size);
size_t y_i = 0;
size_t uv_i = 0;
for (size_t block_y = 0; block_y < 2u; ++block_y) {
for (int y = 0; y < coded_size.height() / 2; ++y) {
for (size_t block_x = 0; block_x < 4u; ++block_x) {
size_t color_index = block_x + block_y * 4;
const uint8_t* yuv = kYuvColors[color_index];
for (int x = 0; x < coded_size.width() / 4; ++x) {
y_pixels[y_i++] = yuv[0];
if ((x % 2) && (y % 2)) {
u_pixels[uv_i] = yuv[1];
v_pixels[uv_i++] = yuv[2];
}
}
}
}
}
DCHECK_EQ(y_i, y_pixels_size);
DCHECK_EQ(uv_i, uv_pixels_size);
auto* sii = context_provider->SharedImageInterface();
auto* ri = context_provider->RasterInterface();
// These SharedImages will be read by the raster interface to create
// intermediate copies in copy to canvas and 2-copy upload to WebGL.
// In the context of the tests using these SharedImages, GPU rasterization is
// always used.
auto usages = gpu::SHARED_IMAGE_USAGE_RASTER_READ |
gpu::SHARED_IMAGE_USAGE_OOP_RASTERIZATION;
#if !BUILDFLAG(IS_ANDROID)
// These SharedImages may be read by the GLES2 interface for 1-copy upload to
// WebGL (not supported on Android).
usages |= gpu::SHARED_IMAGE_USAGE_GLES2_READ;
#endif
// Instead of creating shared image per plane, create a single multiplanar
// shared image and upload pixels to it.
auto shared_image =
sii->CreateSharedImage({viz::MultiPlaneFormat::kI420, coded_size,
gfx::ColorSpace(), usages, "I420Frame"},
gpu::kNullSurfaceHandle);
ri->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
SkPixmap pixmaps[SkYUVAInfo::kMaxPlanes] = {};
// SkColorType is always Alpha8 for I420 8 bit video frames.
auto color_type = kAlpha_8_SkColorType;
SkImageInfo y_info = SkImageInfo::Make(
coded_size.width(), coded_size.height(), color_type, kPremul_SkAlphaType);
pixmaps[0] = SkPixmap(y_info, y_pixels.data(), y_info.minRowBytes());
SkImageInfo u_info = SkImageInfo::Make(uv_size.width(), uv_size.height(),
color_type, kPremul_SkAlphaType);
pixmaps[1] = SkPixmap(u_info, u_pixels.data(), u_info.minRowBytes());
SkImageInfo v_info = SkImageInfo::Make(uv_size.width(), uv_size.height(),
color_type, kPremul_SkAlphaType);
pixmaps[2] = SkPixmap(v_info, v_pixels.data(), v_info.minRowBytes());
SkYUVAInfo info =
SkYUVAInfo({coded_size.width(), coded_size.height()},
SkYUVAInfo::PlaneConfig::kY_U_V, SkYUVAInfo::Subsampling::k420,
kIdentity_SkYUVColorSpace);
SkYUVAPixmaps yuv_pixmap = SkYUVAPixmaps::FromExternalPixmaps(info, pixmaps);
ri->WritePixelsYUV(shared_image->mailbox(), yuv_pixmap);
gpu::SyncToken sync_token;
ri->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
return CreateSharedImageFrame(
VideoPixelFormat::PIXEL_FORMAT_I420, shared_image, sync_token,
GL_TEXTURE_2D, coded_size, visible_rect, visible_rect.size(),
base::Seconds(1), std::move(destroyed_callback));
}
scoped_refptr<VideoFrame> CreateSharedImageNV12Frame(
scoped_refptr<viz::RasterContextProvider> context_provider,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
base::OnceClosure destroyed_callback) {
DCHECK_EQ(coded_size.width() % 8, 0);
DCHECK_EQ(coded_size.height() % 4, 0);
if (!context_provider->ContextCapabilities().texture_rg) {
LOG(ERROR) << "GL_EXT_texture_rg not supported";
return {};
}
gfx::Size uv_size(coded_size.width() / 2, coded_size.height() / 2);
size_t y_pixels_size = coded_size.GetArea();
size_t uv_pixels_size = uv_size.GetArea() * 2;
std::vector<uint8_t> y_pixels(y_pixels_size);
std::vector<uint8_t> uv_pixels(uv_pixels_size);
size_t y_i = 0;
size_t uv_i = 0;
for (size_t block_y = 0; block_y < 2u; ++block_y) {
for (int y = 0; y < coded_size.height() / 2; ++y) {
for (size_t block_x = 0; block_x < 4u; ++block_x) {
size_t color_index = block_x + block_y * 4;
const uint8_t* yuv = kYuvColors[color_index];
for (int x = 0; x < coded_size.width() / 4; ++x) {
y_pixels[y_i++] = yuv[0];
if ((x % 2) && (y % 2)) {
uv_pixels[uv_i++] = yuv[1];
uv_pixels[uv_i++] = yuv[2];
}
}
}
}
}
DCHECK_EQ(y_i, y_pixels_size);
DCHECK_EQ(uv_i, uv_pixels_size);
auto* sii = context_provider->SharedImageInterface();
auto* ri = context_provider->RasterInterface();
// These SharedImages will be read by the raster interface to create
// intermediate copies in copy to canvas and 2-copy upload to WebGL.
// In the context of the tests using these SharedImages, GPU rasterization is
// always used.
auto usages = gpu::SHARED_IMAGE_USAGE_RASTER_READ |
gpu::SHARED_IMAGE_USAGE_OOP_RASTERIZATION;
#if !BUILDFLAG(IS_ANDROID)
// These SharedImages may be read by the GLES2 interface for 1-copy upload to
// WebGL (not supported on Android).
usages |= gpu::SHARED_IMAGE_USAGE_GLES2_READ;
#endif
// Instead of creating shared image per plane, create a single multiplanar
// shared image and upload pixels to it.
auto shared_image =
sii->CreateSharedImage({viz::MultiPlaneFormat::kNV12, coded_size,
gfx::ColorSpace(), usages, "NV12Frame"},
gpu::kNullSurfaceHandle);
ri->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
SkPixmap pixmaps[SkYUVAInfo::kMaxPlanes] = {};
SkImageInfo y_info =
SkImageInfo::Make(coded_size.width(), coded_size.height(),
kAlpha_8_SkColorType, kPremul_SkAlphaType);
pixmaps[0] = SkPixmap(y_info, y_pixels.data(), y_info.minRowBytes());
SkImageInfo uv_info =
SkImageInfo::Make(uv_size.width(), uv_size.height(),
kR8G8_unorm_SkColorType, kPremul_SkAlphaType);
pixmaps[1] = SkPixmap(uv_info, uv_pixels.data(), uv_info.minRowBytes());
SkYUVAInfo info = SkYUVAInfo(
{coded_size.width(), coded_size.height()}, SkYUVAInfo::PlaneConfig::kY_UV,
SkYUVAInfo::Subsampling::k420, kIdentity_SkYUVColorSpace);
SkYUVAPixmaps yuv_pixmap = SkYUVAPixmaps::FromExternalPixmaps(info, pixmaps);
ri->WritePixelsYUV(shared_image->mailbox(), yuv_pixmap);
gpu::SyncToken sync_token;
ri->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
return CreateSharedImageFrame(
VideoPixelFormat::PIXEL_FORMAT_NV12, shared_image, sync_token,
GL_TEXTURE_2D, coded_size, visible_rect, visible_rect.size(),
base::Seconds(1), std::move(destroyed_callback));
}
} // namespace media