blob: e67e79861afbe9e6a41993580845f3a4f4a69ca4 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/video_effects/calculators/video_effects_graph_webgpu.h"
#include <optional>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/task/bind_post_task.h"
#include "services/video_effects/calculators/background_blur_calculator_webgpu.h"
#include "services/video_effects/calculators/downscale_calculator_webgpu.h"
#include "services/video_effects/calculators/in_place_copy_calculator_webgpu.h"
#include "services/video_effects/calculators/inference_calculator_webgpu.h"
#include "services/video_effects/calculators/mediapipe_webgpu_utils.h"
#include "services/video_effects/calculators/video_effects_graph_config.h"
#include "third_party/abseil-cpp/absl/status/status.h"
#include "third_party/mediapipe/src/mediapipe/framework/calculator.pb.h"
#include "third_party/mediapipe/src/mediapipe/framework/calculator_graph.h"
#include "third_party/mediapipe/src/mediapipe/framework/packet.h"
#include "third_party/mediapipe/src/mediapipe/framework/timestamp.h"
#include "third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.h"
#include "third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_format.h"
#include "third_party/mediapipe/src/mediapipe/gpu/webgpu/webgpu_texture_buffer.h"
#include "third_party/mediapipe/src/mediapipe/gpu/webgpu/webgpu_texture_view.h"
namespace video_effects {
namespace {
std::string StreamFromTagAndName(std::string_view tag,
std::string_view stream_name) {
return base::StrCat({tag, ":", stream_name});
}
} // namespace
// Graph inputs:
constexpr char kStaticConfigInputStreamName[] = "static_config";
constexpr char kRuntimeConfigInputStreamName[] = "runtime_config";
constexpr char kInputTextureInputStreamName[] = "input_texture";
constexpr char kOutputTextureInputStreamName[] = "output_texture";
// Intermediate streams:
constexpr char kDownscaledTextureStreamName[] = "downscaled_texture";
constexpr char kBackgroundMaskStreamName[] = "mask";
constexpr char kBluredTextureStreamName[] = "blurred_texture";
// Graph outputs:
constexpr char kOutputTextureOutputStreamName[] = "out";
VideoEffectsGraphWebGpu::~VideoEffectsGraphWebGpu() = default;
VideoEffectsGraphWebGpu::VideoEffectsGraphWebGpu(
std::unique_ptr<mediapipe::CalculatorGraph> graph)
: graph_(std::move(graph)) {}
// static
std::unique_ptr<VideoEffectsGraphWebGpu> VideoEffectsGraphWebGpu::Create() {
mediapipe::CalculatorGraphConfig config;
// Inputs for the entire graph:
config.add_input_side_packet(kStaticConfigInputStreamName);
config.add_input_stream(kRuntimeConfigInputStreamName);
config.add_input_stream(kInputTextureInputStreamName);
// Note: output texture is also provided as an input, the graph will be
// pouplating those with contents:
config.add_input_stream(kOutputTextureInputStreamName);
config.add_output_stream(kOutputTextureOutputStreamName);
auto* downscale_node = config.add_node();
downscale_node->set_calculator(DownscaleCalculatorWebGpu::kCalculatorName);
downscale_node->add_input_stream(
StreamFromTagAndName(DownscaleCalculatorWebGpu::kInputStreamTag,
kInputTextureInputStreamName));
downscale_node->add_output_stream(
StreamFromTagAndName(DownscaleCalculatorWebGpu::kOutputStreamTag,
kDownscaledTextureStreamName));
auto* inference_node = config.add_node();
inference_node->set_calculator(InferenceCalculatorWebGpu::kCalculatorName);
// Inputs for inference calculator node:
inference_node->add_input_side_packet(StreamFromTagAndName(
InferenceCalculatorWebGpu::kStaticConfigInputSidePacketStreamTag,
kStaticConfigInputStreamName));
inference_node->add_input_stream(StreamFromTagAndName(
InferenceCalculatorWebGpu::kRuntimeConfigInputStreamTag,
kRuntimeConfigInputStreamName));
inference_node->add_input_stream(
StreamFromTagAndName(InferenceCalculatorWebGpu::kInputTextureStreamTag,
kDownscaledTextureStreamName));
inference_node->add_output_stream(
StreamFromTagAndName(InferenceCalculatorWebGpu::kOutputTextureStreamTag,
kBackgroundMaskStreamName));
auto* blur_node = config.add_node();
blur_node->set_calculator(BackgroundBlurCalculatorWebGpu::kCalculatorName);
blur_node->add_input_stream(StreamFromTagAndName(
BackgroundBlurCalculatorWebGpu::kRuntimeConfigInputStreamTag,
kRuntimeConfigInputStreamName));
blur_node->add_input_stream(StreamFromTagAndName(
BackgroundBlurCalculatorWebGpu::kInputTextureStreamTag,
kInputTextureInputStreamName));
blur_node->add_input_stream(StreamFromTagAndName(
BackgroundBlurCalculatorWebGpu::kMaskTextureStreamTag,
kBackgroundMaskStreamName));
blur_node->add_output_stream(StreamFromTagAndName(
BackgroundBlurCalculatorWebGpu::kOutputTextureStreamTag,
kBluredTextureStreamName));
auto* in_place_copy_node = config.add_node();
in_place_copy_node->set_calculator(
InPlaceCopyCalculatorWebGpu::kCalculatorName);
in_place_copy_node->add_input_stream(StreamFromTagAndName(
InPlaceCopyCalculatorWebGpu::kInputStreamTag, kBluredTextureStreamName));
in_place_copy_node->add_input_stream(
StreamFromTagAndName(InPlaceCopyCalculatorWebGpu::kOutputInputStreamTag,
kOutputTextureInputStreamName));
in_place_copy_node->add_output_stream(
StreamFromTagAndName(InPlaceCopyCalculatorWebGpu::kOutputStreamTag,
kOutputTextureOutputStreamName));
// Dawn Wire over command buffer is not thread-safe, so we need to make
// MediaPipe use our own thread:
auto* executor = config.add_executor();
executor->set_type("ApplicationThreadExecutor");
auto graph = std::make_unique<mediapipe::CalculatorGraph>();
absl::Status status = graph->Initialize(config);
if (!status.ok()) {
return nullptr;
}
return base::WrapUnique(new VideoEffectsGraphWebGpu(std::move(graph)));
}
void VideoEffectsGraphWebGpu::OnFrameProcessed(
const mediapipe::Packet& packet) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto output_buffer = packet.Get<mediapipe::GpuBuffer>();
auto output_texture_view =
output_buffer.GetReadView<mediapipe::WebGpuTextureView>();
CHECK(on_frame_callback_);
on_frame_callback_.Run(output_texture_view.texture());
}
bool VideoEffectsGraphWebGpu::Start(
StaticConfig static_config,
base::RepeatingCallback<void(wgpu::Texture)> on_frame_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
on_frame_callback_ = std::move(on_frame_cb);
auto graph_cb =
base::BindRepeating(&VideoEffectsGraphWebGpu::OnFrameProcessed,
weak_ptr_factory_.GetWeakPtr());
absl::Status status = graph_->ObserveOutputStream(
kOutputTextureOutputStreamName,
[cb = std::move(graph_cb)](const mediapipe::Packet& packet) {
cb.Run(packet);
return absl::OkStatus();
});
status = graph_->StartRun(
{{kStaticConfigInputStreamName,
mediapipe::MakePacket<StaticConfig>(std::move(static_config))}});
if (!status.ok()) {
return false;
}
return true;
}
bool VideoEffectsGraphWebGpu::ProcessFrame(
base::TimeDelta timedelta,
wgpu::Texture input_texture,
wgpu::Texture output_texture,
const RuntimeConfig& runtime_config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (runtime_config.blur_state != BlurState::kEnabled) {
return false;
}
const mediapipe::Timestamp ts =
mediapipe::Timestamp::FromSeconds(timedelta.InSecondsF());
mediapipe::GpuBuffer input_texture_buffer(
std::make_shared<mediapipe::WebGpuTextureBuffer>(
input_texture, input_texture.GetWidth(), input_texture.GetHeight(),
WebGpuTextureFormatToGpuBufferFormat(input_texture.GetFormat())));
mediapipe::GpuBuffer output_texture_buffer(
std::make_shared<mediapipe::WebGpuTextureBuffer>(
output_texture, output_texture.GetWidth(), output_texture.GetHeight(),
WebGpuTextureFormatToGpuBufferFormat(output_texture.GetFormat())));
absl::Status status = graph_->AddPacketToInputStream(
kInputTextureInputStreamName, mediapipe::MakePacket<mediapipe::GpuBuffer>(
std::move(input_texture_buffer))
.At(ts));
if (!status.ok()) {
return false;
}
status = graph_->AddPacketToInputStream(
kOutputTextureInputStreamName,
mediapipe::MakePacket<mediapipe::GpuBuffer>(
std::move(output_texture_buffer))
.At(ts));
if (!status.ok()) {
return false;
}
status = graph_->AddPacketToInputStream(
kRuntimeConfigInputStreamName,
mediapipe::MakePacket<RuntimeConfig>(runtime_config).At(ts));
return status.ok();
}
bool VideoEffectsGraphWebGpu::WaitUntilIdle() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return graph_->WaitUntilIdle().ok();
}
} // namespace video_effects