From: pocopepe <[email protected]>
---
configure | 5 +
libavutil/Makefile | 3 +
libavutil/hwcontext.c | 4 +
libavutil/hwcontext.h | 1 +
libavutil/hwcontext_internal.h | 1 +
libavutil/hwcontext_webgpu.c | 339 +++++++++++++++++++++++++++++++++
libavutil/hwcontext_webgpu.h | 50 +++++
libavutil/pixdesc.c | 4 +
libavutil/pixfmt.h | 5 +
9 files changed, 412 insertions(+)
create mode 100644 libavutil/hwcontext_webgpu.c
create mode 100644 libavutil/hwcontext_webgpu.h
diff --git a/configure b/configure
index 2f6167fddb..9675e4f918 100755
--- a/configure
+++ b/configure
@@ -372,6 +372,7 @@ External library support:
--disable-vdpau disable Nvidia Video Decode and Presentation API
for Unix code [autodetect]
--disable-videotoolbox disable VideoToolbox code [autodetect]
--disable-vulkan disable Vulkan code [autodetect]
+ --disable-webgpu disable WebGPU code [autodetect]
--enable-vulkan-static statically link to libvulkan [no]
Toolchain options:
@@ -2142,6 +2143,7 @@ HWACCEL_AUTODETECT_LIBRARY_LIST="
vdpau
videotoolbox
vulkan
+ webgpu
v4l2_m2m
"
@@ -7744,6 +7746,9 @@ enabled vdpau &&
enabled vdpau &&
check_lib vdpau_x11 "vdpau/vdpau.h vdpau/vdpau_x11.h"
vdp_device_create_x11 -lvdpau -lX11
+enabled webgpu && { check_headers webgpu/webgpu.h ||
+ die "ERROR: WebGPU enabled but webgpu/webgpu.h not found."; }
+
# Check for the Vulkan headers
if enabled_all vulkan vulkan_static; then
check_pkg_config vulkan "vulkan >= 1.3.277" "vulkan/vulkan.h" "defined
VK_VERSION_1_3" ||
diff --git a/libavutil/Makefile b/libavutil/Makefile
index c5241895ff..ee1ef99cfb 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -56,6 +56,7 @@ HEADERS = adler32.h
\
hwcontext_videotoolbox.h \
hwcontext_vdpau.h \
hwcontext_vulkan.h \
+ hwcontext_webgpu.h \
iamf.h \
imgutils.h \
intfloat.h \
@@ -218,6 +219,7 @@ OBJS-$(CONFIG_VAAPI) += hwcontext_vaapi.o
OBJS-$(CONFIG_VIDEOTOOLBOX) += hwcontext_videotoolbox.o
OBJS-$(CONFIG_VDPAU) += hwcontext_vdpau.o
OBJS-$(CONFIG_VULKAN) += hwcontext_vulkan.o vulkan.o
+OBJS-$(CONFIG_WEBGPU) += hwcontext_webgpu.o
OBJS-$(!CONFIG_VULKAN) += hwcontext_stub.o
@@ -251,6 +253,7 @@ SKIPHEADERS-$(CONFIG_VDPAU) += hwcontext_vdpau.h
SKIPHEADERS-$(CONFIG_VULKAN) += hwcontext_vulkan.h vulkan.h \
vulkan_functions.h \
vulkan_loader.h
+SKIPHEADERS-$(CONFIG_WEBGPU) += hwcontext_webgpu.h
SKIPHEADERS-$(CONFIG_LIBSHADERC) += vulkan_spirv.h
SKIPHEADERS-$(CONFIG_LIBGLSLANG) += vulkan_spirv.h
SKIPHEADERS-$(CONFIG_SHADER_COMPRESSION) += zlib_utils.h
diff --git a/libavutil/hwcontext.c b/libavutil/hwcontext.c
index 83bd7457e8..4e3d221dd1 100644
--- a/libavutil/hwcontext.c
+++ b/libavutil/hwcontext.c
@@ -71,6 +71,9 @@ static const HWContextType * const hw_table[] = {
#endif
#if CONFIG_OHCODEC
&ff_hwcontext_type_oh,
+#endif
+#if CONFIG_WEBGPU
+ &ff_hwcontext_type_webgpu,
#endif
NULL,
};
@@ -90,6 +93,7 @@ static const char *const hw_type_names[] = {
[AV_HWDEVICE_TYPE_VULKAN] = "vulkan",
[AV_HWDEVICE_TYPE_AMF] = "amf",
[AV_HWDEVICE_TYPE_OHCODEC] = "ohcodec",
+ [AV_HWDEVICE_TYPE_WEBGPU]= "webgpu",
};
typedef struct FFHWDeviceContext {
diff --git a/libavutil/hwcontext.h b/libavutil/hwcontext.h
index 29374cf0a7..0f77cbe6b1 100644
--- a/libavutil/hwcontext.h
+++ b/libavutil/hwcontext.h
@@ -41,6 +41,7 @@ enum AVHWDeviceType {
AV_HWDEVICE_TYPE_AMF,
/* OpenHarmony Codec device */
AV_HWDEVICE_TYPE_OHCODEC,
+ AV_HWDEVICE_TYPE_WEBGPU,
};
/**
diff --git a/libavutil/hwcontext_internal.h b/libavutil/hwcontext_internal.h
index dcfdc2016a..bde476c414 100644
--- a/libavutil/hwcontext_internal.h
+++ b/libavutil/hwcontext_internal.h
@@ -165,5 +165,6 @@ extern const HWContextType ff_hwcontext_type_mediacodec;
extern const HWContextType ff_hwcontext_type_vulkan;
extern const HWContextType ff_hwcontext_type_amf;
extern const HWContextType ff_hwcontext_type_oh;
+extern const HWContextType ff_hwcontext_type_webgpu;
#endif /* AVUTIL_HWCONTEXT_INTERNAL_H */
diff --git a/libavutil/hwcontext_webgpu.c b/libavutil/hwcontext_webgpu.c
new file mode 100644
index 0000000000..aefde69683
--- /dev/null
+++ b/libavutil/hwcontext_webgpu.c
@@ -0,0 +1,339 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <string.h>
+
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+#endif
+
+#include <webgpu/webgpu.h>
+
+#include "hwcontext.h"
+#include "hwcontext_internal.h"
+#include "hwcontext_webgpu.h"
+#include "mem.h"
+#include "log.h"
+#include "pixdesc.h"
+
+#define DEFAULT_TEXTURE_USAGE \
+ (WGPUTextureUsage_CopyDst | WGPUTextureUsage_CopySrc | \
+ WGPUTextureUsage_StorageBinding | WGPUTextureUsage_TextureBinding)
+
+typedef struct WebGPUDevicePriv {
+ AVWebGPUDeviceContext p;
+ int async_done;
+} WebGPUDevicePriv;
+
+typedef struct WebGPUFramesPriv {
+ AVWebGPUFramesContext p;
+} WebGPUFramesPriv;
+
+typedef struct {
+ int done;
+ int error;
+} MapContext;
+
+/* Yield until a WebGPU callback sets *flag. Emscripten requires
+ * emscripten_sleep() to hand control back to the JS event loop;
+ * native builds use wgpuInstanceProcessEvents() instead. */
+static av_always_inline void webgpu_await(WGPUInstance instance, volatile int
*flag)
+{
+#ifdef __EMSCRIPTEN__
+ while (!*flag)
+ emscripten_sleep(1);
+#else
+ while (!*flag)
+ wgpuInstanceProcessEvents(instance);
+#endif
+}
+
+static void on_adapter_ready(WGPURequestAdapterStatus status, WGPUAdapter
adapter,
+ WGPUStringView message, void *userdata1, void
*userdata2)
+{
+ WebGPUDevicePriv *priv = userdata1;
+ if (status == WGPURequestAdapterStatus_Success)
+ priv->p.adapter = adapter;
+ priv->async_done = 1;
+}
+
+static void on_device_ready(WGPURequestDeviceStatus status, WGPUDevice device,
+ WGPUStringView message, void *userdata1, void
*userdata2)
+{
+ WebGPUDevicePriv *priv = userdata1;
+ if (status == WGPURequestDeviceStatus_Success) {
+ priv->p.device = device;
+ priv->p.queue = wgpuDeviceGetQueue(device);
+ }
+ priv->async_done = 1;
+}
+
+static void on_buffer_mapped(WGPUMapAsyncStatus status, WGPUStringView message,
+ void *userdata1, void *userdata2)
+{
+ MapContext *ctx = userdata1;
+ ctx->done = 1;
+ if (status != WGPUMapAsyncStatus_Success)
+ ctx->error = 1;
+}
+
+static int webgpu_device_create(AVHWDeviceContext *ctx, const char *device,
+ AVDictionary *opts, int flags)
+{
+ WebGPUDevicePriv *priv = ctx->hwctx;
+ WGPUInstanceDescriptor desc = { 0 };
+
+ priv->p.instance = wgpuCreateInstance(&desc);
+ if (!priv->p.instance) {
+ av_log(ctx, AV_LOG_ERROR, "Could not initialize WebGPU instance.\n");
+ return AVERROR_UNKNOWN;
+ }
+
+ WGPURequestAdapterOptions adapter_opts = { 0 };
+ WGPURequestAdapterCallbackInfo adapter_cb = {
+ .callback = on_adapter_ready,
+ .mode = WGPUCallbackMode_AllowSpontaneous,
+ .userdata1 = priv,
+ };
+
+ priv->async_done = 0;
+ wgpuInstanceRequestAdapter(priv->p.instance, &adapter_opts, adapter_cb);
+ webgpu_await(priv->p.instance, &priv->async_done);
+
+ if (!priv->p.adapter) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to get WebGPU adapter.\n");
+ return AVERROR_UNKNOWN;
+ }
+
+ WGPUDeviceDescriptor dev_desc = { 0 };
+ WGPURequestDeviceCallbackInfo device_cb = {
+ .callback = on_device_ready,
+ .mode = WGPUCallbackMode_AllowSpontaneous,
+ .userdata1 = priv,
+ };
+
+ priv->async_done = 0;
+ wgpuAdapterRequestDevice(priv->p.adapter, &dev_desc, device_cb);
+ webgpu_await(priv->p.instance, &priv->async_done);
+
+ if (!priv->p.device) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to get WebGPU device.\n");
+ return AVERROR_UNKNOWN;
+ }
+
+ av_log(ctx, AV_LOG_VERBOSE, "WebGPU device created successfully.\n");
+ return 0;
+}
+
+static void webgpu_device_uninit(AVHWDeviceContext *ctx)
+{
+ WebGPUDevicePriv *priv = ctx->hwctx;
+ if (priv->p.queue) wgpuQueueRelease(priv->p.queue);
+ if (priv->p.device) wgpuDeviceRelease(priv->p.device);
+ if (priv->p.adapter) wgpuAdapterRelease(priv->p.adapter);
+ if (priv->p.instance) wgpuInstanceRelease(priv->p.instance);
+}
+
+static int webgpu_frames_get_constraints(AVHWDeviceContext *ctx,
+ const void *hwconfig,
+ AVHWFramesConstraints *constraints)
+{
+ constraints->valid_sw_formats = av_malloc_array(2,
sizeof(*constraints->valid_sw_formats));
+ if (!constraints->valid_sw_formats)
+ return AVERROR(ENOMEM);
+ constraints->valid_sw_formats[0] = AV_PIX_FMT_RGBA;
+ constraints->valid_sw_formats[1] = AV_PIX_FMT_NONE;
+
+ constraints->valid_hw_formats = av_malloc_array(2,
sizeof(*constraints->valid_hw_formats));
+ if (!constraints->valid_hw_formats)
+ return AVERROR(ENOMEM);
+ constraints->valid_hw_formats[0] = AV_PIX_FMT_WEBGPU;
+ constraints->valid_hw_formats[1] = AV_PIX_FMT_NONE;
+
+ return 0;
+}
+
+static int webgpu_frames_init(AVHWFramesContext *hwfc)
+{
+ WebGPUFramesPriv *fpriv = hwfc->hwctx;
+
+ if (hwfc->sw_format != AV_PIX_FMT_RGBA) {
+ av_log(hwfc, AV_LOG_ERROR, "Only AV_PIX_FMT_RGBA is supported as
sw_format.\n");
+ return AVERROR(EINVAL);
+ }
+
+ if (!fpriv->p.usage)
+ fpriv->p.usage = DEFAULT_TEXTURE_USAGE;
+ if (!fpriv->p.format)
+ fpriv->p.format = WGPUTextureFormat_RGBA8Unorm;
+
+ return 0;
+}
+
+static void webgpu_frame_free(void *opaque, uint8_t *data)
+{
+ AVWebGPUFrame *f = (AVWebGPUFrame *)data;
+ if (f->view) wgpuTextureViewRelease(f->view);
+ if (f->texture) wgpuTextureRelease(f->texture);
+ av_free(f);
+}
+
+static int webgpu_get_buffer(AVHWFramesContext *hwfc, AVFrame *frame)
+{
+ WebGPUDevicePriv *priv = hwfc->device_ctx->hwctx;
+ WebGPUFramesPriv *fpriv = hwfc->hwctx;
+
+ AVWebGPUFrame *f = av_mallocz(sizeof(AVWebGPUFrame));
+ if (!f)
+ return AVERROR(ENOMEM);
+
+ WGPUTextureDescriptor tex_desc = {
+ .usage = fpriv->p.usage,
+ .dimension = WGPUTextureDimension_2D,
+ .size = (WGPUExtent3D){ hwfc->width, hwfc->height, 1 },
+ .format = fpriv->p.format,
+ .mipLevelCount = 1,
+ .sampleCount = 1,
+ };
+
+ f->texture = wgpuDeviceCreateTexture(priv->p.device, &tex_desc);
+ if (!f->texture) {
+ av_free(f);
+ return AVERROR_EXTERNAL;
+ }
+ f->view = wgpuTextureCreateView(f->texture, NULL);
+
+ frame->data[0] = (uint8_t *)f;
+ frame->format = AV_PIX_FMT_WEBGPU;
+ frame->width = hwfc->width;
+ frame->height = hwfc->height;
+ frame->buf[0] = av_buffer_create((uint8_t *)f, sizeof(AVWebGPUFrame),
+ webgpu_frame_free, hwfc, 0);
+ if (!frame->buf[0]) {
+ webgpu_frame_free(hwfc, (uint8_t *)f);
+ return AVERROR(ENOMEM);
+ }
+ return 0;
+}
+
+static int webgpu_transfer_data_to(AVHWFramesContext *hwfc, AVFrame *dst,
const AVFrame *src)
+{
+ WebGPUDevicePriv *priv = hwfc->device_ctx->hwctx;
+ AVWebGPUFrame *dst_f = (AVWebGPUFrame *)dst->data[0];
+
+ if (src->format != AV_PIX_FMT_RGBA) {
+ av_log(hwfc, AV_LOG_ERROR, "Only AV_PIX_FMT_RGBA source is
supported.\n");
+ return AVERROR(EINVAL);
+ }
+
+ WGPUTexelCopyTextureInfo copy_dest = { .texture = dst_f->texture };
+ WGPUTexelCopyBufferLayout layout = {
+ .bytesPerRow = src->linesize[0],
+ .rowsPerImage = src->height,
+ };
+ WGPUExtent3D write_size = { src->width, src->height, 1 };
+
+ wgpuQueueWriteTexture(priv->p.queue, ©_dest, src->data[0],
+ src->linesize[0] * src->height, &layout,
&write_size);
+ return 0;
+}
+
+static int webgpu_transfer_data_from(AVHWFramesContext *hwfc, AVFrame *dst,
const AVFrame *src)
+{
+ WebGPUDevicePriv *priv = hwfc->device_ctx->hwctx;
+ AVWebGPUFrame *src_f = (AVWebGPUFrame *)src->data[0];
+
+ if (dst->format != AV_PIX_FMT_RGBA) {
+ av_log(hwfc, AV_LOG_ERROR, "Only AV_PIX_FMT_RGBA destination is
supported.\n");
+ return AVERROR(EINVAL);
+ }
+
+ uint32_t padded_bytes_per_row = (dst->width * 4 + 255) & ~255;
+ uint64_t buf_size = (uint64_t)padded_bytes_per_row * dst->height;
+
+ WGPUBufferDescriptor buf_desc = {
+ .usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead,
+ .size = buf_size,
+ };
+ WGPUBuffer staging_buffer = wgpuDeviceCreateBuffer(priv->p.device,
&buf_desc);
+ if (!staging_buffer)
+ return AVERROR_EXTERNAL;
+
+ WGPUCommandEncoderDescriptor enc_desc = { 0 };
+ WGPUCommandEncoder encoder =
wgpuDeviceCreateCommandEncoder(priv->p.device, &enc_desc);
+
+ WGPUTexelCopyTextureInfo copy_src = { .texture = src_f->texture };
+ WGPUTexelCopyBufferInfo copy_buf = {
+ .buffer = staging_buffer,
+ .layout.bytesPerRow = padded_bytes_per_row,
+ .layout.rowsPerImage = dst->height,
+ };
+ WGPUExtent3D copy_size = { dst->width, dst->height, 1 };
+
+ wgpuCommandEncoderCopyTextureToBuffer(encoder, ©_src, ©_buf,
©_size);
+
+ WGPUCommandBufferDescriptor cmd_desc = { 0 };
+ WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, &cmd_desc);
+ wgpuQueueSubmit(priv->p.queue, 1, &commands);
+ wgpuCommandBufferRelease(commands);
+ wgpuCommandEncoderRelease(encoder);
+
+ MapContext map_ctx = { .done = 0, .error = 0 };
+ WGPUBufferMapCallbackInfo cb_info = {
+ .callback = on_buffer_mapped,
+ .mode = WGPUCallbackMode_AllowSpontaneous,
+ .userdata1 = &map_ctx,
+ };
+
+ wgpuBufferMapAsync(staging_buffer, WGPUMapMode_Read, 0, buf_size, cb_info);
+ webgpu_await(priv->p.instance, &map_ctx.done);
+
+ if (map_ctx.error) {
+ wgpuBufferRelease(staging_buffer);
+ return AVERROR_EXTERNAL;
+ }
+
+ const uint8_t *mapped = wgpuBufferGetConstMappedRange(staging_buffer, 0,
buf_size);
+ for (int y = 0; y < dst->height; y++) {
+ memcpy(dst->data[0] + y * dst->linesize[0],
+ mapped + y * padded_bytes_per_row,
+ dst->width * 4);
+ }
+
+ wgpuBufferUnmap(staging_buffer);
+ wgpuBufferRelease(staging_buffer);
+ return 0;
+}
+
+const HWContextType ff_hwcontext_type_webgpu = {
+ .type = AV_HWDEVICE_TYPE_WEBGPU,
+ .name = "WebGPU",
+ .device_hwctx_size = sizeof(WebGPUDevicePriv),
+ .frames_hwctx_size = sizeof(WebGPUFramesPriv),
+ .device_create = webgpu_device_create,
+ .device_uninit = webgpu_device_uninit,
+ .frames_get_constraints = webgpu_frames_get_constraints,
+ .frames_init = webgpu_frames_init,
+ .frames_get_buffer = webgpu_get_buffer,
+ .transfer_data_to = webgpu_transfer_data_to,
+ .transfer_data_from = webgpu_transfer_data_from,
+ .pix_fmts = (const enum AVPixelFormat[]) {
+ AV_PIX_FMT_WEBGPU,
+ AV_PIX_FMT_NONE
+ },
+};
diff --git a/libavutil/hwcontext_webgpu.h b/libavutil/hwcontext_webgpu.h
new file mode 100644
index 0000000000..89dfa19814
--- /dev/null
+++ b/libavutil/hwcontext_webgpu.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#ifndef AVUTIL_HWCONTEXT_WEBGPU_H
+#define AVUTIL_HWCONTEXT_WEBGPU_H
+
+#include <webgpu/webgpu.h>
+#include "frame.h"
+
+/**
+ * @file
+ * API-specific header for AV_HWDEVICE_TYPE_WEBGPU.
+ */
+
+/**
+ * Main WebGPU context, allocated as AVHWDeviceContext.hwctx.
+ */
+typedef struct AVWebGPUDeviceContext {
+ WGPUInstance instance;
+ WGPUAdapter adapter;
+ WGPUDevice device;
+ WGPUQueue queue;
+} AVWebGPUDeviceContext;
+
+/**
+ * Allocated as AVHWFramesContext.hwctx.
+ *
+ * usage and format are optional; zero values fall back to
+ * (CopyDst|CopySrc|StorageBinding|TextureBinding) and RGBA8Unorm respectively.
+ */
+typedef struct AVWebGPUFramesContext {
+ WGPUTextureUsage usage;
+ WGPUTextureFormat format;
+} AVWebGPUFramesContext;
+
+/**
+ * Hardware frame. AVFrame->data[0] points to this when format is
AV_PIX_FMT_WEBGPU.
+ */
+typedef struct AVWebGPUFrame {
+ WGPUTexture texture;
+ WGPUTextureView view;
+} AVWebGPUFrame;
+
+#endif /* AVUTIL_HWCONTEXT_WEBGPU_H */
diff --git a/libavutil/pixdesc.c b/libavutil/pixdesc.c
index 90f9596def..86d440cce8 100644
--- a/libavutil/pixdesc.c
+++ b/libavutil/pixdesc.c
@@ -3267,6 +3267,10 @@ static const AVPixFmtDescriptor
av_pix_fmt_descriptors[AV_PIX_FMT_NB] = {
.name = "ohcodec",
.flags = AV_PIX_FMT_FLAG_HWACCEL,
},
+ [AV_PIX_FMT_WEBGPU] = {
+ .name = "webgpu",
+ .flags = AV_PIX_FMT_FLAG_HWACCEL,
+ },
};
static const char * const color_range_names[] = {
diff --git a/libavutil/pixfmt.h b/libavutil/pixfmt.h
index 2e7b6457e0..cc3d541002 100644
--- a/libavutil/pixfmt.h
+++ b/libavutil/pixfmt.h
@@ -498,6 +498,11 @@ enum AVPixelFormat {
AV_PIX_FMT_GBRP12MSBLE, ///< planar GBR 4:4:4 36bpp, lowest bits zero,
little-endian
AV_PIX_FMT_OHCODEC, /// hardware decoding through openharmony
+ /**
+ * HW acceleration through WebGPU. data[0] contain an
+ * AVWebGPU Frame.
+ */
+ AV_PIX_FMT_WEBGPU,
AV_PIX_FMT_NB ///< number of pixel formats, DO NOT USE THIS if you
want to link with shared libav* because the number of formats might differ
between versions
};
--
2.53.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]