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, &copy_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, &copy_src, &copy_buf, 
&copy_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]

Reply via email to