首页
/ Electron SharedTextureImportTextureInfo:共享纹理导入对象详解与源码级原理剖析

Electron SharedTextureImportTextureInfo:共享纹理导入对象详解与源码级原理剖析

2026-09-06 15:54:32作者:宣聪麟

本文围绕 Electron 的 SharedTextureImportTextureInfo 对象展开——它是 sharedTexture 模块导入外部 GPU 共享纹理时必传的纹理描述信息(textureInfo)。读完后你将掌握:该对象每个字段的完整取值与平台差异(Windows NT HANDLE / macOS IOSurface / Linux dmabuf planes)、它在主进程与渲染进程间的流转路径,以及 Electron 底层如何用 Chromium SharedImage 基础设施把它变成可跨进程传递的 VideoFrame

一、它是什么:从「纹理描述」到「VideoFrame」的入口

sharedTexture 是 Electron 的实验性模块,官方定位是:将外部共享纹理导入 Electron 并转换为平台无关的 Web VideoFrame,支持所有 Web 渲染系统,且可跨 Electron 进程传递(见 docs/api/shared-texture.md)。其典型输入来源是开启 webPreferences.offscreen.useSharedTexture 后,离屏渲染(OSR)paint 事件携带的 OffscreenSharedTexture——该对象上的 textureInfo 字段就是一个 SharedTextureImportTextureInfo(见 docs/api/structures/offscreen-shared-texture.mddocs/api/web-contents.mdpaint 事件说明)。

三条 API 入口都消费这个对象:

  • sharedTexture.importSharedTexture({ textureInfo, allReferencesReleased }):管理式 API,主进程调用,返回 SharedTextureImported,由 Electron 统一跟踪各进程引用并在全部释放后回调;
  • sharedTexture.subtle.importSharedTexture(textureInfo):subtle API,手动管理生命周期,返回 SharedTextureImportedSubtle
  • sharedTexture.subtle.finishTransferSharedTexture(transfer):目标进程完成跨进程传输,transfer 对象内部同样携带像素格式、尺寸等元数据(见 docs/api/structures/shared-texture-subtle.md)。

SharedTextureImportTextureInfo 的完整字段定义如下(原文见 docs/api/structures/shared-texture-import-texture-info.md):

{
  pixelFormat: 'bgra',     // 必填:纹理像素格式
  colorSpace: undefined,   // 可选:ColorSpace 对象
  codedSize: { width: 1280, height: 720 }, // 必填:共享纹理的完整尺寸
  visibleRect: { x: 0, y: 0, width: 1280, height: 720 }, // 可选:可见区域子集
  timestamp: 123456,       // 可选:微秒时间戳
  handle: { /* 平台句柄,见下文 */ } // 必填:SharedTextureHandle
}

二、字段逐项解析

2.1 pixelFormat:六种受支持的纹理格式

pixelFormat 是字符串,决定底层 media::VideoPixelFormatSharedImage 的格式映射。官方文档列出的取值为:

取值 含义
bgra 32bpp BGRA(字节序),单平面
rgba 32bpp RGBA(字节序),单平面
rgbaf16 半浮点 RGBA(Half float),单平面
nv12 12bpp,Y 平面后接 2x2 交错 UV 平面
nv16 16bpp,Y 平面后接 2x1 交错 UV 平面
p010le 4:2:0 10-bit YUV(小端),Y 平面后接 2x2 交错 UV 平面

从源码实现看,字符串到内部枚举的映射在 electron_api_shared_texture.cc 的 gin Converter 中完成:

if (pixel_format_str == "bgra")
  out->pixel_format = media::PIXEL_FORMAT_ARGB;
else if (pixel_format_str == "rgba")
  out->pixel_format = media::PIXEL_FORMAT_ABGR;
else if (pixel_format_str == "rgbaf16")
  out->pixel_format = media::PIXEL_FORMAT_RGBAF16;
else if (pixel_format_str == "nv12")
  out->pixel_format = media::PIXEL_FORMAT_NV12;
else if (pixel_format_str == "nv16")
  out->pixel_format = media::PIXEL_FORMAT_NV16;
else if (pixel_format_str == "p010le")
  out->pixel_format = media::PIXEL_FORMAT_P010LE;
else
  return false;  // 非法取值最终抛出 "Invalid shared texture info object"

注意两点:bgra 对应的是 media::PIXEL_FORMAT_ARGB(Chromium 命名习惯,字节序仍以 B 打头);字符串不匹配六种取值之一时,Converter 直接返回 false,外层 ImportSharedTexture 随即抛出 TypeError Invalid shared texture info objectL707-L714)。反向的枚举→字符串转换函数 TransferVideoPixelFormatToStringL136-L153,它在跨进程传输(startTransferSharedTexture)时把格式序列进 transfer 对象。

2.2 colorSpace:可选颜色空间

