Skip to content

Commit

Permalink
Enable WebGPU video 0-copy import on ChromeOS
Browse files Browse the repository at this point in the history
Currently Dawn can support importing NV12 GBM buffers in a
non-disjoint way without copy on Intel Chromebooks. This enables this
feature in blink correspondingly enforcing the VAAPI decoder to use
SharedImageBackingOzone instead of SharedImageBackingGLImage when the
commandline flag --enable-unsafe-webgpu is on.

Bug: 1258986
Change-Id: Ic3e1e3fe828e6e99594591e3d8555f2cbff67635
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3270773
Reviewed-by: Robert Kroeger <rjkroege@chromium.org>
Reviewed-by: Andres Calderon Jaramillo <andrescj@chromium.org>
Reviewed-by: Dan Sanders <sandersd@chromium.org>
Reviewed-by: Sunny Sachanandani <sunnyps@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Jie A Chen <jie.a.chen@intel.com>
Cr-Commit-Position: refs/heads/main@{#989359}
  • Loading branch information
jchen10 authored and Chromium LUCI CQ committed Apr 6, 2022
1 parent fdba2ff commit 5adbf3b
Show file tree
Hide file tree
Showing 39 changed files with 238 additions and 25 deletions.
3 changes: 2 additions & 1 deletion components/viz/common/resources/resource_format_utils.cc
Expand Up @@ -604,14 +604,15 @@ wgpu::TextureFormat ToDawnFormat(ResourceFormat format) {
return wgpu::TextureFormat::RGBA16Float;
case RGBA_1010102:
return wgpu::TextureFormat::RGB10A2Unorm;
case YUV_420_BIPLANAR:
return wgpu::TextureFormat::R8BG8Biplanar420Unorm;
case RGBA_4444:
case RGB_565:
case BGR_565:
case R16_EXT:
case RG16_EXT:
case BGRA_1010102:
case YVU_420:
case YUV_420_BIPLANAR:
case ETC1:
case LUMINANCE_F16:
case P010:
Expand Down
Expand Up @@ -68,6 +68,7 @@ class FakeNativePixmap : public gfx::NativePixmap {
uint64_t GetBufferFormatModifier() const override { return 0; }
gfx::BufferFormat GetBufferFormat() const override { return format_; }
size_t GetNumberOfPlanes() const override { return 0; }
bool SupportsZeroCopyWebGPUImport() const override { return false; }
gfx::Size GetBufferSize() const override { return size_; }
uint32_t GetUniqueId() const override { return 0; }
bool ScheduleOverlayPlane(
Expand Down
Expand Up @@ -51,11 +51,19 @@ WGPUTexture SharedImageRepresentationDawnOzone::BeginAccess(
if (texture_) {
return nullptr;
}

// For multi-planar formats, Mesa is yet to support to allocate and bind
// vkmemory for each plane respectively.
// https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/intel/vulkan/anv_formats.c#L765
// For now we assume all plane handles are same, and we don't use the
// VK_IMAGE_CREATE_DISJOINT_BIT when creating the vkimage for the pixmap.
DCHECK(pixmap_->SupportsZeroCopyWebGPUImport() ||
pixmap_->GetNumberOfPlanes() == 1)
<< "Disjoint Multi-plane importing is not supported.";

if (!ozone_backing()->VaSync()) {
return nullptr;
}
DCHECK(pixmap_->GetNumberOfPlanes() == 1)
<< "Multi-plane formats are not supported.";

std::vector<gfx::GpuFenceHandle> fences;
bool need_end_fence;
Expand Down Expand Up @@ -94,6 +102,7 @@ WGPUTexture SharedImageRepresentationDawnOzone::BeginAccess(
// closed twice (once by ScopedFD and once by the Vulkan implementation).
int fd = dup(pixmap_->GetDmaBufFd(0));
descriptor.memoryFD = fd;
// stride is not required for multi-planar formats.
descriptor.stride = pixmap_->GetDmaBufPitch(0);
descriptor.drmModifier = pixmap_->GetBufferFormatModifier();
descriptor.waitFDs = {};
Expand Down
8 changes: 8 additions & 0 deletions gpu/command_buffer/service/webgpu_decoder_impl.cc
Expand Up @@ -1099,6 +1099,14 @@ void WebGPUDecoderImpl::DoRequestDevice(
// Pass something invalid.
required_features.push_back(static_cast<WGPUFeatureName>(-1));
}

// Always enable "multi-planar-formats" as long as available.
if (dawn_adapters_[requested_adapter_index]
.GetAdapterProperties()
.multiPlanarFormats) {
required_features.push_back(WGPUFeatureName_DawnMultiPlanarFormats);
}

device_descriptor.requiredFeatures = required_features.data();
device_descriptor.requiredFeaturesCount = required_features.size();

Expand Down
1 change: 1 addition & 0 deletions media/base/video_frame_metadata.cc
Expand Up @@ -50,6 +50,7 @@ void VideoFrameMetadata::MergeMetadataFrom(
MERGE_VALUE_FIELD(dcomp_surface, metadata_source);
MERGE_VALUE_FIELD(protected_video, metadata_source);
MERGE_VALUE_FIELD(hw_protected, metadata_source);
MERGE_VALUE_FIELD(is_webgpu_compatible, metadata_source);
#if BUILDFLAG(USE_VAAPI)
MERGE_OPTIONAL_FIELD(hw_va_protected_session_id, metadata_source);
#endif
Expand Down
4 changes: 4 additions & 0 deletions media/base/video_frame_metadata.h
Expand Up @@ -148,6 +148,10 @@ struct MEDIA_EXPORT VideoFrameMetadata {
// PROTECTED_VIDEO is also set to true.
bool hw_protected = false;

// This video frame's shared image backing can support zero-copy WebGPU
// import.
bool is_webgpu_compatible = false;

#if BUILDFLAG(USE_VAAPI)
// The ID of the VA-API protected session used to decode this frame, if
// applicable. The proper type is VAProtectedSessionID. However, in order to
Expand Down
18 changes: 13 additions & 5 deletions media/gpu/chromeos/mailbox_video_frame_converter.cc
Expand Up @@ -84,7 +84,8 @@ class MailboxVideoFrameConverter::ScopedSharedImage {
std::unique_ptr<VideoFrameConverter> MailboxVideoFrameConverter::Create(
UnwrapFrameCB unwrap_frame_cb,
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
GetCommandBufferStubCB get_stub_cb) {
GetCommandBufferStubCB get_stub_cb,
bool enable_unsafe_webgpu) {
DCHECK(unwrap_frame_cb);
DCHECK(gpu_task_runner);
DCHECK(get_stub_cb);
Expand All @@ -101,16 +102,18 @@ std::unique_ptr<VideoFrameConverter> MailboxVideoFrameConverter::Create(

return base::WrapUnique<VideoFrameConverter>(new MailboxVideoFrameConverter(
std::move(unwrap_frame_cb), std::move(gpu_task_runner),
get_gpu_channel_cb));
get_gpu_channel_cb, enable_unsafe_webgpu));
}

MailboxVideoFrameConverter::MailboxVideoFrameConverter(
UnwrapFrameCB unwrap_frame_cb,
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
GetGpuChannelCB get_gpu_channel_cb)
GetGpuChannelCB get_gpu_channel_cb,
bool enable_unsafe_webgpu)
: unwrap_frame_cb_(std::move(unwrap_frame_cb)),
gpu_task_runner_(std::move(gpu_task_runner)),
get_gpu_channel_cb_(get_gpu_channel_cb) {
get_gpu_channel_cb_(get_gpu_channel_cb),
enable_unsafe_webgpu_(enable_unsafe_webgpu) {
DVLOGF(2);

parent_weak_this_ = parent_weak_this_factory_.GetWeakPtr();
Expand Down Expand Up @@ -243,6 +246,8 @@ void MailboxVideoFrameConverter::WrapMailboxAndVideoFrameAndOutput(
mailbox_frame->set_metadata(frame->metadata());
mailbox_frame->set_ycbcr_info(frame->ycbcr_info());
mailbox_frame->metadata().read_lock_fences_enabled = true;
mailbox_frame->metadata().is_webgpu_compatible =
enable_unsafe_webgpu_ && frame->metadata().is_webgpu_compatible;

output_cb_.Run(mailbox_frame);
}
Expand Down Expand Up @@ -354,8 +359,11 @@ bool MailboxVideoFrameConverter::GenerateSharedImageOnGPUThread(

// The allocated SharedImages should be usable for the (Display) compositor
// and, potentially, for overlays (Scanout).
const uint32_t shared_image_usage =
uint32_t shared_image_usage =
gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT;
if (enable_unsafe_webgpu_ && video_frame->metadata().is_webgpu_compatible)
shared_image_usage |= gpu::SHARED_IMAGE_USAGE_WEBGPU;

const bool success = shared_image_stub->CreateSharedImage(
mailbox, gpu::kPlatformVideoFramePoolClientId,
std::move(gpu_memory_buffer_handle), *buffer_format,
Expand Down
13 changes: 9 additions & 4 deletions media/gpu/chromeos/mailbox_video_frame_converter.h
Expand Up @@ -44,12 +44,14 @@ class MEDIA_GPU_EXPORT MailboxVideoFrameConverter : public VideoFrameConverter {
// Creates a MailboxVideoFrameConverter instance. The callers will send
// wrapped VideoFrames to ConvertFrame(), |unwrap_frame_cb| is the callback
// used to get the original, unwrapped, VideoFrame. |gpu_task_runner| is the
// task runner of the GPU main thread. Returns nullptr if any argument is
// invalid.
// task runner of the GPU main thread. |enable_unsafe_webgpu| hints whether
// to create SharedImage of SHARED_IMAGE_USAGE_WEBGPU for VideoFrame. Returns
// nullptr if any argument is invalid.
static std::unique_ptr<VideoFrameConverter> Create(
UnwrapFrameCB unwrap_frame_cb,
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
GetCommandBufferStubCB get_stub_cb);
GetCommandBufferStubCB get_stub_cb,
bool enable_unsafe_webgpu);

MailboxVideoFrameConverter(const MailboxVideoFrameConverter&) = delete;
MailboxVideoFrameConverter& operator=(const MailboxVideoFrameConverter&) =
Expand All @@ -76,7 +78,8 @@ class MEDIA_GPU_EXPORT MailboxVideoFrameConverter : public VideoFrameConverter {
MailboxVideoFrameConverter(
UnwrapFrameCB unwrap_frame_cb,
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
GetGpuChannelCB get_gpu_channel_cb);
GetGpuChannelCB get_gpu_channel_cb,
bool enable_unsafe_webgpu);
// Destructor runs on the GPU main thread.
~MailboxVideoFrameConverter() override;
void Destroy() override;
Expand Down Expand Up @@ -164,6 +167,8 @@ class MEDIA_GPU_EXPORT MailboxVideoFrameConverter : public VideoFrameConverter {
base::queue<std::pair<scoped_refptr<VideoFrame>, UniqueID>>
input_frame_queue_;

const bool enable_unsafe_webgpu_;

// The weak pointer of this, bound to |parent_task_runner_|.
// Used at the VideoFrame destruction callback.
base::WeakPtr<MailboxVideoFrameConverter> parent_weak_this_;
Expand Down
3 changes: 2 additions & 1 deletion media/gpu/chromeos/mailbox_video_frame_converter_unittest.cc
Expand Up @@ -32,7 +32,8 @@ class MailboxVideoFrameConverterTest : public testing::Test {
: converter_(new MailboxVideoFrameConverter(
base::BindRepeating(&UnwrapVideoFrame),
base::ThreadTaskRunnerHandle::Get(),
base::BindRepeating(&GetGpuChannel))) {}
base::BindRepeating(&GetGpuChannel),
/*enable_unsafe_webgpu=*/false)) {}

MailboxVideoFrameConverterTest(const MailboxVideoFrameConverterTest&) =
delete;
Expand Down
8 changes: 8 additions & 0 deletions media/gpu/chromeos/platform_video_frame_utils.cc
Expand Up @@ -226,6 +226,9 @@ scoped_refptr<VideoFrame> CreateGpuMemoryBufferVideoFrame(
if (gmb_handle.is_null() || gmb_handle.type != gfx::NATIVE_PIXMAP)
return nullptr;

const bool supports_zero_copy_webgpu_import =
gmb_handle.native_pixmap_handle.supports_zero_copy_webgpu_import;

auto buffer_format = VideoPixelFormatToGfxBufferFormat(pixel_format);
DCHECK(buffer_format);
gpu::GpuMemoryBufferSupport support;
Expand All @@ -244,6 +247,11 @@ scoped_refptr<VideoFrame> CreateGpuMemoryBufferVideoFrame(

if (frame)
frame->AddDestructionObserver(destroy_cb.Release());

// We only support importing non-DISJOINT multi-planar GbmBuffer right now.
// TODO(crbug.com/1258986): Add DISJOINT support.
frame->metadata().is_webgpu_compatible = supports_zero_copy_webgpu_import;

return frame;
}

Expand Down
2 changes: 2 additions & 0 deletions media/mojo/mojom/media_types.mojom
Expand Up @@ -337,6 +337,8 @@ struct VideoFrameMetadata {

bool hw_protected;

bool is_webgpu_compatible;

mojo_base.mojom.UnguessableToken? overlay_plane_id;

bool power_efficient;
Expand Down
1 change: 1 addition & 0 deletions media/mojo/mojom/video_frame_metadata_mojom_traits.cc
Expand Up @@ -42,6 +42,7 @@ bool StructTraits<media::mojom::VideoFrameMetadataDataView,
output->wants_promotion_hint = input.wants_promotion_hint();
output->protected_video = input.protected_video();
output->hw_protected = input.hw_protected();
output->is_webgpu_compatible = input.is_webgpu_compatible();
output->power_efficient = input.power_efficient();
output->read_lock_fences_enabled = input.read_lock_fences_enabled();
output->interactive_content = input.interactive_content();
Expand Down
4 changes: 4 additions & 0 deletions media/mojo/mojom/video_frame_metadata_mojom_traits.h
Expand Up @@ -55,6 +55,10 @@ struct StructTraits<media::mojom::VideoFrameMetadataDataView,
return input.hw_protected;
}

static bool is_webgpu_compatible(const media::VideoFrameMetadata& input) {
return input.is_webgpu_compatible;
}

static bool power_efficient(const media::VideoFrameMetadata& input) {
return input.power_efficient;
}
Expand Down
Expand Up @@ -72,6 +72,7 @@ TEST_F(VideoFrameMetadataStructTraitsTest, EmptyMetadata) {
EXPECT_FALSE(metadata_out.wants_promotion_hint);
EXPECT_FALSE(metadata_out.protected_video);
EXPECT_FALSE(metadata_out.hw_protected);
EXPECT_FALSE(metadata_out.is_webgpu_compatible);
EXPECT_FALSE(metadata_out.power_efficient);
EXPECT_FALSE(metadata_out.read_lock_fences_enabled);
EXPECT_FALSE(metadata_out.interactive_content);
Expand Down Expand Up @@ -118,6 +119,7 @@ TEST_F(VideoFrameMetadataStructTraitsTest, ValidMetadata) {
metadata_in.wants_promotion_hint = true;
metadata_in.protected_video = true;
metadata_in.hw_protected = true;
metadata_in.is_webgpu_compatible = true;
metadata_in.power_efficient = true;
metadata_in.read_lock_fences_enabled = true;
metadata_in.interactive_content = true;
Expand Down Expand Up @@ -163,6 +165,8 @@ TEST_F(VideoFrameMetadataStructTraitsTest, ValidMetadata) {
metadata_out.wants_promotion_hint);
EXPECT_EQ(metadata_in.protected_video, metadata_out.protected_video);
EXPECT_EQ(metadata_in.hw_protected, metadata_out.hw_protected);
EXPECT_EQ(metadata_in.is_webgpu_compatible,
metadata_out.is_webgpu_compatible);
EXPECT_EQ(metadata_in.power_efficient, metadata_out.power_efficient);
EXPECT_EQ(metadata_in.read_lock_fences_enabled,
metadata_out.read_lock_fences_enabled);
Expand Down
3 changes: 2 additions & 1 deletion media/mojo/services/gpu_mojo_media_client_cros.cc
Expand Up @@ -111,7 +111,8 @@ std::unique_ptr<VideoDecoder> CreatePlatformVideoDecoder(
auto frame_converter = MailboxVideoFrameConverter::Create(
base::BindRepeating(&PlatformVideoFramePool::UnwrapFrame,
base::Unretained(frame_pool.get())),
traits.gpu_task_runner, traits.get_command_buffer_stub_cb);
traits.gpu_task_runner, traits.get_command_buffer_stub_cb,
traits.gpu_preferences.enable_unsafe_webgpu);
return VideoDecoderPipeline::Create(
traits.task_runner, std::move(frame_pool), std::move(frame_converter),
traits.media_log->Clone());
Expand Down
1 change: 1 addition & 0 deletions third_party/blink/renderer/modules/webgpu/DEPS
Expand Up @@ -10,6 +10,7 @@ include_rules = [
"+gpu/command_buffer/client/shared_image_interface.h",
"+gpu/command_buffer/client/webgpu_interface.h",
"+media/base/video_frame.h",
"+media/base/wait_and_replace_sync_token_client.h",
"+media/renderers/paint_canvas_video_renderer.h",
"+services/metrics/public/cpp/ukm_builders.h",
]
3 changes: 3 additions & 0 deletions third_party/blink/renderer/modules/webgpu/gpu_adapter.cc
Expand Up @@ -154,6 +154,9 @@ void GPUAdapter::InitializeFeatureNameList() {
if (adapter_properties_.depth32FloatStencil8) {
features_->AddFeatureName("depth32float-stencil8");
}
if (adapter_properties_.multiPlanarFormats) {
features_->AddFeatureName("multi-planar-formats");
}
}

ScriptPromise GPUAdapter::requestDevice(ScriptState* script_state,
Expand Down
60 changes: 52 additions & 8 deletions third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
Expand Up @@ -5,6 +5,7 @@
#include "third_party/blink/renderer/modules/webgpu/gpu_external_texture.h"

#include "media/base/video_frame.h"
#include "media/base/wait_and_replace_sync_token_client.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_external_texture_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texture_view_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_htmlvideoelement_videoframe.h"
Expand Down Expand Up @@ -85,6 +86,49 @@ GPUExternalTexture* GPUExternalTexture::Create(
return nullptr;
}

// TODO(crbug.com/1306753): Use SharedImageProducer and CompositeSharedImage
// rather than check 'is_webgpu_compatible'.
if (media_video_frame->HasTextures() &&
(media_video_frame->format() == media::PIXEL_FORMAT_NV12) &&
media_video_frame->metadata().is_webgpu_compatible) {
scoped_refptr<WebGPUMailboxTexture> mailbox_texture =
WebGPUMailboxTexture::FromVideoFrame(
device->GetDawnControlClient(), device->GetHandle(),
WGPUTextureUsage::WGPUTextureUsage_TextureBinding,
media_video_frame);

WGPUTextureViewDescriptor view_desc = {
.format = WGPUTextureFormat_R8Unorm,
.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED,
.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED,
.aspect = WGPUTextureAspect_Plane0Only};
WGPUTextureView plane0 = device->GetProcs().textureCreateView(
mailbox_texture->GetTexture(), &view_desc);
view_desc.format = WGPUTextureFormat_RG8Unorm;
view_desc.aspect = WGPUTextureAspect_Plane1Only;
WGPUTextureView plane1 = device->GetProcs().textureCreateView(
mailbox_texture->GetTexture(), &view_desc);

WGPUExternalTextureDescriptor external_texture_desc = {};
external_texture_desc.plane0 = plane0;
external_texture_desc.plane1 = plane1;
external_texture_desc.colorSpace = WGPUPredefinedColorSpace_Srgb;

GPUExternalTexture* external_texture =
MakeGarbageCollected<GPUExternalTexture>(
device,
device->GetProcs().deviceCreateExternalTexture(
device->GetHandle(), &external_texture_desc),
std::move(mailbox_texture));

// The texture view will be referenced during external texture creation, so
// by calling release here we ensure this texture view will be destructed
// when the external texture is destructed.
device->GetProcs().textureViewRelease(plane0);
device->GetProcs().textureViewRelease(plane1);

return external_texture;
}
// If the context is lost, the resource provider would be invalid.
auto context_provider_wrapper = SharedGpuContext::ContextProviderWrapper();
if (!context_provider_wrapper ||
Expand Down Expand Up @@ -135,16 +179,16 @@ GPUExternalTexture* GPUExternalTexture::Create(
WGPUTextureUsage::WGPUTextureUsage_TextureBinding,
std::move(recyclable_canvas_resource));

WGPUTextureViewDescriptor viewDesc = {};
viewDesc.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED;
viewDesc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED;
WGPUTextureViewDescriptor view_desc = {};
view_desc.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED;
view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED;
WGPUTextureView plane0 = device->GetProcs().textureCreateView(
mailbox_texture->GetTexture(), &viewDesc);
mailbox_texture->GetTexture(), &view_desc);

WGPUExternalTextureDescriptor dawn_desc = {};
dawn_desc.plane0 = plane0;

GPUExternalTexture* externalTexture =
GPUExternalTexture* external_texture =
MakeGarbageCollected<GPUExternalTexture>(
device,
device->GetProcs().deviceCreateExternalTexture(device->GetHandle(),
Expand All @@ -156,14 +200,14 @@ GPUExternalTexture* GPUExternalTexture::Create(
// the external texture is destructed.
device->GetProcs().textureViewRelease(plane0);

return externalTexture;
return external_texture;
}

GPUExternalTexture::GPUExternalTexture(
GPUDevice* device,
WGPUExternalTexture externalTexture,
WGPUExternalTexture external_texture,
scoped_refptr<WebGPUMailboxTexture> mailbox_texture)
: DawnObject<WGPUExternalTexture>(device, externalTexture),
: DawnObject<WGPUExternalTexture>(device, external_texture),
mailbox_texture_(mailbox_texture) {}

void GPUExternalTexture::Destroy() {
Expand Down
Expand Up @@ -24,7 +24,7 @@ class GPUExternalTexture : public DawnObject<WGPUExternalTexture> {
ExceptionState& exception_state);
explicit GPUExternalTexture(
GPUDevice* device,
WGPUExternalTexture externalTexture,
WGPUExternalTexture external_texture,
scoped_refptr<WebGPUMailboxTexture> mailbox_texture);

GPUExternalTexture(const GPUExternalTexture&) = delete;
Expand Down

0 comments on commit 5adbf3b

Please sign in to comment.