webwallgl 1.3.5 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +26 -84
- package/README.md +26 -84
- package/package.json +1 -1
- package/types.d.ts +132 -5
- package/webwallgl.d.ts +58 -2
- package/webwallgl.global.js +40500 -10454
- package/webwallgl.global.js.map +1 -1
- package/webwallgl.global.min.js +707 -221
- package/webwallgl.global.min.js.map +1 -1
- package/webwallgl.min.mjs +707 -221
- package/webwallgl.min.mjs.map +1 -1
- package/webwallgl.mjs +40499 -10454
- package/webwallgl.mjs.map +1 -1
package/README.en.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## Introduction
|
|
6
6
|
|
|
7
|
-
WebWallGL is a browser-side renderer for Wallpaper Engine wallpapers — scene, video, and web: its main job is replaying workshop scene packages (scene.pkg) in WebGL in real time, with layer effect chains, particles, 3D puppet bones, text widgets, script sandboxes, audio response and live user-property updates; web wallpapers run in a sandboxed iframe with a WE API shim injected before author scripts. Upcoming versions will add effects exclusive to this library — stay tuned.
|
|
7
|
+
Make Wallpaper Engine great again!!! Congratulations — you've found a treasure. This is the most faithful, most feature-complete, and fastest-updated self-built Wallpaper Engine core on the web, implemented in pure TypeScript/JS. WebWallGL is a browser-side renderer for Wallpaper Engine wallpapers — scene, video, and web: its main job is replaying workshop scene packages (scene.pkg) in WebGL in real time, with layer effect chains, particles, 3D puppet bones, text widgets, script sandboxes, audio response and live user-property updates; web wallpapers run in a sandboxed iframe with a WE API shim injected before author scripts. Upcoming versions will add effects exclusive to this library — stay tuned. If your project uses this library, please give it a star — many thanks!
|
|
8
8
|
|
|
9
9
|
- [GitHub repository](https://github.com/oneincase/webwallgl)
|
|
10
10
|
- [Live demo (GitHub Pages)](https://oneincase.github.io/webwallgl/)
|
|
11
11
|
|
|
12
|
-
- Zero runtime dependencies, single-file delivery (min ESM ~
|
|
12
|
+
- Zero runtime dependencies, single-file delivery (min ESM ~930KB / gzip ~295KB)
|
|
13
13
|
- Installable via npm or a <script> CDN tag; multiple isolated instances per page
|
|
14
14
|
- This bench is the library's first consumer — everything you see here is public API
|
|
15
15
|
|
|
@@ -36,12 +36,12 @@ import { mount, httpSource } from "webwallgl";
|
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
// 2) ESM CDN via jsDelivr (without a bundler)
|
|
39
|
-
import { mount, httpSource } from "https://cdn.jsdelivr.net/npm/webwallgl@1.
|
|
39
|
+
import { mount, httpSource } from "https://cdn.jsdelivr.net/npm/webwallgl@1.4.1/webwallgl.min.mjs";
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
```
|
|
43
43
|
<!-- 3) UMD <script>: exposes the global WebWallGL -->
|
|
44
|
-
<script src="https://cdn.jsdelivr.net/npm/webwallgl@1.
|
|
44
|
+
<script src="https://cdn.jsdelivr.net/npm/webwallgl@1.4.1/webwallgl.global.min.js"></script>
|
|
45
45
|
<script>
|
|
46
46
|
const { mount, httpSource } = WebWallGL;
|
|
47
47
|
</script>
|
|
@@ -123,15 +123,24 @@ input.addEventListener("change", () => {
|
|
|
123
123
|
| --- | --- | --- |
|
|
124
124
|
| `source (required)` | `—` | See Source above |
|
|
125
125
|
| `fit` | `"cover"` | cover crops to fill / contain letterboxes / stretch distorts |
|
|
126
|
-
| `renderDpr` | `
|
|
126
|
+
| `renderDpr` | `0 (auto)` | Render DPR: 0 = follow devicePixelRatio (native-sharp on Retina/HiDPI); a positive number = target DPR and may exceed the reported device value (supersamples to physical resolution even when a host WKWebView misreports 1). Physical long edge capped at 4096; lower it to save VRAM. The tier also drives the texture resource scale — at lower tiers textures resolve to smaller sizes per layer footprint (see "VRAM & clarity" below) |
|
|
127
127
|
| `fps` | `60` | FPS cap; skipped frames don't count into stats.fps, so drops are visible |
|
|
128
128
|
| `volume` | `0` | 0..1. Starts muted (autoplay policy); set a non-zero volume after ready |
|
|
129
129
|
| `autoplay` | `true` | When false, stays paused after mount |
|
|
130
130
|
| `properties` | `{}` | Initial user property overrides (keys are property names) |
|
|
131
131
|
| `pointer / audio / media` | `built-in` | Pointer follows the canvas; audio/media are deterministic sims; pass null to disable |
|
|
132
132
|
| `features` | `all on` | Debug switches: models / text / particles / effects / components |
|
|
133
|
+
| `quality` | `all defaults` | Render quality tiers (like the WE client's performance options): antiAliasing "off" (default)/"fxaa"/"msaa2"/"msaa4", particles "high" (default)/"medium"/"low"/"off" (low/medium scale both the count cap and emission rate), postProcessing "high" (default)/"medium"/"low"/"off" (low/medium cap effect-chain FBO resolution; off bypasses effect chains, fullscreen post layers and Bloom). Supersampling lives in renderDpr |
|
|
133
134
|
| `onReady / onError / onDiagnostic` | `—` | Callback surface; can also subscribe later via instance.on() |
|
|
134
135
|
|
|
136
|
+
VRAM & clarity: textures plus canvas/effect-chain buffers dominate VRAM, and both scale automatically with the mount options — no manual management required:
|
|
137
|
+
|
|
138
|
+
- Textures auto-pick their resolution from "clarity × the layer's on-screen footprint": as long as texture resolution ≥ the layer's device-pixel footprint, output is pixel-identical to full-resolution assets — only oversampling is removed. Mip-chained textures just truncate the chain (zero resampling); single-image giants get resampled
|
|
139
|
+
- Compressed textures (DXT1/3/5, BC7, ETC2) upload their embedded mip chains straight to the GPU instead of decoding to RGBA; single-channel R8 uploads as GL_R8. Unsupported browsers fall back to the decode path with identical visuals
|
|
140
|
+
- CPU-side decoded copies are freed right after upload (except multi-frame animated .tex) — up to hundreds of MB on the worst wallpapers
|
|
141
|
+
- Whitelisted assets never shrink: LUT data grids, multi-frame animations and video textures stay native; normal/mask maps have their own floor. window.__memStats() after mount reports per-bucket usage (package/decoded/uploaded)
|
|
142
|
+
- Recommended combo for low-memory devices: clarity 0.8 + quality low (particles/post low, anti-aliasing off). On 4K screens the canvas and effect-chain FBOs dominate — they grow with DPR squared and MSAA4 multiplies by 4 — textures come second
|
|
143
|
+
|
|
135
144
|
## The SceneInstance API
|
|
136
145
|
|
|
137
146
|
| Member | Description |
|
|
@@ -139,13 +148,15 @@ input.addEventListener("change", () => {
|
|
|
139
148
|
| `pause() / resume() / paused` | Pause/resume. Never remounts the package: video/audio resume from where they were |
|
|
140
149
|
| `setFit(fit) / setFps(n) / setVolume(v)` | Live updates, no remount (the render loop reads them per frame) |
|
|
141
150
|
| `setRenderDpr(dpr)` | Changing DPR rebuilds the canvas; remounts internally (pkg cache hit, no re-download) |
|
|
151
|
+
| `setQuality(patch) / getQuality()` | Live quality-tier patch (partial update), applied in place with no remount; survives scene/load() changes |
|
|
142
152
|
| `setProperties(props)` | Live property updates: patches the property table / effect constants / script sandboxes in place, no re-fetch |
|
|
143
153
|
| `getProperties()` | The current flattened property value map |
|
|
144
154
|
| `setAudio(src)` | Swap the audio spectrum source (pull model, once per frame); null falls back to the built-in sim. Survives scene changes. Works for scene and web |
|
|
145
155
|
| `setMedia(src)` | Swap the system media source (Now Playing); shared by scene and web, survives scene changes |
|
|
146
156
|
| `media` | Media control surface: read snapshot, plus skipNext / skipPrevious / play / pause / playPause transport control |
|
|
147
|
-
| `pushPointer(u, v, buttons?)` | Inject pointer state (u/v normalized 0..1). For hosts whose window cannot receive the mouse; works for scene and web |
|
|
157
|
+
| `pushPointer(u, v, buttons?, mods?)` | Inject pointer state (u/v normalized 0..1; mods is a ctrl/shift/alt/meta mask). For hosts whose window cannot receive the mouse; works for scene and web |
|
|
148
158
|
| `pointerLeave()` | Pointer left: clears buttons but keeps the last position (dropping it makes parallax and xray visibly jump) |
|
|
159
|
+
| `pushWheel(dx, dy, mode?, mods?)` | Inject wheel / trackpad gestures (web wallpapers only). Positive dy scrolls content down; a macOS pinch maps to the ctrl bit in mods |
|
|
149
160
|
| `load(source)` | Switch scenes reusing the same canvas and WebGL context; resolves after the first frame |
|
|
150
161
|
| `release() / restore()` | Free GL resources keeping the config (display sleep) / rebuild from the kept config |
|
|
151
162
|
| `destroy()` | Terminal: frees resources, unbinds listeners; the instance is dead afterwards |
|
|
@@ -243,14 +254,14 @@ console.log(wp.media.snapshot.title);
|
|
|
243
254
|
wp.media.playPause();
|
|
244
255
|
```
|
|
245
256
|
|
|
246
|
-
- The five palette fields must be chainable color objects (scripts write c.subtract(o).multiply(t).add(o); a plain array throws a TypeError that kills the whole script) — createMediaSource guarantees this for you
|
|
257
|
+
- The five palette fields must be chainable color objects (scripts write c.subtract(o).multiply(t).add(o); a plain array throws a TypeError that kills the whole script) — createMediaSource guarantees this for you; to build one by hand use the exported mediaColor(r, g, b), which accepts 0..1 components, {x,y,z} or arrays and returns an object with the full chain API
|
|
247
258
|
- All transport methods are optional: if you only provide metadata, wallpaper buttons are silently inert rather than throwing
|
|
248
259
|
- setMedia survives scene changes: install once and it applies to every scene loaded afterwards
|
|
249
260
|
- For video wallpapers the audio spectrum is captured from the <video> automatically (visualizers react to the video's own audio); an explicit setAudio() injection takes precedence
|
|
250
261
|
|
|
251
262
|
## Injecting pointer & audio
|
|
252
263
|
|
|
253
|
-
A wallpaper host often cannot rely on the browser's native input: desktop wallpapers sit in the desktop underlay layer where the system's desktop window swallows mouse events, and the audio spectrum has to be captured by the host itself.
|
|
264
|
+
A wallpaper host often cannot rely on the browser's native input: desktop wallpapers sit in the desktop underlay layer where the system's desktop window swallows mouse and wheel events, and the audio spectrum has to be captured by the host itself. These channels are fed through instance methods.
|
|
254
265
|
|
|
255
266
|
```
|
|
256
267
|
// Pointer: u/v are normalized 0..1; buttons matches MouseEvent.buttons
|
|
@@ -258,6 +269,10 @@ wp.pushPointer(0.5, 0.5, 0); // hover at the center
|
|
|
258
269
|
wp.pushPointer(0.5, 0.5, 1); // press the left button
|
|
259
270
|
wp.pointerLeave(); // pointer left (clears buttons, keeps last position)
|
|
260
271
|
|
|
272
|
+
// Wheel / trackpad (web wallpapers only): dy matches DOM deltaY; mode 0=px 1=line 2=page
|
|
273
|
+
wp.pushWheel(0, 100, 0, 0); // two-finger scroll down one notch
|
|
274
|
+
wp.pushWheel(0, -50, 0, 1); // macOS pinch = ctrl bit (mods bit0)
|
|
275
|
+
|
|
261
276
|
// Audio: pull model — the render loop calls snapshot() once per frame
|
|
262
277
|
let latest = { left: new Float32Array(64), right: new Float32Array(64) };
|
|
263
278
|
wp.setAudio({ snapshot: () => latest });
|
|
@@ -269,6 +284,8 @@ wp.setAudio(null); // remove the source, fall back to the built-in s
|
|
|
269
284
|
```
|
|
270
285
|
|
|
271
286
|
- Injected pointer state coexists with the canvas's own DOM listeners — last writer wins. It works for scene and web wallpapers; media wallpapers have no pointer concept, so the call is silently inert
|
|
287
|
+
- pushWheel only affects web wallpapers: scenes have no wheel API (zero consumers across 194 scene wallpapers tested). The web side synthesizes both the modern wheel and the legacy mousewheel — the only wallpaper that genuinely uses the wheel (a 360° panorama, 3406740580) listens solely to the legacy event, while three.js OrbitControls listens solely to the modern one. DOMMouseScroll is deliberately not dispatched, or the same scroll would be processed twice
|
|
288
|
+
- macOS trackpad: feed two-finger scrolls as pixel deltas (mode=0); map a two-finger pinch to ctrl+wheel (mods bit0), exactly as browsers do — OrbitControls / pano2vr rely on that bit to tell zoom from scroll
|
|
272
289
|
- Audio contract: 64 bands per channel, values 0..1. Short arrays are zero-padded and long ones truncated; the 32/16-band downsamples plus level and silence detection are derived by the library
|
|
273
290
|
- Returning null (or throwing) from snapshot() means "no data this frame" and the engine falls back to the built-in simulation — no special handling needed while host capture is still warming up
|
|
274
291
|
- setAudio survives scene changes: install it once and it applies to every scene loaded afterwards
|
|
@@ -321,82 +338,7 @@ b.pause(); // does not affect a
|
|
|
321
338
|
|
|
322
339
|
## Changelog
|
|
323
340
|
|
|
324
|
-
Current version: 1.
|
|
325
|
-
|
|
326
|
-
| Version | Date | Notes |
|
|
327
|
-
| --- | --- | --- |
|
|
328
|
-
| `1.3.5` | 2026-09-08 | xray effect: when the author doesn't configure size in the scene, the fallback is now 1 (identity) instead of the shader comment's 0.2 |
|
|
329
|
-
| `1.3.4` | 2026-09-08 | Closes the four items deferred from 1.3.3: setFit now affects web wallpapers, audio:null truly mutes, the bare-iframe fallback no longer reports 0 fps, and debug globals are cleared on unmount |
|
|
330
|
-
| `1.3.3` | 2026-09-08 | Full audit: fixed autoplay:false hanging mount(), setMedia being inert on the scene path, AudioContext leaking on wallpaper swap, and more |
|
|
331
|
-
| `1.3.2` | 2026-09-08 | Fix: the "live system" microphone only fed scene wallpapers; web wallpaper visualizers still showed the synthetic stream |
|
|
332
|
-
| `1.3.1` | 2026-09-08 | Fix: injected audio/media sources never reached web wallpapers (visualizers kept playing the default stream) |
|
|
333
|
-
| `1.3.0` | 2026-09-08 | mediaSource() for arbitrary video/images; type sniffing; volume for media wallpapers; one Now Playing driver shared by scene and web |
|
|
334
|
-
| `1.2.0` | 2026-09-08 | Video wallpapers work through the library entry; audio and pointer injection wired into the public API (three downstream reports) |
|
|
335
|
-
| `1.1.0` | 2026-09-07 | External pointer injection channel, web wallpaper interaction, effect-pass compile fixes, complete pause semantics |
|
|
336
|
-
| `1.0.0` | 2026-09-06 | First stable release: the public API is settled (mount / SceneInstance / Source) |
|
|
337
|
-
| `1.0.0-beta1` | 2026-09-04 | First public preview |
|
|
338
|
-
|
|
339
|
-
1.3.5 contains a single change: the xray effect's fallback value. xray's size drives the effect radius (it is inverted internally, so size=1 is identity). When the author doesn't write size into the scene's constantshadervalues, the shader declaration comment's "default":0.2 used to be applied — but that is the slider's initial position when the WE editor creates the effect, not a runtime fallback: as soon as the editor attaches the effect to a layer it writes the current slider value into the scene file, so the official runtime always reads an explicit value. Applying 0.2 shrank the effect radius to a fifth, leaving only a small patch around the cursor. The fallback is now 1. The change is scoped to this one parameter; the comment defaults for multiply and the texture slots are unchanged.
|
|
340
|
-
|
|
341
|
-
1.3.4 closes the four items 1.3.3 listed as deferred. All four are observable behaviour bugs, not cleanup refactors:
|
|
342
|
-
|
|
343
|
-
- **setFit() did nothing on web wallpapers**: it only updated the config and the cover alignment, while a web wallpaper's scaling lives in an iframe transform written by a layout pass. Without a re-layout the old ratio stayed. setFit now triggers one (scene and media wallpapers read the config every frame and were never affected)
|
|
344
|
-
- **The bare-iframe fallback reported 0 fps forever**: when shim injection fails that path starts no rAF at all, so nothing advanced the frame counter and hosts saw what looked like a dead wallpaper. The fallback now runs a heartbeat rAF and reports the real refresh rate
|
|
345
|
-
- **audio:null fell back to the synthetic stream on web wallpapers instead of muting**: the check only asked whether an injected source existed, conflating "explicitly disabled" with "never set". The two are now distinct, and audio:null / media:null genuinely disable on both scene and web (setAudio(src) re-enables)
|
|
346
|
-
- **Debug globals outlived unmount**: 19 diagnostic hooks such as __scene and __textures stayed on window after clear(), pointing at the destroyed scene's object graph. That both pinned the previous wallpaper's textures and layer tree in memory and let hosts read stale state from the console. clear() now deletes each of them
|
|
347
|
-
|
|
348
|
-
1.3.3 is a full audit covering unwired API, defects and memory leaks. Everything fixed here was confirmed by measurement:
|
|
349
|
-
|
|
350
|
-
- **autoplay:false hung mount() forever** (scene and media wallpapers): pausing before assembly meant the render loop never ran a single frame, so the only first-frame trigger was unreachable and the promise neither resolved nor rejected. Measured: the same wallpaper resolved by default but was still pending after 500 live rAF frames with autoplay:false. It now assembles normally and pauses after the first frame is drawn
|
|
351
|
-
- The first-frame callback now fires after render() completes (previously it ran before the render call, landing one frame early, so autoplay:false handed back a blank canvas)
|
|
352
|
-
- **setMedia() was inert on scene wallpapers**: the scene captured its media driver once at assembly, while setMedia is typically called after mount(). It now re-picks on every read (the web path already did)
|
|
353
|
-
- **Swapping wallpapers leaked an AudioContext**: the video spectrum takeover registered its release on the instance-level list, but swapping goes through clear() while only destroy() drained it. Browsers cap out at roughly 6 AudioContexts, after which audio reactivity silently dies. A per-wallpaper release list is now drained by clear()
|
|
354
|
-
- **One throwing cleanup dropped the entire teardown**: clear() called the assembly-layer cleanup without try/catch, so a single exception skipped the WebGL context release, video element recycling and blob revocation that followed
|
|
355
|
-
- **Swapping wallpapers during the mic permission prompt orphaned the stream**: getUserMedia blocks on the system dialog, and the release was registered after the await, so nothing on that path ever called it and the browser's recording indicator stayed lit. Both assembly paths now register the release slot synchronously
|
|
356
|
-
- The instance.media control surface is reset by clear() (previously it still pointed at the destroyed scene's sandbox closures after release()/destroy())
|
|
357
|
-
|
|
358
|
-
Still unwired by design (marked in the type comments): MountOptions' pointer and features. Use pushPointer() to feed pointer state from outside.
|
|
359
|
-
|
|
360
|
-
1.3.2 closes the **second channel** of the same symptom: 1.3.1 fixed host injection (setAudio), while the bench's "live system" checkbox takes a different path (cfg.liveSystem, where the library captures the microphone itself). That path was only consumed by the scene assembly — web.ts referenced liveSystem zero times — so ticking the box made scene visualizers follow the mic while web wallpapers stayed on the synthetic stream.
|
|
361
|
-
|
|
362
|
-
- Web wallpapers now receive the live-system microphone through the same capture path as scene (startLiveSystem)
|
|
363
|
-
- Microphone capture is asynchronous (getUserMedia needs user consent) and does not block pump startup: the default source runs first, then the mic is filled in and the per-frame source pick switches over
|
|
364
|
-
- Audio source priority: host injection (setAudio) > live-system microphone > built-in simulation
|
|
365
|
-
- The microphone stream is released on teardown (otherwise the browser's recording indicator stays lit)
|
|
366
|
-
|
|
367
|
-
1.3.1 fixes a defect found downstream in real use: **the microphone was connected, yet web wallpapers kept showing the default synthetic stream**.
|
|
368
|
-
|
|
369
|
-
- Root cause 1: the web assembly path never read rt.audioBridge (1.3.0 wired this up for the media source but missed the audio line), so injected spectra never reached the iframe
|
|
370
|
-
- Root cause 2: both the audio and media pumps captured their driver at assembly time, while setAudio()/setMedia() are typically called after mount() (that is when the host's microphone or SSE channel becomes ready) — a fixed driver means a later source never takes effect. Both pumps now pick their source per frame and fall back cleanly when the source is removed
|
|
371
|
-
- Injected spectra are no longer put through the gamma contrast expansion: that step exists for the built-in simulation's unclamped bands, and applying it to a host's already-normalized 0..1 spectrum would peg every bar at full scale
|
|
372
|
-
|
|
373
|
-
1.3.0 continues directly from 1.2.0, closing four more items reported by the downstream host:
|
|
374
|
-
|
|
375
|
-
- New mediaSource(urlOrFile): any video or image can be a wallpaper, from a remote URL or a local File (drag & drop). The library revokes the objectURL on destroy()/source swap — without that, every wallpaper change would leak a multi-megabyte blob
|
|
376
|
-
- Type sniffing: with no project.type the URL extension decides (correctly stripping a signed URL's ?query and #hash), falling back to scene only when unrecognized. An explicit project.type always wins and is never overridden
|
|
377
|
-
- setVolume now works on media wallpapers (previously a complete no-op — volume only reached the scene audio graph). It writes both volume and muted, since changing volume on a muted <video> does nothing; if unmuting is blocked by the autoplay policy that is reported via onDiagnostic rather than silently swallowed
|
|
378
|
-
- MediaSource was widened to the driver's real contract (an 18-field snapshot plus five optional transport methods) and is now **one shared instance across scene and web** — previously each side created its own simulation with no way for the host to inject. The new createMediaSource() takes only the fields you have and fills in palette, lyric line and trackIndex, guaranteeing chainable color instances
|
|
379
|
-
- New SceneInstance.setMedia() and a media control surface (snapshot plus previous/next/play-pause); transport commands forward straight to the host driver, and host-side data updates are visible to the engine in the same frame
|
|
380
|
-
- Video wallpapers capture their spectrum from the <video> itself so visualizers react to the video's own audio; an explicit setAudio() injection takes precedence
|
|
381
|
-
|
|
382
|
-
1.2.0 closes three gaps where the runtime capability already worked but was never exposed through the library entry (reported by the downstream host wallpaperEM):
|
|
383
|
-
|
|
384
|
-
- video / gif / image wallpapers can now be mounted via mount(): a new Source.mediaEntry() supplies the URL and the media path gained the library contract it lacked. Previously it fired neither onFirstFrame nor onError, so even with routing in place the mount() promise would hang forever — never resolving, never rejecting
|
|
385
|
-
- The media path now honors a caller-supplied canvas and sizes its backing store from CSS dimensions rather than the window (previously an embedded canvas got a full-window buffer and was never inserted into the DOM at all)
|
|
386
|
-
- MountOptions.audio is actually wired now (it was a declared-but-unreferenced dead field), plus a new SceneInstance.setAudio() for swapping after mount — host spectrum channels usually become ready only after mount()
|
|
387
|
-
- New SceneInstance.pushPointer() / pointerLeave(), matching the full-page renderer's __wp in both name and signature so downstream code needs no changes when migrating to the library
|
|
388
|
-
- Known boundaries at the time: audio injection was scene-only — web wallpapers use a separate iframe-shim channel, wired up in 1.3.1; media wallpapers have no pointer concept; MountOptions' pointer / media / features were unwired (media landed in 1.3.0)
|
|
389
|
-
|
|
390
|
-
What went into 1.1.0 (merged after 1.0.0):
|
|
391
|
-
|
|
392
|
-
- New external pointer injection channel (__wp.pushPointer / pointerLeave): when the wallpaper window cannot receive the mouse (e.g. the Finder desktop window swallows events on macOS), the host polls the system cursor and pushes it in. Scene and web wallpapers share one protocol — callers need not branch on type
|
|
393
|
-
- Web wallpapers joined the same channel: the shim synthesizes DOM events against the hit element (full over/out/enter/leave chains, click derived from button edges). Of 49 local web wallpapers, interaction went from dead to working on 24 with mousemove, 29 with click and 16 with pointer events. Hard limit: CSS :hover is driven by browser hit-testing and cannot be lit by synthetic events
|
|
394
|
-
- Effect-pass compile failures driven to near zero (seven rounds): the transpiler now handles int/float mixing, macro scoping, vector narrowing, scientific notation and more. Library-wide effect-pass compilation went from 1653/1873 (88.3%) to 1823/1873 (97.3%), +170 in total. The symptom was an effect silently missing — a failed compile only warns, so god rays / visualizers / glows simply vanished
|
|
395
|
-
- Pause semantics completed: pausing must freeze rAF/timers and CSS animations together (compositor-driven CSS animations ignore JS freezing — 1444432396 kept animating after pause); resuming must re-arm held rAF callbacks (self-recursive rAF wallpapers break their chain permanently — 1278092907 froze forever after resume), restoring only what we paused
|
|
396
|
-
- Fidelity fixes: keyframe animations now run on the real clock (previously they accumulated the target frame interval, diverging from the bone clock by 5.5s over 30s — hair desynced from the head and the scalp showed through); object scripts and keyframe animations now work in local space with per-frame parent/child recomposition (previously local return values were written straight into world slots, so elements drifted away untouched and got clipped); hidden mask layers referenced by clipping_mask now correctly read back what is behind them (previously they fell back to the referencing layer itself, painting a solid white block)
|
|
397
|
-
- Two new documentation sections, "Mount target" and "User properties": web wallpapers require a container div rather than a canvas, and what value shape each property type expects
|
|
398
|
-
|
|
399
|
-
The full commit history lives in the GitHub repository; every fix records its symptom, root cause, measured scope and verification method in the commit message.
|
|
341
|
+
Current version: 1.4.1. Per-version lists are no longer maintained here: every change records its symptom, root cause, measured scope and verification method in the corresponding commit message — see the commit history and Releases on the GitHub repository.
|
|
400
342
|
|
|
401
343
|
## Copyright & compliance
|
|
402
344
|
|
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 简介
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
让 Wallpaper Engine 实现再次伟大!!!恭喜你发现宝藏,这是全网复刻程度最高,功能最强,更新最快的 Wallpaper Engine 自研核心库,纯 TypeScript/JS 实现。webwallgl 是一个浏览器端的 Wallpaper Engine「scene」场景、视频、web 网页壁纸渲染库:主要功能是把创意工坊场景包(scene.pkg)在 WebGL 里实时还原,支持图层效果链、粒子、3D 木偶骨骼、文字挂件、脚本沙箱、音频响应与用户自定义属性热更新;网页类型壁纸经 sandbox iframe + 加载前 WE shim 注入运行。后续版本将加入本库独有效果支持,请敬请期待。下游如果进行了库的引用,麻烦给个 star,非常感谢!
|
|
8
8
|
|
|
9
9
|
- [GitHub 开源仓库](https://github.com/oneincase/webwallgl)
|
|
10
10
|
- [在线版(GitHub Pages)](https://oneincase.github.io/webwallgl/)
|
|
11
11
|
|
|
12
|
-
- 零运行时依赖,单文件引入(min ESM 约
|
|
12
|
+
- 零运行时依赖,单文件引入(min ESM 约 930KB / gzip 约 295KB)
|
|
13
13
|
- 可 npm 安装,也可 <script> CDN 引入;一页可开多个互不干扰的实例
|
|
14
14
|
- 本测试台本身就是库的第一个使用者 —— 你在这里看到的能力都是公共 API
|
|
15
15
|
|
|
@@ -36,12 +36,12 @@ import { mount, httpSource } from "webwallgl";
|
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
// 2) ESM CDN(jsDelivr,vite/webpack 之外的直引方式)
|
|
39
|
-
import { mount, httpSource } from "https://cdn.jsdelivr.net/npm/webwallgl@1.
|
|
39
|
+
import { mount, httpSource } from "https://cdn.jsdelivr.net/npm/webwallgl@1.4.1/webwallgl.min.mjs";
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
```
|
|
43
43
|
<!-- 3) UMD <script>:暴露全局 WebWallGL -->
|
|
44
|
-
<script src="https://cdn.jsdelivr.net/npm/webwallgl@1.
|
|
44
|
+
<script src="https://cdn.jsdelivr.net/npm/webwallgl@1.4.1/webwallgl.global.min.js"></script>
|
|
45
45
|
<script>
|
|
46
46
|
const { mount, httpSource } = WebWallGL;
|
|
47
47
|
</script>
|
|
@@ -122,15 +122,24 @@ input.addEventListener("change", () => {
|
|
|
122
122
|
| --- | --- | --- |
|
|
123
123
|
| `source(必填)` | `—` | 见上节 Source |
|
|
124
124
|
| `fit` | `"cover"` | cover 等比裁切铺满 / contain 等比留边 / stretch 拉伸 |
|
|
125
|
-
| `renderDpr` | `
|
|
125
|
+
| `renderDpr` | `0(自动)` | 渲染 DPR:0=跟随设备 devicePixelRatio(Retina/HiDPI 原生清晰);正数=目标 DPR,可高于设备上报值(宿主 WKWebView 误报 1 时仍能超采样到物理分辨率)。物理最长边封顶 4096,调低省显存。该档位同时决定贴图资源倍率(档位越低,贴图按图层足迹选得越小,见下节「显存与清晰度」) |
|
|
126
126
|
| `fps` | `60` | 帧率上限;被上限跳过的帧不计入 stats.fps,掉帧一眼可见 |
|
|
127
127
|
| `volume` | `0` | 0..1。默认静音起步(浏览器自动播放策略);就绪后再设非零音量 |
|
|
128
128
|
| `autoplay` | `true` | false 时挂载后保持暂停 |
|
|
129
129
|
| `properties` | `{}` | 初始用户属性覆盖值(键为属性名) |
|
|
130
130
|
| `pointer / audio / media` | `内置` | 指针跟随 canvas、音频/系统媒体为确定性模拟;传 null 禁用 |
|
|
131
131
|
| `features` | `全开` | 调试开关:models / text / particles / effects / components |
|
|
132
|
+
| `quality` | `全默认` | 渲染质量档位(对标 WE 客户端性能选项):antiAliasing: "off"(默认)/"fxaa"/"msaa2"/"msaa4"、particles: "high"(默认)/"medium"/"low"/"off"(低/中档按倍率同时缩数量上限与发射率)、postProcessing: "high"(默认)/"medium"/"low"/"off"(低/中档压效果链 FBO 分辨率;off=效果链直通+跳整屏后期层+关 Bloom)。超采样走 renderDpr |
|
|
132
133
|
| `onReady / onError / onDiagnostic` | `—` | 回调面;也可之后用 instance.on() 订阅 |
|
|
133
134
|
|
|
135
|
+
显存与清晰度:显存的大头是纹理与画布/效果链缓冲,两者都随挂载选项自动伸缩,不需要手动管理——
|
|
136
|
+
|
|
137
|
+
- 贴图按「清晰度 × 图层在屏幕上的实际足迹」自动选分辨率:只要贴图分辨率 ≥ 图层的设备像素足迹,画面与全分辨率素材逐像素一致——省掉的只是过采样。有 mip 链的图直接截链(零重采样),单级大图才重采样
|
|
138
|
+
- 压缩纹理(DXT1/3/5、BC7、ETC2)按文件自带的 mip 链直传给 GPU,不解成 RGBA;单通道 R8 走 GL_R8 直传。浏览器不支持对应扩展时自动回退解码路径,观感不变
|
|
139
|
+
- 上传完成后立即释放 CPU 侧解码副本(多帧动画 .tex 除外),最坏单墙可省数百 MB
|
|
140
|
+
- 白名单不缩:LUT 数据栅格、多帧动画、视频纹理保持原样;法线/蒙版可降但有独立下限。挂载后 window.__memStats() 给出 pkg/解码/上传的分项台账
|
|
141
|
+
- 低内存设备的推荐组合:清晰度 0.8 + 质量 low(粒子/后处理 low、抗锯齿关)。4K 屏上画布与效果链 FBO 才是大头(随 DPR 平方增长,MSAA4 再 ×4),纹理反而是其次
|
|
142
|
+
|
|
134
143
|
## 实例 API SceneInstance
|
|
135
144
|
|
|
136
145
|
| 成员 | 说明 |
|
|
@@ -138,13 +147,15 @@ input.addEventListener("change", () => {
|
|
|
138
147
|
| `pause() / resume() / paused` | 暂停恢复。禁止整包重挂:恢复时视频/音频从暂停点继续 |
|
|
139
148
|
| `setFit(fit) / setFps(n) / setVolume(v)` | 热更新,无需重挂载(渲染循环每帧读取) |
|
|
140
149
|
| `setRenderDpr(dpr)` | 改 DPR 需重建画布,内部自动重挂(pkg 缓存命中,不重新下载) |
|
|
150
|
+
| `setQuality(patch) / getQuality()` | 质量档位热更(部分更新),就地生效不重挂载;换场景/load() 后保持 |
|
|
141
151
|
| `setProperties(props)` | 属性热更新:就地改属性表/效果常量/脚本沙箱,不重新拉包 |
|
|
142
152
|
| `getProperties()` | 当前生效的扁平化属性值表 |
|
|
143
153
|
| `setAudio(src)` | 换音频频谱源(拉模式,每帧一次);null 回落内置模拟。换场景不清空。scene 与 web 都生效 |
|
|
144
154
|
| `setMedia(src)` | 换系统媒体源(Now Playing);scene 与 web 共用同一实例,换场景不清空 |
|
|
145
155
|
| `media` | 媒体控制面:读 snapshot,以及 skipNext / skipPrevious / play / pause / playPause 反向控制 |
|
|
146
|
-
| `pushPointer(u, v, buttons?)` | 外部指针注入(u/v 为 0..1
|
|
156
|
+
| `pushPointer(u, v, buttons?, mods?)` | 外部指针注入(u/v 为 0..1 归一化;mods 为 ctrl/shift/alt/meta 掩码)。用于窗口收不到鼠标的宿主;scene 与 web 均生效 |
|
|
147
157
|
| `pointerLeave()` | 指针离开:只清按键、保留最后位置(清位置会让视差与 xray 明显抽一下) |
|
|
158
|
+
| `pushWheel(dx, dy, mode?, mods?)` | 滚轮 / 触摸板注入(仅网页壁纸)。dy 正=内容向下;Mac 触摸板双指捏合映射成 mods 的 ctrl 位 |
|
|
148
159
|
| `load(source)` | 换场景,复用同一 canvas 与 WebGL 上下文;首帧后 resolve |
|
|
149
160
|
| `release() / restore()` | 释放显存但保留配置(显示器睡眠)/ 用保留的配置重建 |
|
|
150
161
|
| `destroy()` | 终态:释放资源、解绑监听,之后实例不可再用 |
|
|
@@ -242,14 +253,14 @@ console.log(wp.media.snapshot.title);
|
|
|
242
253
|
wp.media.playPause();
|
|
243
254
|
```
|
|
244
255
|
|
|
245
|
-
- 五个配色字段必须是可链式调用的颜色对象(脚本会写 c.subtract(o).multiply(t).add(o),给普通数组会 TypeError 熔断整个脚本)——用 createMediaSource
|
|
256
|
+
- 五个配色字段必须是可链式调用的颜色对象(脚本会写 c.subtract(o).multiply(t).add(o),给普通数组会 TypeError 熔断整个脚本)——用 createMediaSource 构造即自动满足;要手工构造时用导出的 mediaColor(r, g, b),它接受 0..1 分量、{x,y,z} 或数组,返回带完整链式方法的对象
|
|
246
257
|
- 控制方法全是可选的:只提供元数据、不支持控制时,壁纸里的按钮点了静默无效,不会报错
|
|
247
258
|
- setMedia 换场景不清空,装一次对之后所有场景生效
|
|
248
259
|
- 视频壁纸的音频频谱会自动从 <video> 取(音条能跟着视频里的音乐动),宿主已用 setAudio 显式注入时则不接管
|
|
249
260
|
|
|
250
261
|
## 注入指针与音频
|
|
251
262
|
|
|
252
|
-
壁纸宿主常常拿不到浏览器天然的输入:桌面壁纸叠在桌面 underlay
|
|
263
|
+
壁纸宿主常常拿不到浏览器天然的输入:桌面壁纸叠在桌面 underlay 层,鼠标与滚轮事件被系统的桌面窗口吃掉;音频频谱也得由宿主自己采集。这几条通道都由实例方法喂进来。
|
|
253
264
|
|
|
254
265
|
```
|
|
255
266
|
// 指针:u/v 是 0..1 归一化坐标,buttons 同 MouseEvent.buttons
|
|
@@ -257,6 +268,10 @@ wp.pushPointer(0.5, 0.5, 0); // 悬停在正中
|
|
|
257
268
|
wp.pushPointer(0.5, 0.5, 1); // 按下左键
|
|
258
269
|
wp.pointerLeave(); // 鼠标移出(只清按键,保留最后位置)
|
|
259
270
|
|
|
271
|
+
// 滚轮 / 触摸板(仅网页壁纸):dy 与 DOM deltaY 同向;mode 0=像素 1=行 2=页
|
|
272
|
+
wp.pushWheel(0, 100, 0, 0); // 双指向下滚一格
|
|
273
|
+
wp.pushWheel(0, -50, 0, 1); // Mac 双指捏合 = ctrl 位(mods bit0)
|
|
274
|
+
|
|
260
275
|
// 音频:拉模式,渲染循环每帧调一次 snapshot()
|
|
261
276
|
let latest = { left: new Float32Array(64), right: new Float32Array(64) };
|
|
262
277
|
wp.setAudio({ snapshot: () => latest });
|
|
@@ -268,6 +283,8 @@ wp.setAudio(null); // 撤源,回落内置模拟
|
|
|
268
283
|
```
|
|
269
284
|
|
|
270
285
|
- 指针注入与 canvas 自身的 DOM 监听并存,谁后写谁赢;scene 与 web 壁纸都生效,媒体壁纸没有指针概念,调用静默无效
|
|
286
|
+
- pushWheel 只对网页壁纸生效:场景壁纸没有滚轮 API(实测 194 张场景壁纸零消费)。网页侧会同时合成现代 wheel 与旧式 mousewheel——语料里唯一真正用滚轮的 360° 全景(3406740580)只听旧式,而 three.js OrbitControls 只听现代;不发 DOMMouseScroll,否则同一滚动会被处理两遍
|
|
287
|
+
- Mac 触摸板:双指滚动直接喂像素级 delta(mode=0);双指捏合按浏览器约定映射成 ctrl+滚轮(mods bit0),OrbitControls / pano2vr 都靠它区分缩放与滚动
|
|
271
288
|
- 音频契约:left/right 各 64 段、值域 0..1。段数不足补零、超出截断;32/16 段降采样与响度、静音判定由库派生
|
|
272
289
|
- snapshot() 返回 null(或抛错)表示本帧无数据,引擎自动回落内置模拟源——宿主采集还没就绪时不必特殊处理
|
|
273
290
|
- setAudio 换场景不清空:装一次对之后 load() 的所有场景都生效
|
|
@@ -320,82 +337,7 @@ b.pause(); // 不影响 a
|
|
|
320
337
|
|
|
321
338
|
## 版本更新说明
|
|
322
339
|
|
|
323
|
-
当前版本 1.
|
|
324
|
-
|
|
325
|
-
| 版本 | 日期 | 说明 |
|
|
326
|
-
| --- | --- | --- |
|
|
327
|
-
| `1.3.5` | 2026-09-08 | xray 效果:作者未在场景里配置 size 时,缺省从 shader 注释的 0.2 改为 1(恒等) |
|
|
328
|
-
| `1.3.4` | 2026-09-08 | 补齐 1.3.3 遗留四项:setFit 对网页壁纸生效、audio:null 真静音、裸 iframe 回退不再帧数恒 0、调试全局随卸载清理 |
|
|
329
|
-
| `1.3.3` | 2026-09-08 | 全量审计:修 autoplay:false 挂死 mount()、scene 侧 setMedia 无效、换壁纸泄漏 AudioContext 等 |
|
|
330
|
-
| `1.3.2` | 2026-09-08 | 修复:「系统实况」麦克风此前只喂 scene,网页壁纸音谱仍是合成流 |
|
|
331
|
-
| `1.3.1` | 2026-09-08 | 修复:注入的频谱/媒体源到不了网页壁纸(音谱仍放默认流) |
|
|
332
|
-
| `1.3.0` | 2026-09-08 | mediaSource() 任意视频/图片;类型嗅探;媒体壁纸支持音量;scene 与 web 共用一套 Now Playing driver |
|
|
333
|
-
| `1.2.0` | 2026-09-08 | video 壁纸可走库入口;音频与指针注入接到公共 API(下游三项反馈) |
|
|
334
|
-
| `1.1.0` | 2026-09-07 | 外部指针注入通道、网页壁纸交互、效果 pass 编译清零、暂停语义补全 |
|
|
335
|
-
| `1.0.0` | 2026-09-06 | 首个正式版:公共 API 定稿(mount / SceneInstance / Source 三件套) |
|
|
336
|
-
| `1.0.0-beta1` | 2026-09-04 | 首个公开测试版 |
|
|
337
|
-
|
|
338
|
-
1.3.5 只有一项,改的是 xray 效果的缺省值。xray 的 size 决定效果范围(内部取倒数,size=1 是恒等)。作者若没在场景的 constantshadervalues 里写 size,此前会套用 shader 声明注释里的 "default":0.2 —— 但那是 WE 编辑器新建效果时滑条的初始位置,不是运行时缺省:编辑器一旦把效果加到层上就会把当时的滑条值写进场景文件,所以官方运行时永远读得到显式值。套 0.2 会让效果范围缩成五分之一,只剩光标旁一小块。现在缺省是 1。作用面收窄在这一个参数上,multiply 与贴图槽的注释缺省不变。
|
|
339
|
-
|
|
340
|
-
1.3.4 收掉 1.3.3 结尾列为「留待后续」的四项,都是能观测到的行为偏差,不是清理式重构:
|
|
341
|
-
|
|
342
|
-
- **setFit() 对网页壁纸不生效**:它只改了配置和 cover 对齐量,而网页壁纸的缩放是靠一次布局计算写进 iframe 的 transform 的,不重排就还是旧比例。现在 setFit 会触发重排(场景与媒体壁纸本来就每帧读配置,不受影响)
|
|
343
|
-
- **注入 shim 失败退回裸 iframe 后帧率恒 0**:这条路径不启动任何 rAF,统计里没人推进帧计数,宿主看到的是「壁纸挂了」。回退分支补上心跳 rAF,帧率与实际刷新一致
|
|
344
|
-
- **audio:null 在网页壁纸上不是静音,而是退回合成流**:判定只看「有没有设过注入源」,分不清「显式禁用」与「没设置」。现在两者分开,audio:null / media:null 在 scene 与 web 上都真的关掉(setAudio(src) 会重新打开)
|
|
345
|
-
- **调试全局不随卸载清理**:__scene / __textures 等 19 个诊断入口在 clear() 后仍挂在 window 上,指着已销毁场景的对象图,既让上一张壁纸的纹理和层树无法回收,也会让宿主在控制台里读到过期状态。改为 clear() 时逐个删除
|
|
346
|
-
|
|
347
|
-
1.3.3 是一次覆盖「漏接 / 缺陷 / 内存泄漏」三类的全量审计,修掉的都是实测确认的问题:
|
|
348
|
-
|
|
349
|
-
- **autoplay:false 会让 mount() 永久挂起**(scene 与媒体壁纸):装配前就置 paused,渲染循环一帧都不跑,唯一触发首帧回调的地方永远到不了,Promise 既不 resolve 也不 reject。实测同一张壁纸默认能 resolve、autoplay:false 在 rAF 活跃 500 帧后仍 pending。改为照常装配、首帧画完再暂停
|
|
350
|
-
- 首帧回调改到 render() **完成之后**触发(此前排在 render 调用之前,早一帧落地,autoplay:false 会拿到一张空画布)
|
|
351
|
-
- **setMedia() 在场景壁纸上无效**:scene 把 media driver 在装配时一次性捕获,而 setMedia 通常在 mount() 之后才调用。改为每次读取重新选(web 侧本来就是这样)
|
|
352
|
-
- **换壁纸会泄漏 AudioContext**:视频壁纸的频谱接管把释放登记在实例级列表里,而换壁纸走的是 clear(),只有 destroy() 才排空。浏览器约 6 个 AudioContext 就到顶,之后音频响应静默失效。新增壁纸级释放列表,clear() 逐张排空
|
|
353
|
-
- **清理链一处抛异常会丢掉整个 teardown**:clear() 里调用装配层清理没有 try/catch,一旦抛出,后面的 WebGL 上下文释放、视频元素回收、blob revoke 全部跳过
|
|
354
|
-
- **麦克风授权期间换壁纸会漏掉麦克风流**:getUserMedia 阻塞在系统弹窗上,此前的释放登记写在 await 之后,这条路径上没人会调它,浏览器录音指示一直亮着。两条装配路径都改为同步登记释放槽
|
|
355
|
-
- instance.media 控制面在 clear() 时重置(此前 release()/destroy() 之后它仍指向已销毁场景的沙箱闭包)
|
|
356
|
-
|
|
357
|
-
已知仍未接线(有意为之,类型注释已标注):MountOptions 的 pointer 与 features。外部喂指针请用 pushPointer()。
|
|
358
|
-
|
|
359
|
-
1.3.2 补上同一症状的**第二条通道**:1.3.1 修的是宿主注入(setAudio),而测试台「系统实况」勾选框走的是另一条路(cfg.liveSystem,库自己采麦克风),它此前只被 scene 装配路径消费——web.ts 里 liveSystem 零引用,所以勾上之后场景壁纸的音条跟着麦克风动、网页壁纸却始终是合成流。
|
|
360
|
-
|
|
361
|
-
- 网页壁纸现在也接系统实况麦克风,与 scene 用同一份采集(startLiveSystem)
|
|
362
|
-
- 麦克风采集是异步的(getUserMedia 要用户授权),不阻塞泵启动:先按默认源跑,授权通过后回填、由逐帧选源切过去
|
|
363
|
-
- 音频源优先级:宿主注入(setAudio)> 系统实况麦克风 > 内置模拟
|
|
364
|
-
- 卸载时释放麦克风流(否则浏览器地址栏的录音指示会一直亮着)
|
|
365
|
-
|
|
366
|
-
1.3.1 修一个下游实测发现的缺陷:**麦克风都接上了,网页壁纸的音谱还在放默认合成流**。
|
|
367
|
-
|
|
368
|
-
- 根因一:web 装配路径压根不读 rt.audioBridge(1.3.0 给媒体源接了这一环,音频这行漏了),注入的频谱到不了 iframe
|
|
369
|
-
- 根因二:音频与媒体两个泵都在**装配时**捕获 driver,而 setAudio()/setMedia() 通常在 mount() 之后才调用(宿主的麦克风 / SSE 通道那时才就绪)——定死 driver 等于后装的源永远不生效。两个泵均改为逐帧选源,撤源后也能落回默认
|
|
370
|
-
- 注入的频谱**不再套 gamma 对比扩展**:那道处理是给内置模拟源的未钳位频段用的,对宿主给的 0..1 真实频谱再乘一遍会把音条整体顶到满格
|
|
371
|
-
|
|
372
|
-
1.3.0 是 1.2.0 的直接延续,继续按下游宿主的反馈补齐(四项):
|
|
373
|
-
|
|
374
|
-
- 新增 mediaSource(urlOrFile):任意视频/图片可直接当壁纸,支持远程 URL 与本地 File(拖拽导入)。本地文件的 objectURL 由库在 destroy()/换源时自动 revoke——不 revoke 就是每换一次壁纸泄漏一个几十 MB 的 blob
|
|
375
|
-
- 类型嗅探:project.type 缺失时按 URL 扩展名判定(正确剥掉签名 URL 的 ?query 与 #hash),认不出才落回 scene。显式声明的 project.type 永远优先,不会被嗅探覆盖
|
|
376
|
-
- 媒体壁纸现在支持 setVolume(此前完全无效:音量只打到 scene 的音频节点)。同时写 volume 与 muted——<video muted> 下只改 volume 一点用都没有;取消静音被自动播放策略拒绝时经 onDiagnostic 如实报出,不静默吞掉
|
|
377
|
-
- MediaSource 扩成 driver 的真实契约(18 字段快照 + 5 个可选控制方法),并由 scene 与 web **共用同一个实例**——此前两侧各自 new 一份模拟源,宿主无从注入。新增 createMediaSource() 只需给已知字段,配色/歌词行/trackIndex 由库补齐并保证颜色是可链式调用的实例
|
|
378
|
-
- 新增 SceneInstance.setMedia() 与 media 控制面(读快照 + 上一曲/下一曲/播放暂停),反向控制直接转发给宿主 driver;宿主更新数据后引擎同帧可见
|
|
379
|
-
- 视频壁纸的频谱自动从 <video> 取,音条能跟着视频里的音乐动;宿主已用 setAudio 显式注入时不接管
|
|
380
|
-
|
|
381
|
-
1.2.0 补的是三个「运行时早就能跑、只是公共库入口没接出来」的缺口(由下游宿主 wallpaperEM 反馈):
|
|
382
|
-
|
|
383
|
-
- video / gif / image 壁纸现在能经 mount() 挂载:新增 Source.mediaEntry() 取址,并补齐媒体路径的库化契约。此前媒体路径不触发 onFirstFrame / onError,即便接上分流,mount() 的 Promise 也会永久挂起——成功不 resolve、失败不 reject
|
|
384
|
-
- 媒体路径改为支持调用方传入的 canvas,并按 CSS 尺寸而非窗口尺寸分配缓冲区(此前嵌入式画布会拿到整窗口大小的 backing store,且画布根本不会被插入 DOM)
|
|
385
|
-
- MountOptions.audio 真正接线(此前是声明了却零引用的死字段),并新增 SceneInstance.setAudio() 供挂载后切换——宿主的频谱通道常在 mount() 之后才就绪
|
|
386
|
-
- 新增 SceneInstance.pushPointer() / pointerLeave(),与整页渲染器的 __wp 同名同签名,下游从整页迁到库时代码不用改
|
|
387
|
-
- 已知边界(当时状态):音频注入只对 scene 生效——网页壁纸走 iframe shim 的另一条通道,1.3.1 已补上;媒体壁纸没有指针概念;MountOptions 的 pointer / media / features 当时未接线(media 已在 1.3.0 接线)
|
|
388
|
-
|
|
389
|
-
1.1.0 的内容(在 1.0.0 之后合入):
|
|
390
|
-
|
|
391
|
-
- 新增外部指针注入通道 __wp.pushPointer / pointerLeave:桌面壁纸窗口收不到鼠标时(如 macOS 下 Finder 桌面窗口吃掉事件),由宿主轮询系统鼠标后推进来。场景与网页两类壁纸共用同一套协议,调用方不必判断类型
|
|
392
|
-
- 网页壁纸接入同一条注入通道:shim 按命中元素合成 DOM 事件(over/out/enter/leave 链完整、click 靠按键边缘合成)。本机库 49 张网页壁纸里 mousemove 24 / click 29 / pointer* 16 张的交互从「完全无反应」变为可用。硬限制:CSS :hover 由浏览器 hit-test 驱动,合成事件点不亮
|
|
393
|
-
- 效果 pass 编译失败清零(七批):转译器修掉整浮混用、宏作用域、向量收窄、科学计数法等形态。全库效果 pass 编译通过率 1653/1873 (88.3%) → 1823/1873 (97.3%),累计 +170。症状是「某个效果静默不出现」——编译失败只 warn 不报错,画面上表现为体积光/音谱/光晕整个缺失
|
|
394
|
-
- 暂停语义补全:暂停必须同时冻结 rAF/定时器与 CSS 动画(合成器驱动的 CSS 动画不受 JS 冻结影响,1444432396 表现为「点了暂停画面照旧」);恢复必须重挂 rAF 挂起项(rAF 自递归的壁纸暂停一次就永久断链,1278092907 表现为「恢复后永久定格」),且只还原我们代为暂停的部分
|
|
395
|
-
- 还原度修复若干:关键帧动画改用真实时钟(此前按目标帧间隔累加,与骨骼两套时基必然发散,30 秒漂 5.5 秒,表现为头发与头不同步、头顶漏模);对象脚本与关键帧动画的坐标空间改为 local 并每帧重算父子变换(此前把脚本返回的 local 值直接写进 world 槽,表现为元素无人操作就自行滑走、被边缘裁切);clipping_mask 引用的隐藏遮罩层现在能正确回读身后画面(此前回退成引用方自身,表现为一块纯白板)
|
|
396
|
-
- 使用说明新增「挂载目标」与「用户属性」两节:网页壁纸必须传容器 div 而非 canvas,以及各类属性该传什么形态的值
|
|
397
|
-
|
|
398
|
-
完整提交历史见 GitHub 仓库;每条修复在提交信息里都写明了症状、根因、影响面数字与验证方式。
|
|
340
|
+
当前版本 1.4.1。这里不再维护逐版本列表:每一次变化的症状、根因、影响面数字与验证方式都写在对应的提交信息里,完整历史见 GitHub 仓库的提交记录与 Release。
|
|
399
341
|
|
|
400
342
|
## 版权与合规
|
|
401
343
|
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -124,6 +124,17 @@ export type MediaSnapshot = {
|
|
|
124
124
|
position: number;
|
|
125
125
|
duration: number;
|
|
126
126
|
hasThumbnail: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* 真实封面图(data URL 或同源 URL)。宿主能拿到系统封面时给这里,
|
|
129
|
+
* 网页壁纸的 `mediaThumbnailChanged(e)` 会直接收到它作为 `e.thumbnail`。
|
|
130
|
+
*
|
|
131
|
+
* 留空时库用 primary/secondary 生成一张渐变占位图 —— 保证语料里
|
|
132
|
+
* `img.src = e.thumbnail` 那类写法不会拿到空串,但显示的不是真专辑封面。
|
|
133
|
+
*
|
|
134
|
+
* 注意场景(WebGL)壁纸不消费图片本体,其脚本只读 `e.hasThumbnail` 与取色,
|
|
135
|
+
* 所以这个字段只对网页壁纸有效。
|
|
136
|
+
*/
|
|
137
|
+
thumbnail?: string;
|
|
127
138
|
/** 封面取色。见 MediaColor 的类型约束 */
|
|
128
139
|
primaryColor: MediaColor;
|
|
129
140
|
secondaryColor: MediaColor;
|
|
@@ -161,6 +172,56 @@ export type MediaSource = {
|
|
|
161
172
|
pause?(): void;
|
|
162
173
|
playPause?(): void;
|
|
163
174
|
};
|
|
175
|
+
/**
|
|
176
|
+
* `createMediaSource(init, controls)` 的 init:宿主通常拿得到的那部分字段,
|
|
177
|
+
* 其余(配色、歌词行、trackIndex)由库补默认值。
|
|
178
|
+
*
|
|
179
|
+
* 类型本体放在这里而不是 api/media-source.ts,是因为只有本文件会被
|
|
180
|
+
* tsconfig.lib-types.json 编成发布的 types.d.ts —— 定义在别处的话
|
|
181
|
+
* 消费方 `import { createMediaSource } from "webwallgl"` 摸不到入参类型。
|
|
182
|
+
*/
|
|
183
|
+
export type MediaSourceInit = {
|
|
184
|
+
hasMedia?: boolean;
|
|
185
|
+
/** 0=停止 1=播放 2=暂停;也接受布尔 playing(true→1、false→2) */
|
|
186
|
+
state?: MediaPlaybackState;
|
|
187
|
+
playing?: boolean;
|
|
188
|
+
title?: string;
|
|
189
|
+
artist?: string;
|
|
190
|
+
album?: string;
|
|
191
|
+
albumArtist?: string;
|
|
192
|
+
/** 秒 */
|
|
193
|
+
position?: number;
|
|
194
|
+
duration?: number;
|
|
195
|
+
hasThumbnail?: boolean;
|
|
196
|
+
/**
|
|
197
|
+
* 真实封面图(data URL 或同源 URL)。给了就直接透给网页壁纸的
|
|
198
|
+
* `mediaThumbnailChanged(e).thumbnail`;不给则库生成渐变占位图。
|
|
199
|
+
* 传了非空值时 hasThumbnail 自动视为 true
|
|
200
|
+
*/
|
|
201
|
+
thumbnail?: string;
|
|
202
|
+
primaryColor?: MediaColorInit;
|
|
203
|
+
secondaryColor?: MediaColorInit;
|
|
204
|
+
tertiaryColor?: MediaColorInit;
|
|
205
|
+
textColor?: MediaColorInit;
|
|
206
|
+
highContrastColor?: MediaColorInit;
|
|
207
|
+
trackIndex?: number;
|
|
208
|
+
/** [秒, 文本] 按时间升序;库按 position 定位当前行 */
|
|
209
|
+
lyrics?: Array<[number, string]>;
|
|
210
|
+
};
|
|
211
|
+
/** `mediaColor()` 与配色字段接受的形态 */
|
|
212
|
+
export type MediaColorInit = number | number[] | {
|
|
213
|
+
x: number;
|
|
214
|
+
y: number;
|
|
215
|
+
z: number;
|
|
216
|
+
} | MediaColor;
|
|
217
|
+
/** `createMediaSource(init, controls)` 的 controls:宿主转发给真实播放器 */
|
|
218
|
+
export type MediaSourceControls = {
|
|
219
|
+
skipNext?(): void;
|
|
220
|
+
skipPrevious?(): void;
|
|
221
|
+
play?(): void;
|
|
222
|
+
pause?(): void;
|
|
223
|
+
playPause?(): void;
|
|
224
|
+
};
|
|
164
225
|
/** 壁纸侧可用的媒体控制面(SceneInstance.media) */
|
|
165
226
|
export type MediaControl = {
|
|
166
227
|
readonly snapshot: MediaSnapshot;
|
|
@@ -183,6 +244,28 @@ export type FeatureFlags = {
|
|
|
183
244
|
/** WE 内置组件对象(本机库 0 个,真出现时按开关处理) */
|
|
184
245
|
components: boolean;
|
|
185
246
|
};
|
|
247
|
+
/** 抗锯齿模式。off=关闭(默认);fxaa=帧末后处理抗锯齿(平滑所有边缘,含纹理
|
|
248
|
+
* alpha 边);msaa2/msaa4=多重采样(只平滑几何边缘,与 WE 的 MSAA 语义一致)。
|
|
249
|
+
* 单选不叠加。 */
|
|
250
|
+
export type AntiAliasingMode = "off" | "fxaa" | "msaa2" | "msaa4";
|
|
251
|
+
/** 粒子质量档。off=不渲染不推进;low/medium 按倍率同时缩 maxcount 上限与
|
|
252
|
+
* 发射率;high=原始配置(默认)。 */
|
|
253
|
+
export type ParticleQuality = "off" | "low" | "medium" | "high";
|
|
254
|
+
/** 后处理质量档。off=图层效果链直通 + 跳过整屏后期层 + 关内置 Bloom;
|
|
255
|
+
* low/medium/high=效果链开启,仅效果链 FBO 分辨率预算不同。 */
|
|
256
|
+
export type PostQuality = "off" | "low" | "medium" | "high";
|
|
257
|
+
/** 质量设置(缺省键按默认值补全:AA off / 粒子 high / 后处理 high) */
|
|
258
|
+
export type QualityOptions = {
|
|
259
|
+
antiAliasing?: AntiAliasingMode;
|
|
260
|
+
particles?: ParticleQuality;
|
|
261
|
+
postProcessing?: PostQuality;
|
|
262
|
+
};
|
|
263
|
+
/** 三项齐全的规范化结果(getQuality / normalizeQuality 的输出) */
|
|
264
|
+
export type ResolvedQuality = {
|
|
265
|
+
antiAliasing: AntiAliasingMode;
|
|
266
|
+
particles: ParticleQuality;
|
|
267
|
+
postProcessing: PostQuality;
|
|
268
|
+
};
|
|
186
269
|
/** 场景装配完成后的基本信息 */
|
|
187
270
|
export type SceneInfo = {
|
|
188
271
|
/** 场景逻辑分辨率(scene.json 的 general.orthogonalprojection) */
|
|
@@ -208,8 +291,11 @@ export type MountOptions = {
|
|
|
208
291
|
/** 适配模式。默认 "cover" */
|
|
209
292
|
fit?: Fit;
|
|
210
293
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
294
|
+
* 渲染分辨率 DPR,决定 backing store = CSS 像素 × DPR。
|
|
295
|
+
* - 0 或不传(默认):自动跟随设备 devicePixelRatio,Retina/HiDPI 屏原生清晰;
|
|
296
|
+
* - 正数:目标 DPR,**允许高于设备上报值**(某些壁纸宿主 WKWebView 把
|
|
297
|
+
* devicePixelRatio 报成 1,传 2 仍按 2 超采样到物理分辨率);
|
|
298
|
+
* 物理最长边封顶 4096 防爆显存,超出等比回收。调低(如 1)可省显存。
|
|
213
299
|
*/
|
|
214
300
|
renderDpr?: number;
|
|
215
301
|
/** 帧率上限。默认 60。渲染循环跳过比目标更快的帧,降低 GPU 占用 */
|
|
@@ -237,6 +323,12 @@ export type MountOptions = {
|
|
|
237
323
|
media?: MediaSource | null;
|
|
238
324
|
/** 渲染开关(调试用)。**当前未接线** */
|
|
239
325
|
features?: Partial<FeatureFlags>;
|
|
326
|
+
/**
|
|
327
|
+
* 渲染质量设置(抗锯齿/粒子/后处理档位,见 quality.ts)。
|
|
328
|
+
* 缺省 = 全默认(AA off、粒子 high、后处理 high),与引入前的行为一致。
|
|
329
|
+
* 挂载后可用 `SceneInstance.setQuality()` 热调。
|
|
330
|
+
*/
|
|
331
|
+
quality?: QualityOptions;
|
|
240
332
|
/** 诊断回调。替代旧的 GET /diag 上报 */
|
|
241
333
|
onDiagnostic?: SceneEvents["diagnostic"];
|
|
242
334
|
/** 装配或渲染失败。库不自带降级页,由调用方决定怎么兜 */
|
|
@@ -260,6 +352,14 @@ export type SceneInstance = {
|
|
|
260
352
|
setVolume(volume: number): void;
|
|
261
353
|
/** 改 DPR 需重建画布尺寸,内部自动重挂当前场景 */
|
|
262
354
|
setRenderDpr(dpr: number): void;
|
|
355
|
+
/**
|
|
356
|
+
* 渲染质量设置热更(对标 WE 客户端的性能选项:抗锯齿/粒子/后处理档位)。
|
|
357
|
+
* 部分更新:只传要改的键。**就地生效,不重挂载**(与 setRenderDpr 不同)。
|
|
358
|
+
* 换场景/load() 之后保持(与 setAudio 同纪律,合并进挂载选项)。
|
|
359
|
+
*/
|
|
360
|
+
setQuality(patch: QualityOptions): void;
|
|
361
|
+
/** 当前生效的质量设置(三项齐全,缺省键已按默认值补全) */
|
|
362
|
+
getQuality(): ResolvedQuality;
|
|
263
363
|
/**
|
|
264
364
|
* 用户属性热更新。就地改属性表 / 效果常量 / 脚本沙箱,
|
|
265
365
|
* **不重新拉取与解析 scene.pkg**(百 MB 包重挂是可感知的卡顿)。
|
|
@@ -300,11 +400,13 @@ export type SceneInstance = {
|
|
|
300
400
|
* @param u 归一化横坐标 0..1(相对画布左缘)
|
|
301
401
|
* @param v 归一化纵坐标 0..1(相对画布上缘,y 向下)
|
|
302
402
|
* @param buttons 按键位掩码,同 MouseEvent.buttons;只有 bit0(左键)被消费
|
|
403
|
+
* @param mods 修饰键位掩码:bit0 ctrl / bit1 shift / bit2 alt / bit3 meta。
|
|
404
|
+
* 省略等于 0。只有 web 壁纸消费(合成事件的 ctrlKey 等字段)
|
|
303
405
|
*
|
|
304
406
|
* 与 canvas 自身的 DOM 指针监听并存,谁后写谁赢。scene 与 web 壁纸都生效,
|
|
305
407
|
* 媒体壁纸(video/gif/image)没有指针概念,调用静默无效。
|
|
306
408
|
*/
|
|
307
|
-
pushPointer(u: number, v: number, buttons?: number): void;
|
|
409
|
+
pushPointer(u: number, v: number, buttons?: number, mods?: number): void;
|
|
308
410
|
/**
|
|
309
411
|
* 外部指针离开本窗口(鼠标移到了别的显示器)。
|
|
310
412
|
*
|
|
@@ -312,14 +414,39 @@ export type SceneInstance = {
|
|
|
312
414
|
* 视差弹回中心,画面明显抽一下。语义与 DOM 的 mouseleave 一致。
|
|
313
415
|
*/
|
|
314
416
|
pointerLeave(): void;
|
|
417
|
+
/**
|
|
418
|
+
* 外部滚轮注入:把宿主捕获的滚轮 / 触摸板手势推进壁纸。
|
|
419
|
+
*
|
|
420
|
+
* **只有 web 壁纸生效。** scene 壁纸静默无效不是遗漏:WE 的场景脚本沙箱
|
|
421
|
+
* 不暴露任何滚轮 API,实测 194 张场景壁纸零消费(场景包里的 `scroll`
|
|
422
|
+
* 全都是纹理滚动 shader 的 `g_ScrollSpeed`,与鼠标无关)。
|
|
423
|
+
*
|
|
424
|
+
* @param dx 横向滚动量,正 = 内容向右(与 WheelEvent.deltaX 同向同量级)
|
|
425
|
+
* @param dy 纵向滚动量,正 = 内容向下(与 WheelEvent.deltaY 同向;
|
|
426
|
+
* macOS 原生 NSEvent.scrollingDeltaY 是**反向**的,宿主需取反)
|
|
427
|
+
* @param mode 同 WheelEvent.deltaMode:0 像素 / 1 行 / 2 页。触摸板恒为 0
|
|
428
|
+
* @param mods 修饰键位掩码,bit0 ctrl。**触摸板双指捏合应映射成 ctrl + 滚轮**
|
|
429
|
+
* —— 浏览器就是这样把 macOS 的 magnify 手势喂给网页的,
|
|
430
|
+
* OrbitControls / pano2vr 一族都靠 `event.ctrlKey` 区分缩放与滚动
|
|
431
|
+
*
|
|
432
|
+
* 位置沿用最后一次 `pushPointer()` 的坐标(滚轮事件本身不带位置)。
|
|
433
|
+
*/
|
|
434
|
+
pushWheel(dx: number, dy: number, mode?: number, mods?: number): void;
|
|
315
435
|
/** 换场景,复用同一 canvas 与 WebGL 上下文 */
|
|
316
436
|
load(source: Source): Promise<void>;
|
|
317
437
|
/** 释放 GL/视频/音频资源,保留配置(显示器睡眠等场景) */
|
|
318
438
|
release(): void;
|
|
319
439
|
/** 用保留的配置重建 */
|
|
320
440
|
restore(): void;
|
|
321
|
-
/**
|
|
322
|
-
|
|
441
|
+
/**
|
|
442
|
+
* 彻底销毁:解绑事件监听、释放全部资源,之后不可再用。
|
|
443
|
+
* `releasePkgCache: true` 连带淘汰本实例 source 的 scene.pkg 解析缓存 ——
|
|
444
|
+
* 缓存默认跨实例保留(同壁纸重挂不重新下载),宿主"销毁即放弃"的语义
|
|
445
|
+
* (如桌面壁纸逐张切换)需要显式声明,否则旧包会压在缓存里不落内存。
|
|
446
|
+
*/
|
|
447
|
+
destroy(opts?: {
|
|
448
|
+
releasePkgCache?: boolean;
|
|
449
|
+
}): void;
|
|
323
450
|
readonly stats: FrameStats;
|
|
324
451
|
readonly info: SceneInfo | null;
|
|
325
452
|
/** 事件订阅,返回取消函数 */
|