Electron SharedTextureImportTextureInfo:共享纹理导入对象详解与源码级原理剖析
本文围绕 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.md 与 docs/api/web-contents.md 的 paint 事件说明)。
三条 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::VideoPixelFormat 与 SharedImage 的格式映射。官方文档列出的取值为:
| 取值 | 含义 |
|---|---|
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 object(L707-L714)。反向的枚举→字符串转换函数 TransferVideoPixelFormatToString 见 L136-L153,它在跨进程传输(startTransferSharedTexture)时把格式序列进 transfer 对象。
2.2 colorSpace:可选颜色空间
colorSpace 可选,类型为 ColorSpace。源码中该字段缺省值为 sRGB——ImportSharedTextureInfo::color_space = gfx::ColorSpace::CreateSRGB()(L582),解析在 L640。创建 SharedImage 时 color_space 会随格式一起传入 sii->CreateSharedImage(...)(L799-L802);之后由 SharedImage 承载,getVideoFrame() 生成的 VideoFrame 再通过 raw_frame->set_color_space(si->color_space()) 携带到 Web 层(L259)。因此做 HDR(如 p010le)纹理导入时应显式传入对应颜色空间。
2.3 codedSize 与 visibleRect:完整尺寸 vs 可见区域
codedSize(Size)必填:共享纹理的完整编码尺寸,对应源码gfx::Size coded_size;visibleRect(Rectangle)可选:[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 上(L257 的 base::Microseconds(ist->timestamp))。内部注释称其为 "the capture timestamp, microseconds since capture start"(L584-L585)。对实时视频管线而言,这个值可用于与音频时钟对齐、做帧调度判断。
2.5 handle:平台相关的 SharedTextureHandle
handle 必填,类型为 SharedTextureHandle,按平台提供互斥的字段:
- Windows:
ntHandle(Buffer)。必须是 NT HANDLE(由CreateSharedHandle产生),且必须是已经复制到当前进程的句柄;文档特别指出rgba/bgra/rgbaf16格式的纹理句柄不带 keyed mutex,而nv12格式带 keyed mutex。源码侧从 Buffer 中读取 8 字节指针值(GetNativeHandle,L650-L660),导入时先DuplicateHandle出一个可托管副本,再包装成gfx::DXGIHandle(L717-L735)——这正是设计文档强调的「非 NT HANDLE 是进程本地的,Chromium 销毁GpuMemoryBuffer时会CloseHandle,若传入旧式全局句柄会导致崩溃」(见 shell/common/api/shared_texture/README.md 第 2 节)。 - macOS:
ioSurface(Buffer),持有IOSurfaceRef指针,必须是当前进程中有效的 IOSurface。导入时对其做RETAIN递增引用计数,而不是接管所有权(L736-L746)。 - Linux:
nativePixmap对象,包含:
一个关键前提(原文档 shared-texture.md 与设计文档均强调):调用 importSharedTexture 时,句柄必须已经对当前进程可见。Chromium 的 IPC 层会透明处理跨进程句柄复制(这也是 OSR paint 事件能直接用句柄的原因),但如果你把 textureInfo 通过自己的 IPC 传给另一个进程再导入,必须自行保证目标进程能访问该句柄(例如 Windows 下先 DuplicateHandle 到目标进程)。
三、导入流程:从 textureInfo 到 SharedImage
主流程 electron::api::shared_texture::ImportSharedTexture(L707-L822)按如下顺序执行:
- 解析
textureInfo:gin::Converter<ImportSharedTextureInfo>::FromV8一次性取出pixelFormat、codedSize、visibleRect(缺省补全为整个 codedSize)、colorSpace、timestamp、id以及平台句柄; - 构造
gfx::GpuMemoryBufferHandle:按平台把ntHandle(先DuplicateHandle)/ioSurface(RETAIN)/dmabuf planes(逐 fddup)包装起来,见 L716-L767; - 格式校验:
media::VideoPixelFormatToSharedImageFormat(pixel_format)失败即抛 "Invalid shared texture buffer format"; - 创建 SharedImage:取当前进程的
SharedImageInterface(主进程来自content::ImageTransportFactory,渲染进程来自blink::SharedGpuContext,L75-L88),按平台选择 usage 标志(Windows/macOS 额外启用WEBGPU_READ/WRITE,L787-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"; - 构建
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(字符串化)、codedSize、visibleRect、timestamp、syncToken 一并打进 transfer 对象(L267-L296);目标进程调用 finishTransferSharedTexture 时,这些字段再次通过同一个 Converter 被解析(FinishTransferSharedTexture,L824-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 纹理所需的最小信息集」:pixelFormat 与 handle 决定内存如何被解读,codedSize/visibleRect 决定几何,colorSpace/timestamp 决定呈现与同步语义。理解它与 SharedTextureHandle 的平台差异、SharedTextureImported 的引用计数与 SyncToken 生命周期机制后,你就能在 Electron 中把自研编码器、摄像头管线或 OSR 的 GPU 输出,以零拷贝方式接入 Web 标准渲染路径。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00