Electron SharedTextureTransfer 对象详解:共享纹理跨进程传输的机制与实战
SharedTextureTransfer 是 Electron sharedTexture 模块中用于在浏览器进程与渲染进程之间传输共享纹理的核心数据结构:它把一个已在某进程导入的 SharedImage 序列化为纯数据对象,使纹理引用可以像普通 IPC 消息一样被传递。读懂这个对象的六个字段及其底层生成/消费流程后,你能够基于 OSR(离屏渲染)产出的纹理句柄,将外部共享纹理导入 Electron、跨进程转移到渲染进程,并用 VideoFrame + WebGPU 完成零拷贝渲染与安全的资源释放。
SharedTextureTransfer 在 sharedTexture API 体系中的位置
sharedTexture 模块负责把平台特定的纹理句柄(Windows 的 NT HANDLE、macOS 的 IOSurfaceRef、Linux 的 NativePixmapHandle)导入 Electron 并转换为标准的 Web VideoFrame,详见 sharedTexture 模块文档。整个跨进程流转涉及四类对象:
| 对象 | 作用 | 文档 |
|---|---|---|
SharedTextureSubtle |
底层细粒度 API:importSharedTexture、finishTransferSharedTexture |
shared-texture-subtle.md |
SharedTextureImportedSubtle |
已导入纹理的句柄对象:getVideoFrame、release、startTransferSharedTexture、getFrameCreationSyncToken、setReleaseSyncToken |
shared-texture-imported-subtle.md |
SharedTextureTransfer |
本文主题:可序列化、可跨进程传输的纹理描述数据 | shared-texture-transfer.md |
SharedTextureSyncToken |
不透明同步令牌数据,用于 GPU 侧的资源生命周期同步 | shared-texture-sync-token.md |
典型链路为:
- 主进程从 OSR
paint事件(offscreen: { useSharedTexture: true })或其他原生渠道获得纹理句柄; - 调用
sharedTexture.subtle.importSharedTexture(textureInfo)得到SharedTextureImportedSubtle; - 调用其
startTransferSharedTexture()得到SharedTextureTransfer; - 通过 IPC(
webContents.send或托管 APIsendSharedTexture)把该对象送入渲染进程; - 渲染进程调用
sharedTexture.subtle.finishTransferSharedTexture(transfer)取回一个新的SharedTextureImportedSubtle,之后用getVideoFrame()参与渲染。
shared-texture-transfer.md 文档的最后一句正点明了第 5 步:“Use sharedTexture.subtle.finishTransferSharedTexture to get SharedTextureImportedSubtle back.”
字段逐项解析:六个 Readonly 属性
SharedTextureTransfer 的全部属性均为 _Readonly_,在 C++ 侧通过 SetReadOnly 绑定(见 electron_api_shared_texture.cc#L284-L293),生成后不可修改:
| 字段 | 类型 | 文档说明 | 源码层面的实际含义 |
|---|---|---|---|
transfer |
string | 共享纹理的不透明传输数据,可跨 Electron 进程传递 | gpu::ClientSharedImage::Export() 得到的 gpu::ExportedSharedImage 经 mojo 序列化器编码后再 Base64 的字符串,承载了指向 GPU 进程中 SharedImageBacking 的 Mailbox 引用 |
syncToken |
string | 帧创建用的不透明同步令牌数据 | 源对象 frame_creation_sync_token 的 Base64 编码(gpu::SyncToken 原始字节流,见 GetBase64StringFromSyncToken),接收端用于 WaitSyncToken,保证目标对象在 GPU 侧真正取得资源之前源对象不会释放它 |
pixelFormat |
string | 正在传输的纹理的像素格式 | 由 TransferVideoPixelFormatToString 映射,见下文取值表 |
codedSize |
Size | 共享纹理的完整尺寸 | gfx::Size coded_size,即帧数据完整维度 |
visibleRect |
Rectangle | [0, 0, codedSize.width(), codedSize.height()] 内的子区域,常见情况下就是全帧 |
gfx::Rect visible_rect;OSR 场景下默认取整个 codedSize(导入转换时若未提供即 gfx::Rect(coded_size),见 Converter::FromV8) |
timestamp |
number | 微秒级时间戳,会被反映到 VideoFrame |
int64_t timestamp(自采集开始起算的微秒数),创建 VideoFrame 时以 base::Microseconds(timestamp) 传入 media::VideoFrame::WrapSharedImage |
pixelFormat 字符串与内部枚举的双向映射定义在 TransferVideoPixelFormatToString 及导入时的 Converter<ImportSharedTextureInfo>::FromV8:
| 字符串 | 内部 media::VideoPixelFormat |
|---|---|
bgra |
PIXEL_FORMAT_ARGB |
rgba |
PIXEL_FORMAT_ABGR |
rgbaf16 |
PIXEL_FORMAT_RGBAF16 |
nv12 |
PIXEL_FORMAT_NV12 |
nv16 |
PIXEL_FORMAT_NV16 |
p010le |
PIXEL_FORMAT_P010LE |
注意一个反直觉细节:ARGB 在传输对象上表现为 "bgra",ABGR 表现为 "rgba"。此外 getVideoFrame() 只能用于渲染进程——源码中浏览器进程调用会直接抛出 “The VideoFrame cannot be created at current process.”(ImportedTextureGetVideoFrame),所以“主进程生成 Transfer、渲染进程消费 Transfer”正是被设计支持的方向。
生成端:startTransferSharedTexture 的序列化实现
SharedTextureTransfer 由 SharedTextureImportedSubtle.startTransferSharedTexture() 创建,C++ 实现是 ImportedSharedTexture::StartTransferSharedTexture:
v8::Local<v8::Value> ImportedSharedTexture::StartTransferSharedTexture(
v8::Isolate* isolate) {
auto exported = client_shared_image->Export();
// Use mojo to serialize the exported shared image.
mojo::Message message(0, 0, MOJO_CREATE_MESSAGE_FLAG_UNLIMITED_SIZE, 0);
mojo::internal::MessageFragment<
gpu::mojom::internal::ExportedSharedImage_Data>
data(message);
data.Allocate();
mojo::internal::Serializer<gpu::mojom::ExportedSharedImageDataView,
gpu::ExportedSharedImage>::Serialize(exported,
data);
auto encoded = base::Base64Encode(...message payload...);
gin_helper::Dictionary root(isolate, v8::Object::New(isolate));
root.SetReadOnly("transfer", encoded);
root.SetReadOnly("syncToken", GetBase64StringFromSyncToken(frame_creation_sync_token));
root.SetReadOnly("pixelFormat", TransferVideoPixelFormatToString(pixel_format));
root.SetReadOnly("codedSize", coded_size);
root.SetReadOnly("visibleRect", visible_rect);
root.SetReadOnly("timestamp", timestamp);
return gin::ConvertToV8(isolate, root);
}
从实现可以看出三点关键事实:
- 传输的是 GPU 进程的引用,而不是像素数据。
ClientSharedImage::Export()导出的是ExportedSharedImage,其核心是Mailbox——指向 GPU 进程中同一块SharedImageBacking的引用。这与 shared_texture 设计文档 的设计说明一致:正是SharedImage对Mailbox的持有使得跨进程共享成为可能(该设计文档注明基于 Electron 37 / Chromium 137 编写,且提示 Chromium 的SharedImage接口尤其是GpuMemoryBuffer、VideoFrame生命周期管理部分可能快速演进)。 transfer是 Base64 字符串,因此可被任意 IPC 机制透传。 mojo 消息体被编码为字符串后,就能走webContents.send、ipcRenderer.invoke等结构化克隆通道,这正是“can be transferred across Electron processes”的由来。- 调用前提: 对象未被
release(),且当前进程 GPU 可用,否则分别抛出 “The shared texture has been released.” 或 “Failed to start shared texture transfer: GPU is not available”(ImportedTextureStartTransferSharedTexture)。
消费端:finishTransferSharedTexture 的反序列化与同步保证
接收侧的 C++ 实现是 FinishTransferSharedTexture,与生成端严格对称:
v8::Local<v8::Value> FinishTransferSharedTexture(v8::Isolate* isolate,
v8::Local<v8::Value>& options) {
// ... 从 options 中取出 id / transfer / syncToken 三个字符串 ...
auto transfer_data = base::Base64Decode(transfer);
// Use mojo to deserialize the exported shared image.
mojo::Message message(transfer_data.value(), {});
// ... Claim + Deserialize 出 gpu::ExportedSharedImage ...
auto* sii = GetSharedImageInterface(); // 无 GPU 时抛错
auto si = sii->ImportSharedImage(std::move(exported));
auto source_st = GetSyncTokenFromBase64String(sync_token_data);
sii->WaitSyncToken(source_st); // 等待源对象的帧创建同步令牌
ImportedSharedTexture* imported = new ImportedSharedTexture();
imported->pixel_format = partial.pixel_format;
imported->codedSize / visibleRect / timestamp 从传输对象恢复;
imported->frame_creation_sync_token = sii->GenUnverifiedSyncToken();
imported->client_shared_image = std::move(si);
imported->id = id;
return CreateImportedSharedTextureFromSharedImage(isolate, imported);
}
其中 WaitSyncToken(source_st) 是跨进程生命周期的关键一环:目标对象在 GPU 侧真正“取得”纹理引用之前,源对象侧通过 gpu::SharedImageInterface 的同步令牌机制不会销毁底层资源。源码注释(electron_api_shared_texture.h 对应实现区)明确了两种生命周期管理方式:
- 回调方式(默认):对目标对象调用
release(callback),GPU 命令缓冲用完后回调触发,再由用户去释放源对象——简单直观; - 同步令牌方式(进阶):目标对象拿到
getFrameCreationSyncToken()后,传给调用过startTransferSharedTexture的源对象并执行setReleaseSyncToken,此后源对象可直接release(),无需关心目标进程是否完成异步获取。
finishTransferSharedTexture 接收的参数就是整个 SharedTextureTransfer 对象(其字段名 transfer、syncToken 被逐一读取)。绑定入口在 Initialize:importSharedTexture 与 finishTransferSharedTexture 都注册在 Node 绑定 electron_common_shared_texture(node_bindings.cc#L113)下,对应 JS 侧的 sharedTexture.subtle。
实战一:subtle 手动模式(主进程导入 → IPC 传 Transfer → 渲染进程消费)
以下流程改编自仓库内测试 spec/api-shared-texture-spec.ts 中 “successfully imported and rendered with subtle api” 用例(该用例从 OSR 窗口捕获 paint 事件,最终用 capturePage 与目标图像做像素级比对验证整条链路):
主进程:
const { BrowserWindow, sharedTexture, ipcMain } = require('electron');
const capturedTextures = new Map(); // id -> { importedSubtle, texture }
const win = new BrowserWindow({
width: 256, height: 256,
webPreferences: { preload: preloadPath }
});
// OSR 窗口:useSharedTexture 让 paint 事件直接携带纹理句柄
const osr = new BrowserWindow({
width: 128, height: 128,
webPreferences: { offscreen: { useSharedTexture: true } }
});
osr.webContents.setFrameRate(1);
osr.webContents.on('paint', (event) => {
const texture = event.texture;
if (!texture) return; // GPU 可能不可用
// Step 2: 把 OSR 纹理句柄导入为 SharedTextureImportedSubtle
const importedSubtle = sharedTexture.subtle.importSharedTexture(texture.textureInfo);
// Step 3: 生成可跨进程传递的 SharedTextureTransfer
const transfer = importedSubtle.startTransferSharedTexture();
const id = randomUUID();
capturedTextures.set(id, { importedSubtle, texture });
// Step 4: 通过 IPC 把 transfer 对象(纯数据)发给渲染进程
win.webContents.send('shared-texture', id, transfer);
});
ipcMain.on('shared-texture-done', (event, id) => {
// Step 12-14: 渲染进程释放完成后,主进程再释放导入对象与源纹理
const { importedSubtle, texture } = capturedTextures.get(id);
capturedTextures.delete(id);
importedSubtle.release(() => texture.release());
});
渲染进程 preload(参考 spec/fixtures/api/shared-texture/subtle/preload.js):
const { sharedTexture } = require('electron');
const { ipcRenderer, contextBridge } = require('electron/renderer');
contextBridge.exposeInMainWorld('textures', {
onSharedTexture: (cb) => {
ipcRenderer.on('shared-texture', async (e, id, transfer) => {
// Step 5: 用 SharedTextureTransfer 完成传输,取回 SharedTextureImportedSubtle
const importedSubtle = sharedTexture.subtle.finishTransferSharedTexture(transfer);
// Step 6: 交给 WebGPU 渲染(getVideoFrame() 只能在渲染进程调用)
await cb(id, importedSubtle);
// Step 10: 用 release 的回调精确等待 GPU 命令缓冲结束
importedSubtle.release(() => {
ipcRenderer.send('shared-texture-done', id);
});
});
}
});
几个值得注意的约束(均有仓库依据):
- 纹理句柄必须对调用进程可见。设计文档明确:Windows 上必须是
CreateSharedHandle产生的 NT HANDLE(非 NT 的旧式 HANDLE 会被 Chromium 的CloseHandle清理逻辑破坏导致崩溃),macOS 上IOSurface需通过mach_port跨进程传递;因此调用importSharedTexture前,句柄须已可用(ImportSharedTextureInfo 的平台注释)。 release()是手动义务:若对象被 GC 而未调用release(),弱引用回调会打出 “The imported shared texture ... was garbage collected before callingrelease(). You have to manually release the resource once you're done with it.” 的 ERROR 日志(PersistentCallbackPass1)。- 如果一次传输产生了多个
SharedTextureImported对象(比如同一纹理转发给多个进程),每一个都必须release,GPU 侧资源在最后一个被释放时才销毁(SharedTextureImportedSubtle 文档)。
实战二:managed 托管模式(sendSharedTexture + setSharedTextureReceiver)
sharedTexture 模块还封装了托管流程,免去手动 IPC:
- 渲染进程先注册
sharedTexture.setSharedTextureReceiver(callback); - 主进程调用
sharedTexture.sendSharedTexture({ frame: webContents.mainFrame, importedSharedTexture }, ...args),该方法有 1000ms 超时,要求接收端已就绪、渲染进程存活(sharedTexture 模块文档)。
渲染进程侧的实现见 lib/renderer/api/shared-texture.ts:
ipcRendererInternal.on(transferChannelName, async (event, requestId, ...args) => {
const replyChannel = `${transferChannelName}_RESPONSE_${requestId}`;
const transfer = args[0] as Electron.SharedTextureTransfer;
const textureId = args[1] as string;
// 1) 在渲染进程完成 finishTransferSharedTexture
const imported = sharedTextureNative.finishTransferSharedTexture(Object.assign(transfer, { id: textureId }));
// 2) 把 getFrameCreationSyncToken() 回传给主进程,
// 供源对象 setReleaseSyncToken 使用(对应上文“同步令牌方式”)
event.sender.send(replyChannel, null, imported.getFrameCreationSyncToken());
// 3) 组装 SharedTextureImported 包装对象并回调用户
await sharedTextureReceiverCallback?.(data, ...args.slice(2));
});
对应测试 spec/fixtures/api/shared-texture/managed/preload.js 中,release() 无需回调——注释说明“util function automatically managed the lifetime via sync token”,即托管模式已经替你走完了 setReleaseSyncToken 路径。渲染端用法(managed/renderer.js)非常简洁:
window.textures.onSharedTexture(async (imported) => {
const frame = imported.getVideoFrame(); // Step 6: 取 VideoFrame
await window.renderFrame(frame); // Step 7: WebGPU 渲染
frame.close(); // Step 8: 用完即关
imported.release();
});
设计原理小结:为什么 Transfer 是字符串而非句柄直传
shared_texture 设计文档 解释了这条链路背后的四个工程障碍,其中两个直接决定了 SharedTextureTransfer 的形态:
- 共享句柄是进程局部的:Windows NT HANDLE 需要
DuplicateHandle才能跨进程,macOS 的IOSurface需要经mach_port传递。SharedTextureTransfer绕开了原生句柄传递,因为transfer字段里装的是 GPU 进程侧SharedImageBacking的引用序列化数据——任何拿到它的进程都通过SharedImageInterface::ImportSharedImage在 GPU 进程中“认领”同一份 backing,无需操作平台句柄。 - GPU 使用何时结束无法同步得知:两个进程各持有一份引用同一
Mailbox的SharedImage,谁都不能先释放。这就是syncToken字段与release回调 /setReleaseSyncToken机制存在的意义——底层通过gpu::ContextSupport::SignalSyncToken在 GPU 命令缓冲真正完成后触发 JS 回调(SetupReleaseSyncTokenCallback)。
适用前提与限制
- 实验性 API:
sharedTexture模块全部方法在文档中标记 Experimental,未来可能被移除(shared-texture.md)。 - GPU 必须可用:
startTransferSharedTexture/finishTransferSharedTexture/getFrameCreationSyncToken/setReleaseSyncToken在无 GPU 上下文时都会抛出 “GPU is not available” 错误;测试用例也因此在捕获不到纹理时跳过比对。 - 测试平台基线:当前仓库的端到端测试 api-shared-texture-spec.ts 仅在 macOS arm64 上运行(
process.platform !== 'darwin' || process.arch !== 'arm64'时整体ifdescribe跳过);设计文档亦声明其基于 Electron 37 / Chromium 137 编写,且 ChromiumSharedImage相关接口演进较快,其他平台的细节以实际仓库源码为准。 - 进程分工:
importSharedTexture与startTransferSharedTexture可在主进程调用;getVideoFrame()只能在渲染进程调用。若要导入渲染进程侧加载的原生代码产出的纹理,需自行保证句柄对该进程可见(设计文档 “Example” 一节)。
相关仓库资料索引
- 文档:SharedTextureTransfer、SharedTextureSubtle、SharedTextureImportedSubtle、SharedTextureSyncToken、sharedTexture 模块
- 设计与实现:shared_texture 设计文档、electron_api_shared_texture.cc、electron_api_shared_texture.h、lib/renderer/api/shared-texture.ts
- 测试与夹具:api-shared-texture-spec.ts、subtle/preload.js、managed/preload.js、managed/renderer.js
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 StartedRust0623
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