blob: ac89741a7d087efe0a007ab0cab6e2c245438065 [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/downscale_calculator_webgpu.h"
#include "base/bit_cast.h"
#include "base/strings/strcat.h"
#include "base/strings/to_string.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/dawn/include/dawn/webgpu_cpp_print.h"
#include "third_party/mediapipe/src/mediapipe/framework/calculator_context.h"
#include "third_party/mediapipe/src/mediapipe/framework/calculator_contract.h"
#include "third_party/mediapipe/src/mediapipe/framework/calculator_registry.h"
#include "third_party/mediapipe/src/mediapipe/framework/packet.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_service.h"
#include "third_party/mediapipe/src/mediapipe/gpu/webgpu/webgpu_texture_view.h"
namespace video_effects {
namespace {
wgpu::RenderPipeline CreateRenderPipelineForTextureCopy(
wgpu::Device device,
wgpu::TextureFormat destination_format) {
std::vector<wgpu::BindGroupLayoutEntry> entries;
entries.push_back({
.binding = 0,
.visibility = wgpu::ShaderStage::Fragment,
.sampler =
{
.type = wgpu::SamplerBindingType::NonFiltering,
},
});
entries.push_back(
{.binding = 1,
.visibility = wgpu::ShaderStage::Fragment,
.texture = {
.sampleType = wgpu::TextureSampleType::UnfilterableFloat,
.viewDimension = wgpu::TextureViewDimension::e2D,
.multisampled = false,
}});
entries.push_back({
.binding = 2,
.visibility = wgpu::ShaderStage::Fragment,
.buffer =
{
.type = wgpu::BufferBindingType::Uniform,
},
});
const std::string bind_group_layout_label =
base::StrCat({"DownscaleCalculatorTextureCopyBindGroup::",
base::ToString(destination_format)});
wgpu::BindGroupLayoutDescriptor bind_group_layout_descriptor = {
.label = bind_group_layout_label.c_str(),
.entryCount = entries.size(),
.entries = entries.data(),
};
wgpu::BindGroupLayout bind_group_layout =
device.CreateBindGroupLayout(&bind_group_layout_descriptor);
const std::string pipeline_layout_label =
base::StrCat({"DownscaleCalculatorTextureCopyPipelineLayout::",
base::ToString(destination_format)});
wgpu::PipelineLayoutDescriptor pipeline_layout_descriptor = {
.label = pipeline_layout_label.c_str(),
.bindGroupLayoutCount = 1,
.bindGroupLayouts = &bind_group_layout,
};
wgpu::ShaderModuleWGSLDescriptor shader_module_wgsl_descriptor;
shader_module_wgsl_descriptor.code = R"(
struct Uniforms {
perform_y_flip: u32, // bools are not host-shareable
}
@group(0) @binding(0) var sourceTextureSampler: sampler;
@group(0) @binding(1) var sourceTexture: texture_2d<f32>;
@group(0) @binding(2) var<uniform> uniforms: Uniforms;
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) uv: vec2f,
};
@vertex
fn vertex_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput {
// x,y coordinates of our triangles, in clip space:
var triangle_points = array<vec2<f32>, 6>(
// 1st triangle:
vec2<f32>(-1.0, -1.0), // lower left
vec2<f32>( 1.0, 1.0), // upper right
vec2<f32>(-1.0, 1.0), // upper left
// 2nd triangle:
vec2<f32>(-1.0, -1.0), // lower left
vec2<f32>( 1.0, -1.0), // lower right
vec2<f32>( 1.0, 1.0), // upper right
);
let xy = triangle_points[in_vertex_index];
var out: VertexOutput;
// `position` is a vec4 so let's extend x,y with z=0.0 (at near plane), w=1.0.
// (with w=1.0, x,y,z will be unchanged during clip space -> NDC conversion)
out.position = vec4f(xy, 0.0, 1.0);
// Transform from clip space to UV:
out.uv = xy.xy * 0.5 + 0.5; // [-1; 1] => [0; 1]
return out;
}
@fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4f {
let uvs = select(
in.uv, vec2f(in.uv.x, 1.0 - in.uv.y), uniforms.perform_y_flip == 1);
return textureSample(sourceTexture, sourceTextureSampler, uvs);
}
)";
const std::string shader_label =
base::StrCat({"DownscaleCalculatorTextureCopyShaderModule::",
base::ToString(destination_format)});
wgpu::ShaderModuleDescriptor shader_module_descriptor = {
.nextInChain = &shader_module_wgsl_descriptor,
.label = shader_label.c_str(),
};
wgpu::ShaderModule shader_module =
device.CreateShaderModule(&shader_module_descriptor);
wgpu::ColorTargetState color_target_state = {
.format = destination_format,
.blend = nullptr,
.writeMask = wgpu::ColorWriteMask::All,
};
wgpu::FragmentState fragment_state = {
.module = shader_module,
.entryPoint = "fragment_main",
.constantCount = 0,
.constants = nullptr,
.targetCount = 1,
.targets = &color_target_state,
};
const std::string pipeline_label =
base::StrCat({"DownscaleCalculatorTextureCopyRenderPipeline::",
base::ToString(destination_format)});
wgpu::RenderPipelineDescriptor pipeline_descriptor = {
.label = pipeline_label.c_str(),
.layout = device.CreatePipelineLayout(&pipeline_layout_descriptor),
.vertex =
{
.module = shader_module,
.entryPoint = "vertex_main",
.constantCount = 0,
.constants = nullptr,
},
.primitive =
{
.topology = wgpu::PrimitiveTopology::TriangleList,
.stripIndexFormat = wgpu::IndexFormat::Undefined,
.frontFace = wgpu::FrontFace::CCW,
.cullMode = wgpu::CullMode::Back,
},
.depthStencil = nullptr, // no need for depth test
.multisample = {}, // no need to multisample
.fragment = &fragment_state,
};
return device.CreateRenderPipeline(&pipeline_descriptor);
}
} // namespace
DownscaleCalculatorWebGpu::DownscaleCalculatorWebGpu() = default;
DownscaleCalculatorWebGpu::~DownscaleCalculatorWebGpu() = default;
absl::Status DownscaleCalculatorWebGpu::GetContract(
mediapipe::CalculatorContract* cc) {
cc->UseService(mediapipe::kWebGpuService);
cc->Inputs().Tag(kInputStreamTag).Set<mediapipe::GpuBuffer>();
cc->Outputs().Tag(kOutputStreamTag).Set<mediapipe::GpuBuffer>();
return absl::OkStatus();
}
absl::Status DownscaleCalculatorWebGpu::Open(mediapipe::CalculatorContext* cc) {
device_ = cc->Service(mediapipe::kWebGpuService).GetObject().device();
if (!device_) {
return absl::InternalError(
"Failed to obtain the WebGPU device from the service!");
}
wgpu::BufferDescriptor uniforms_descriptor = {
.label = "VideoEffectsProcessorTextureCopyUniforms",
.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst,
.size = sizeof(uint32_t),
.mappedAtCreation = false,
};
texture_copy_uniforms_buffer_ = device_.CreateBuffer(&uniforms_descriptor);
render_pipeline_ = CreateRenderPipelineForTextureCopy(
device_, wgpu::TextureFormat::RGBA32Float);
return absl::OkStatus();
}
absl::Status DownscaleCalculatorWebGpu::Process(
mediapipe::CalculatorContext* cc) {
mediapipe::Packet& input_packet = cc->Inputs().Tag(kInputStreamTag).Value();
auto input_buffer = input_packet.Get<mediapipe::GpuBuffer>();
auto input_texture_view =
input_buffer.GetReadView<mediapipe::WebGpuTextureView>();
mediapipe::GpuBuffer output_buffer(
kInferenceInputBufferWidth, kInferenceInputBufferHeight,
WebGpuTextureFormatToGpuBufferFormat(kInferenceInputBufferFormat));
auto output_texture_view =
output_buffer.GetWriteView<mediapipe::WebGpuTextureView>();
wgpu::CommandEncoderDescriptor command_encoder_descriptor = {
.label = "DownscaleCalculatorComandEncoder"};
wgpu::CommandEncoder command_encoder =
device_.CreateCommandEncoder(&command_encoder_descriptor);
wgpu::RenderPassColorAttachment destination_color_attachment = {
.view = output_texture_view.texture().CreateView(),
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue =
{
.r = 1.0,
.g = 0.0,
.b = 0.0,
.a = 1.0,
},
};
const std::vector<wgpu::BindGroupEntry> entries = {
{.binding = 0, .sampler = device_.CreateSampler()},
{.binding = 1, .textureView = input_texture_view.texture().CreateView()},
{.binding = 2, .buffer = texture_copy_uniforms_buffer_},
};
wgpu::BindGroupDescriptor bind_group_descriptor = {
.label = "DownscaleCalculatorBindGroup",
.layout = render_pipeline_.GetBindGroupLayout(0),
.entryCount = entries.size(),
.entries = entries.data(),
};
const uint32_t uniforms_perform_y_flip = true;
auto uniforms_bytes = base::bit_cast<
std::array<const uint8_t, sizeof(uniforms_perform_y_flip)>>(
uniforms_perform_y_flip);
command_encoder.WriteBuffer(texture_copy_uniforms_buffer_, 0,
uniforms_bytes.data(), uniforms_bytes.size());
wgpu::RenderPassDescriptor render_pass_descriptor = {
.label = "DownscaleCalculatorRenderPass",
.colorAttachmentCount = 1,
.colorAttachments = &destination_color_attachment,
};
wgpu::RenderPassEncoder render_pass_encoder =
command_encoder.BeginRenderPass(&render_pass_descriptor);
render_pass_encoder.SetPipeline(render_pipeline_);
render_pass_encoder.SetBindGroup(
0, device_.CreateBindGroup(&bind_group_descriptor));
render_pass_encoder.Draw(6);
render_pass_encoder.End();
wgpu::CommandBufferDescriptor command_buffer_descriptor = {
.label = "DownscaleCalculatorCommandBuffer"};
auto command_buffer = command_encoder.Finish(&command_buffer_descriptor);
device_.GetQueue().Submit(1, &command_buffer);
cc->Outputs()
.Tag(kOutputStreamTag)
.AddPacket(
mediapipe::MakePacket<mediapipe::GpuBuffer>(std::move(output_buffer))
.At(input_packet.Timestamp()));
return absl::OkStatus();
}
absl::Status DownscaleCalculatorWebGpu::Close(
mediapipe::CalculatorContext* cc) {
return absl::OkStatus();
}
REGISTER_CALCULATOR(DownscaleCalculatorWebGpu)
} // namespace video_effects