colorSpace 可选,类型为 ColorSpace。源码中该字段缺省值为 sRGB——ImportSharedTextureInfo::color_space = gfx::ColorSpace::CreateSRGB()L582),解析在 L640。创建 SharedImagecolor_space 会随格式一起传入 sii->CreateSharedImage(...)L799-L802);之后由 SharedImage 承载,getVideoFrame() 生成的 VideoFrame 再通过 raw_frame->set_color_space(si->color_space()) 携带到 Web 层(L259)。因此做 HDR(如 p010le)纹理导入时应显式传入对应颜色空间。

2.3 codedSizevisibleRect:完整尺寸 vs 可见区域

  • codedSizeSize)必填:共享纹理的完整编码尺寸,对应源码 gfx::Size coded_size
  • visibleRectRectangle)可选:[0, 0, codedSize.width, codedSize.height] 的子集,常见情况下就是全区域。

解析逻辑印证了这一点(L636-L639):

dict.Get("codedSize", &out->coded_size);
if (!dict.Get("visibleRect", &out->visible_rect)) {
  out->visible_rect = gfx::Rect(out->coded_size);  // 缺省即整个 codedSize
}

visible_rect 随后原样传给 media::VideoFrame::WrapSharedImage(...)L253-L257),因此它直接影响最终 VideoFrame 中有效像素的范围——例如外部编码器输出带填充(padding)的缓冲时,可用它裁剪出真实画面区域。

2.4 timestamp:微秒时间戳

可选 number,单位微秒,会被反射到生成的 VideoFrame 上(L257base::Microseconds(ist->timestamp))。内部注释称其为 "the capture timestamp, microseconds since capture start"(L584-L585)。对实时视频管线而言,这个值可用于与音频时钟对齐、做帧调度判断。

2.5 handle:平台相关的 SharedTextureHandle

handle 必填,类型为 SharedTextureHandle,按平台提供互斥的字段:

  • WindowsntHandle(Buffer)。必须是 NT HANDLE(由 CreateSharedHandle 产生),且必须是已经复制到当前进程的句柄;文档特别指出 rgba/bgra/rgbaf16 格式的纹理句柄不带 keyed mutex,而 nv12 格式带 keyed mutex。源码侧从 Buffer 中读取 8 字节指针值(GetNativeHandleL650-L660),导入时先 DuplicateHandle 出一个可托管副本,再包装成 gfx::DXGIHandleL717-L735)——这正是设计文档强调的「非 NT HANDLE 是进程本地的,Chromium 销毁 GpuMemoryBuffer 时会 CloseHandle,若传入旧式全局句柄会导致崩溃」(见 shell/common/api/shared_texture/README.md 第 2 节)。
  • macOSioSurface(Buffer),持有 IOSurfaceRef 指针,必须是当前进程中有效的 IOSurface。导入时对其做 RETAIN 递增引用计数,而不是接管所有权(L736-L746)。
  • LinuxnativePixmap 对象,包含:
    • planes:每个平面(dmabuf fd 一组)的 strideoffsetsizefd;源码对每个 fd 执行 dup() 后再交给 gfx::NativePixmapHandle,避免进程已拥有该 fd 的所有权(L747-L766);
    • modifier:字符串形式的 GBM modifier,传给 EGL 驱动;
    • supportsZeroCopyWebGpuImport:是否支持零拷贝 WebGPU 导入(L688-L697)。

一个关键前提(原文档 shared-texture.md 与设计文档均强调):调用 importSharedTexture 时,句柄必须已经对当前进程可见。Chromium 的 IPC 层会透明处理跨进程句柄复制(这也是 OSR paint 事件能直接用句柄的原因),但如果你把 textureInfo 通过自己的 IPC 传给另一个进程再导入,必须自行保证目标进程能访问该句柄(例如 Windows 下先 DuplicateHandle 到目标进程)。

三、导入流程:从 textureInfo 到 SharedImage

主流程 electron::api::shared_texture::ImportSharedTextureL707-L822)按如下顺序执行:

  1. 解析 textureInfogin::Converter<ImportSharedTextureInfo>::FromV8 一次性取出 pixelFormatcodedSizevisibleRect(缺省补全为整个 codedSize)、colorSpacetimestampid 以及平台句柄;
  2. 构造 gfx::GpuMemoryBufferHandle:按平台把 ntHandle(先 DuplicateHandle)/ioSurface(RETAIN)/dmabuf planes(逐 fd dup)包装起来,见 L716-L767
  3. 格式校验media::VideoPixelFormatToSharedImageFormat(pixel_format) 失败即抛 "Invalid shared texture buffer format";
  4. 创建 SharedImage:取当前进程的 SharedImageInterface(主进程来自 content::ImageTransportFactory,渲染进程来自 blink::SharedGpuContextL75-L88),按平台选择 usage 标志(Windows/macOS 额外启用 WEBGPU_READ/WRITEL787-L798)后调用 sii->CreateSharedImage({format, coded_size, color_space, usage, "SharedTextureVideoFrame"}, gmb_handle)。失败时抛 "Texture format or dimension might not be supported on current device or platform";
  5. 构建 SharedTextureImported 包装对象:保存元数据与 creation_sync_token,返回带 getVideoFrame / release / startTransferSharedTexture / getFrameCreationSyncToken / setReleaseSyncToken 方法的 JS 字典(L502-L553),其结构见 docs/api/structures/shared-texture-imported.md

