Skip to content

About

Wallpaper Engine 场景壁纸(scene.pkg)离线渲染成一张 PNG 的独立实现:可当库或 CLI 用,不依赖任何宿主。Standalone offline static-frame renderer for Wallpaper Engine scene wallpapers (library + CLI).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

43 Commits

Folders and files

Repository files navigation

we-static-frame

license node CI Wallpaper Engine

把 Wallpaper Engine 的场景壁纸(scene.pkg / 场景目录)离线渲染成一张 PNG 的独立实现。 不依赖任何宿主(无桌面端、无插件、无 HTTP 服务):既能当库调用,也能当命令行工具用。

为什么存在:实时渲染(webwallgl)在有 GPU 的机器上是主路径; 静态帧的定位是虚拟机 / 远程端 / 无 WebGL2 / 需要批量出图时的取舍方案 —— 也就是"没有实时渲染可用"时的兜底。 它独立发版、独立评测,边界与取舍由本仓库自己决定。

出处(历史):本仓库初始从 dsh-wallpaper-engine 的静态帧链抽出(fork 点 f716df0,MIT),此后在这里独立演进,不再与上游互相同步 —— 本仓库是该实现的发布源,缺陷与需求提到本仓库 Issues。 上游仓库是否仍包含一份静态帧渲染的副本,以其自身说明为准。

依赖

  • Node ≥ 18(ESM)。
  • Wallpaper Engine 安装:渲染器需要官方 assets/(shader / material / model / particle / scripts)才能还原效果链。 用 --we-assets <WE 安装目录>/assets 指定,或让 CLI 自动定位(环境变量 WE_ASSETS / DSH_WE_ASSETS、 DSH_WE_STEAM_ROOT,以及各平台 Steam 默认库 + libraryfolders.vdf:Windows 盘符、 Linux ~/.steam / ~/.local/share/Steam / Flatpak / snap、macOS ~/Library/Application Support/Steam)。 这些资产是 WE 自带的,不随本仓库分发。
  • 可选:supreium-headless-gl(x64)—— 效果链走 WebGL 的 GPU 加速(--gpu)。缺失、架构不符或驱动异常即自动回退 CPU。
  • 可选:场景内嵌视频纹理需要调用方先抽帧(例如用 ffmpeg),通过库参数 videoFrames 传入;CLI 暂不支持, 这类场景会渲染成空白帧(见下方"已知限制")。

用法

# 命令行
npx we-sf render "/path/to/scene.pkg" -o frame.png --w 3840 --h 2160 --t 2.5
npx we-sf render ./my-scene-dir -o frame.png --we-assets ".../wallpaper_engine/assets"
npx we-sf locate          # 只打印自动定位到的 assets 路径
npx we-sf render <scene> -o out.png --log      # 把效果编译失败 / 缺纹理 / 降级上报打到 stderr
npx we-sf render <scene> -o out.png --strict   # 有降级或空白帧时退出码 3(批量出图建议开)

降级与空白帧默认就会报到 stderr(不再静默):--json 里另有结构化的 degraded: [{object, feature, action}] 与 blank / meanLuma 字段。 退出码:0 干净出图 / 1 渲染失败 / 2 参数错误 / 3 出图但有降级或空白(仅 --strict)。

// 库
import { renderFrame, renderToFile, locateWeAssets } from 'we-static-frame';

const { png, width, height, ms, degraded, blank } = await renderFrame({
  input: 'C:/.../431960/3486806915/scene.pkg',
  width: 3840, height: 2160, time: 2.5,
  weAssetsDir: locateWeAssets(),        // <WE>/assets 或 <WE> 本身都接受
  gpuAccel: false,
  onDegraded: ({ object, feature, action }) => console.warn(feature, action), // 可选:实时回调
});
if (blank || degraded.length) console.warn('画面与官方不一致', degraded);

目录结构