其中 getVideoFrame() 只在渲染进程可用(主进程调用会抛 "The VideoFrame cannot be created at current process",L395-L399),它内部走 media::VideoFrame::WrapSharedImage(pixel_format, client_shared_image, creation_sync_token, release_callback, visible_rect, coded_size, Microseconds(timestamp))——这正是 pixelFormat/codedSize/visibleRect/timestamp 四个字段最终落到 VideoFrame 上的位置。

四、跨进程传输中的 textureInfo 元数据

textureInfo 本身是可经 IPC 传递的纯数据(句柄除外,需自行保证目标进程可见)。subtle 路径下,startTransferSharedTexture() 会导出 ClientSharedImage 并用 mojo 序列化为 base64 字符串,同时把 pixelFormat(字符串化)、codedSizevisibleRecttimestampsyncToken 一并打进 transfer 对象(L267-L296);目标进程调用 finishTransferSharedTexture 时,这些字段再次通过同一个 Converter 被解析(FinishTransferSharedTextureL824-L875),经 ImportSharedImage 取回同一 Mailbox 的 SharedImage 引用,并以 WaitSyncToken 等待源进程的 creation token 完成,防止资源在目标侧真正取得 GPU 使用权之前被释放。

生命周期保障是这套设计的核心难点,完整论述见 shell/common/api/shared_texture/README.md:两进程引用同一 Mailbox 时,GPU 何时用完由 SyncToken 保证;release() 若发现帧已导入 WebGPU 管线,会经 gpu::ContextSupport::SignalSyncToken 等待 GPU 完成后再触发回调,通知你释放主进程中的源纹理。JS 层未手动 release() 直接 GC 时,弱回调会记录错误日志并代为释放(L362-L380)。

五、端到端示例(来自测试规范)

仓库中 spec/api-shared-texture-spec.ts 给出了可复现的完整流程(以 OSR 为纹理源):

// 1. 开启共享纹理的离屏窗口
const osr = new BrowserWindow({
  width: 128, height: 128,
  webPreferences: { offscreen: { useSharedTexture: true } }
});
osr.webContents.setFrameRate(1);

osr.webContents.on('paint', async (event) => {
  const texture = event.texture;           // OffscreenSharedTexture
  if (!texture) return;                    // GPU 不可用时跳过

  // 2. textureInfo 即 SharedTextureImportTextureInfo
  const imported = sharedTexture.importSharedTexture({
    textureInfo: texture.textureInfo,
    allReferencesReleased: () => texture.release() // GPU 完成后释放源
  });

  // 3. 发送到渲染进程(需先在渲染进程 setSharedTextureReceiver)
  await sharedTexture.sendSharedTexture({
    frame: win.webContents.mainFrame,
    importedSharedTexture: imported
  });

  // 4. 源侧释放引用
  imported.release();
});

渲染进程侧用 sharedTexture.setSharedTextureReceiver(callback) 接收,回调拿到的 receivedSharedTextureData.importedSharedTexture 上调用 getVideoFrame() 即可在 Web 渲染系统(WebGL/WebGPU/<video> 等)中使用。

适用前提与限制需注意:

  • sharedTexture 全系列方法标注 Experimental,可能在未来版本移除;
  • importSharedTexture / sendSharedTexture 仅主进程可用,setSharedTextureReceiver 仅渲染进程可用;
  • 测试用例目前声明"仅在 macOS arm64 上正常运行"(spec/api-shared-texture-spec.ts 的平台判断),且 GPU 不可用时 paint 事件的 texture 为空,示例代码对此做了跳过处理;
  • sendSharedTexture 有 1000ms 超时,调用前必须确保渲染进程已注册接收端且进程存活;
  • 句柄可见性规则(第二节)是跨进程使用时的第一排查点。

六、小结

SharedTextureImportTextureInfo 字段虽少,却精确对应了「Chromium 导入外部 GPU 纹理所需的最小信息集」:pixelFormathandle 决定内存如何被解读,codedSize/visibleRect 决定几何,colorSpace/timestamp 决定呈现与同步语义。理解它与 SharedTextureHandle 的平台差异、SharedTextureImported 的引用计数与 SyncToken 生命周期机制后,你就能在 Electron 中把自研编码器、摄像头管线或 OSR 的 GPU 输出,以零拷贝方式接入 Web 标准渲染路径。

登录后查看全文
热门项目推荐
相关项目推荐