src/**                  ← 渲染器本体(效果链 / GLSL 运行时 / GPU 适配 / 场景脚本),可在此直接修改
src/render.js           ← 库入口(renderFrame / renderToFile / locateWeAssets)
src/cli.js              ← CLI(we-sf)
test/smoke.mjs          ← 冒烟:能拿到场景就渲染一张并校验 PNG 头(拿不到则 SKIP)
test/survey.mjs         ← 普查:本机全库逐个渲染,汇总空白帧 / 降级 / 耗时(人读表格 + JSON)
test/gpu-parity.mjs     ← CPU/GPU 对拍:`gpu:'auto'` + 全部效果 `gpu-only`,
                          用 decisions[] 测出「哪些效果 GPU 能跑、哪些跑不了及原因」

src/** 的约定(单一实现):这是渲染器的唯一实现与发布源,没有需要对齐的上游副本 —— 直接在这里修 bug、加能力、独立发版即可。两条约束仍然保留:

  1. 不反向依赖任何宿主:src/** 里不允许出现 dsh-wallpaper-engine(或其它宿主)专有模块的 import —— 渲染器只依赖 Node 内置模块与 package.json 里声明的依赖。
  2. 改动落点尽量小而集中,并在提交信息里写清"修的是什么、怎么验证"(本仓库既有惯例)。

出处只作历史记录保留:src/** 的初始版本抽自 dsh-wallpaper-engine 的静态帧链(fork 点 f716df0)。 那条上游同步链路(scripts/sync-webwallgl.mjs / .upstream.json)不再对本仓库生效: src/** 以本仓库为准,不要用上游的副本覆盖它。

下游决策接口(逐效果 / GPU)

渲染器把决策权交给调用方:哪些效果应用、用哪条后端、GPU 开不开,都能逐条指定, 并且每条决策都有可解释的记录(谁被跳过、依据是什么)。

const { png, decisions, gpuStats } = await renderFrame({
  input: 'scene.pkg', weAssetsDir, time: 2.5,
  // GPU:'auto'(默认)| 'off'(永不探测)| 'force'(重探,含已熔断的进程)
  //      也可以是对象:{ mode, failStreakLimit, allowEffects, denyEffects }
  //      failStreakLimit: 0 = 永不熔断("宁可慢也要 GPU")
  gpu: { mode: 'auto', failStreakLimit: 0, denyEffects: ['godrays'] },
  effects: {
    deny: ['filmgrain'],                     // 黑名单:跳过(对象保留)
    allow: ['waterwaves', 'bloom'],          // 白名单(给定时 = 只允许这些)
    backend: { waterwaves: 'cpu', godrays: 'gpu-only' },
    //   'cpu'      → 该效果禁用 GPU,只走 CPU 内核/解释器
    //   'gpu'      → 优先 GPU(失败仍回退 CPU)
    //   'gpu-only' → GPU 未产出就**跳过**该效果(不静默回退,便于 CPU/GPU 严格对拍)
    skipDegenerate: true,                    // 是否启用"输出退化就丢弃"的保护
    onDecision: (d) => log(d),               // 实时回调,也可从返回值 decisions 里拿
  },
  // 自由度最高的一层:钩子优先级高于名单与 backend 表
  policy: {
    decideEffect: ({ effect, layer, index }) =>
      effect === 'bloom' && layer === '天空' ? { action: 'skip', reason: '业务规则' } : 'apply',
    decideBackend: ({ effect }) => (effect.startsWith('water') ? 'cpu' : 'gpu'),
  },
  // 逐着色器源码覆写(借鉴 webwallgl 的 __shaderPatch,但这里是官方接口)
  shaderPatch: { waterwaves: (src, { stage }) => src.replace('0.05', '0.02') },
});

console.log(decisions.byAction, decisions.byBackend);
// decisions.items[] = { effect, layer, index, action, backend, reason, source }
//   source ∈ hook | hook-error | backend-map | backend-hook | deny | allow | default
console.log(gpuStats); // { state: 'unknown'|'ok'|'off', failStreak, used, failed, fallback, unavailable }

CLI 对应开关:

we-sf render scene.pkg -o out.png --gpu off            # 或 --gpu auto / --gpu force
we-sf render scene.pkg -o out.png --skip-effect bloom  # 可重复;--no-effect 同义
we-sf render scene.pkg -o out.png --only-effect waterwaves   # 白名单(可重复)
we-sf render scene.pkg -o out.png --effect-backend waterwaves:cpu
we-sf render scene.pkg -o out.png --list-decisions     # 把决策记录打到 stderr
npm run gpu-parity -- --only 3461168300 --out parity.json   # CPU/GPU 逐效果能力边界

GPU 能力边界(实测):GPU 适配层目前只覆盖按名字猜到的单 pass GLSL 效果; effect.json 数据通路的多 pass 效果(如 bloom、blurprecise、bokeh_blur)不会被 GPU 接手。 用 gpu-only 可以把这个边界显式化:拿不到 GPU 产出的效果会被跳过并记录原因, 而不是静默回退 CPU —— 下游据此决定"这个效果是只走 CPU、还是干脆不要"。

设计约定(与实时渲染路线 webwallgl 的对比):

  • webwallgl 对宿主只暴露 quality.{antiAliasing,particles,postProcessing} 三个粗档位 (postProcessing:"off" 是"图层效果链 + 整屏后期 + bloom"三合一总闸),逐效果开关只有 场景数据里的 effect.visible,着色器编译失败则静默跳过。本仓库提供的是逐效果、 可解释、可回放的决策接口。
  • 任何非法配置都不抛错:逐键回落到安全默认(渲染是长任务,不该因配置崩)。
  • 未提供策略时零开销:policy 为空时决策点直接短路,行为与不带该功能时逐位一致。
  • 钩子抛错不牵连渲染:按默认放行并记 source: 'hook-error'。
  • shaderPatch 抛错或返回非字符串 → 保持原样。

已知限制(会静默影响画面,务必先读)

这些都会让"渲染成功"的图与官方不一致。现在都会上报:CLI 打到 stderr 且进 --json 的 degraded / blank,库调用方从 renderFrame() 的返回值或 onDegraded 拿到同样的结构化条目;需要硬性拦截就用 --strict(退出码 3)或自行判断 blank || degraded.length。

  • 内嵌视频纹理 → 整层被跳过。若主图层就是视频纹理,整帧会是纯黑(blank: true)。 库调用方需自行抽帧并传 videoFrames;CLI 不支持,--strict 下会以 3 退出。
  • 效果编译/执行失败(GLSL 报错、缺纹理、缺 combo 支持等)→ 该效果被丢弃,对象保留, 画面缺效果(degraded 里 feature 形如 effect:<名字>)。
  • --gpu 的熔断:同一进程内连续若干次 GPU 效果失败后,本次渲染剩余效果链全部回退 CPU, 此时 --gpu 可能反而更慢(实测有 0.57×–1.0× 的场景)。

明确不做(边界)

本仓库只负责"把场景渲染成一帧"这一层,宿主关注点一律不做:

  • 不做缓存 / 变体档位 / .fb. 兜底 / 预热调度 / 设置与 UI —— 这些属于调用方(谁长期持有进程、谁决定复用哪一帧)。
  • 不做进程派发与 IPC(worker / fork / GPU node 探测)—— 同上。 (src/scene-render-worker.mjs、src/scene-prewarm.js 是移植时一并带过来的宿主侧遗留文件, 本仓库不调用它们;保留只为与出处版本 diff 时不丢上下文,不要在此基础上继续扩展。)
  • 不做"用 WebWallGL 实时渲染器截帧"(那是另一条路线:实时渲染 + 缓存回填,与本仓库的离线单帧不重叠)。

现状与待办

  • 采样时刻选点("t=2.5 可能是空白帧 ⇒ 试更晚时刻")与空白帧度量口径本仓库尚未提供: src/scene-render-worker.mjs 里有一份宿主侧实现,但本仓库不调用它;将来若确认它属于"渲染" 而非"宿主策略",应当在这里实现一份,而不是让调用方各写一份。
  • --gpu 的实际收益只在效果链上,且取决于场景:效果占比高的场景实测 1.8×–4.4×, 效果少或触发熔断的场景会持平甚至变慢 —— 非效果段仍是 CPU。
  • 已知残余降级(本地 16 场景实测,2026-02;都已上报,不会静默):
    • 内嵌视频纹理场景 2/16 输出空白帧(设计限制,见上)。
    • auto_sway:main is not defined —— shader 用 #if AA_VERSION == 1/2/3 分出三份 main, 而 AA_VERSION/NODE_COUNT 未在 shader/材质/effect.json 里给出默认值 ⇒ 三个分支全被裁掉。 需要在 combo 注入侧补"场景材质实例 → pass combos"的传递。
    • lens_flare_sun:x.map is not a function —— 进入其 noise() 后返回了非数值;待最小复现。
    • bokeh_blur / bloom:Cannot read properties of undefined (reading '0');待最小复现。
    • 3582367840 的某个效果:预处理器在 #endif 报 Expected control line(#if 结构不被 shaderfrog 预处理器接受)。 用 --log 或 DSH_WE_FX_TRACE=1(会打印生成的 JS 出错行)可定位。 逐 pass 取证用 DSH_WE_FX_DUMP=1(打印每个 pass 产出的尺寸与像素摘要 —— 找"哪一个 pass 先变成常量");配合 effects: { skipDegenerate: false } 保留退化输出。
  • 更多缺陷与修复进度见 Issues。

许可证

MIT。渲染器实现的初始版本取自 dsh-wallpaper-engine(同为 MIT)。

About

Wallpaper Engine 场景壁纸(scene.pkg)离线渲染成一张 PNG 的独立实现:可当库或 CLI 用,不依赖任何宿主。Standalone offline static-frame renderer for Wallpaper Engine scene wallpapers (library + CLI).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages