open-sea-skin-dsh 2.0.6 → 2.0.7
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/CHANGELOG.md +18 -0
- package/README.en.md +6 -12
- package/README.md +6 -12
- package/README.zh.md +3 -3
- package/native-dist/ocean.js +175 -16
- package/package.json +1 -1
- package/plugin/client.js +132 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
# 2.0.7
|
|
2
|
+
|
|
3
|
+
## Added — 「性能」分组:帧预算的四个开关
|
|
4
|
+
|
|
5
|
+
- 新增分组「性能」(在「毛玻璃」与「高级」之间):帧调度器、遮挡暂停、WebGL2 自适应;「高级」里追加窗口缩放防抖与毛玻璃刷新方式。
|
|
6
|
+
- 帧调度器把逻辑从渲染节拍里拆出来:帧率上限低于屏幕刷新率时,不再每帧空转星空、船队和相机。
|
|
7
|
+
- 遮挡暂停在窗口被盖住 / 切到后台时停掉渲染循环(或降到 10 fps),回到前台立即续上,时钟不跳。
|
|
8
|
+
- WebGL2 回退不再硬编码 1.0 像素比 / 30 fps / 0.6 比例,改用与画质档一致的自适应,并在降档后锁 30 秒防抖。
|
|
9
|
+
- 窗口缩放防抖 150ms:拖动窗口边缘时先拉伸旧画面,停手后再按新尺寸重建,避免连串重建。
|
|
10
|
+
- 毛玻璃刷新方式:实时 / 静态(保留底色、停掉每帧取样)/ 关闭。
|
|
11
|
+
|
|
12
|
+
## Changed — 渲染比例改为探针升档
|
|
13
|
+
|
|
14
|
+
- 旧判据 `fps > TARGET_FPS + 6` 在帧率上限附近永远不可达(读数被上限钳住),比例呈现「只会降不会升」的棘轮。
|
|
15
|
+
- 现在每个窗口向上探 3%,用下一个窗口裁决;保不住的退回比例并把冷却翻倍(4 → 8 → 16 → 32 秒)。
|
|
16
|
+
- 保住的步进后静默 24 秒再做下一次,且不追最后 1.5% 的余量——每次裁决都要重分配绘制缓冲,那一下就是一帧闪烁,间隔太短会被读成卡顿。
|
|
17
|
+
- 切换画质档会清空在飞的探针。
|
|
18
|
+
|
|
1
19
|
# 2.0.6
|
|
2
20
|
|
|
3
21
|
## Fixed — 浏览器模块 id 改为 npm 包名
|
package/README.en.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Open Sea Skin · Lab (2.0.
|
|
1
|
+
# Open Sea Skin · Lab (2.0.7)
|
|
2
2
|
|
|
3
3
|
**A living ocean skin** for DeepSeek Harness Desktop and Web: waves that follow your session load, a sky driven by real ephemeris data, a star field built from a real catalogue, and boats that are your running subagents.
|
|
4
4
|
|
|
5
|
-
  
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
This project is a **rebuilt fork (Lab branch)** of **[d-dev0101/open-sea-skin](https://github.com/d-dev0101/open-sea-skin)** (author **d-dev0101**, MIT licence) at version **1.2.7**. It is **not** an official upstream release and is not maintained by upstream.
|
|
12
12
|
|
|
13
|
-
It keeps upstream's rendering foundation (three.js WebGPU + TSL ocean, sky, clouds, glass approach) and rebuilds the wave driver, astronomical sky, star field, boat fleet and the whole settings panel on top of it, up to **2.0.
|
|
13
|
+
It keeps upstream's rendering foundation (three.js WebGPU + TSL ocean, sky, clouds, glass approach) and rebuilds the wave driver, astronomical sky, star field, boat fleet and the whole settings panel on top of it, up to **2.0.7**. All current code, shaders, panel and tests are maintained here — please file issues against **this** repository, not upstream.
|
|
14
14
|
|
|
15
15
|
- Upstream: [d-dev0101/open-sea-skin](https://github.com/d-dev0101/open-sea-skin) · fork point `v1.2.7`
|
|
16
16
|
- Upstream licence: MIT, full text in [`LICENSE`](LICENSE); required third-party notices in [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)
|
|
17
17
|
- Why the fork exists and how to fall back: [`docs/FORK.md`](docs/FORK.md)
|
|
18
|
-
- Current version: **2.0.
|
|
18
|
+
- Current version: **2.0.7** (tag `v2.0.7`). 2.0.0 stays the frozen baseline and its tag still resolves to the source; the 2.0.0–2.0.3 releases are off the shelf, so only the newest installable package is published
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -58,12 +58,6 @@ Or install straight from GitHub (builds from this source tree):
|
|
|
58
58
|
dsh plugin add github:BeckChiang/open-sea-skin
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
To skip the source build on your machine, use the prebuilt tarball from the Release (no `allowBuilds` approval needed):
|
|
62
|
-
|
|
63
|
-
```powershell
|
|
64
|
-
dsh plugin add https://github.com/BeckChiang/open-sea-skin/releases/download/v2.0.4/open-sea-skin-2.0.4.tgz
|
|
65
|
-
```
|
|
66
|
-
|
|
67
61
|
### Option 2: local link (for development)
|
|
68
62
|
|
|
69
63
|
1. Clone into the DSH plugin directory:
|
|
@@ -83,8 +77,8 @@ dsh plugin add https://github.com/BeckChiang/open-sea-skin/releases/download/v2.
|
|
|
83
77
|
**Verify**: the console should print
|
|
84
78
|
|
|
85
79
|
```text
|
|
86
|
-
[open-sea-skin] client lab-2.0.
|
|
87
|
-
[open-sea-skin] ocean lab-2.0.
|
|
80
|
+
[open-sea-skin] client lab-2.0.7
|
|
81
|
+
[open-sea-skin] ocean lab-2.0.7
|
|
88
82
|
```
|
|
89
83
|
|
|
90
84
|
---
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Open Sea Skin · Lab(2.0.
|
|
1
|
+
# Open Sea Skin · Lab(2.0.7)
|
|
2
2
|
|
|
3
3
|
**实时海洋皮肤** —— 给 DeepSeek Harness 桌面版 / Web 用的一层会呼吸的海面:浪跟着你的会话负载起伏,天光按真实星历走,星空是真的星表,船是你正在跑的子智能体。
|
|
4
4
|
|
|
5
|
-
  
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
本项目是基于 **[d-dev0101/open-sea-skin](https://github.com/d-dev0101/open-sea-skin)**(作者 **d-dev0101**,MIT 许可)**1.2.7** 版本**重新构建的 Lab 分支(fork)**,**不是上游官方版本**,也不由上游维护。
|
|
12
12
|
|
|
13
|
-
它沿用了上游的渲染底座(three.js WebGPU + TSL 的海面、天空、云与毛玻璃思路),在底座之上重做了海浪驱动、天文日照、星空、船队与整块设置面板,演进到 **2.0.
|
|
13
|
+
它沿用了上游的渲染底座(three.js WebGPU + TSL 的海面、天空、云与毛玻璃思路),在底座之上重做了海浪驱动、天文日照、星空、船队与整块设置面板,演进到 **2.0.7**。原生代码、着色器、面板与测试都由本仓库维护,出现的问题请提到**本仓库**而不是上游。
|
|
14
14
|
|
|
15
15
|
- 上游项目:[d-dev0101/open-sea-skin](https://github.com/d-dev0101/open-sea-skin) · 分叉起点:`v1.2.7`
|
|
16
16
|
- 上游许可:MIT,全文见 [`LICENSE`](LICENSE);必须保留的第三方署名见 [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)
|
|
17
17
|
- 为什么分叉、改了什么、怎么回退上游:[`docs/FORK.md`](docs/FORK.md)
|
|
18
|
-
- 当前版本:**2.0.
|
|
18
|
+
- 当前版本:**2.0.7**(tag `v2.0.7`)。2.0.0 是冻结基线,tag 仍可检出源码;2.0.0–2.0.3 的发布包与 Release 已下架,只保留最新一版的可安装包
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -62,12 +62,6 @@ dsh plugin add open-sea-skin-dsh
|
|
|
62
62
|
dsh plugin add github:BeckChiang/open-sea-skin
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
不想让本机走源码构建的话,用 Release 上的预构建包(免 `allowBuilds` 授权):
|
|
66
|
-
|
|
67
|
-
```powershell
|
|
68
|
-
dsh plugin add https://github.com/BeckChiang/open-sea-skin/releases/download/v2.0.4/open-sea-skin-2.0.4.tgz
|
|
69
|
-
```
|
|
70
|
-
|
|
71
65
|
### 方式二:本地 link(改代码用)
|
|
72
66
|
|
|
73
67
|
1. 把仓库放到 DSH 插件目录:
|
|
@@ -87,8 +81,8 @@ dsh plugin add https://github.com/BeckChiang/open-sea-skin/releases/download/v2.
|
|
|
87
81
|
**确认生效**:控制台第一行会出现
|
|
88
82
|
|
|
89
83
|
```text
|
|
90
|
-
[open-sea-skin] client lab-2.0.
|
|
91
|
-
[open-sea-skin] ocean lab-2.0.
|
|
84
|
+
[open-sea-skin] client lab-2.0.7
|
|
85
|
+
[open-sea-skin] ocean lab-2.0.7
|
|
92
86
|
```
|
|
93
87
|
|
|
94
88
|
---
|
package/README.zh.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Open Sea Skin · Lab 分支说明(2.0.
|
|
1
|
+
# Open Sea Skin · Lab 分支说明(2.0.7)
|
|
2
2
|
|
|
3
3
|
这是 [open-sea-skin](https://github.com/d-dev0101/open-sea-skin)(MIT,作者 d-dev0101)1.2.7 的**重新构建版(fork)**,装在 DSH 桌面版的 `desktop` profile 里。首页与安装说明见 [`README.md`](README.md),本文是该版本的技术说明。
|
|
4
4
|
|
|
5
5
|
上游只提供「海面 + 夕阳 + 玻璃界面」;本分支在它的地基上重做了五块:**负载驱动的波浪**、**真实天文的天色与星空**、**跟着会话变化的船队**、**可逐项取舍的性能开关**、**可折叠且带解释的设置面板**。
|
|
6
6
|
|
|
7
7
|
- 上游版本:1.2.7
|
|
8
|
-
- 本分支版本:**2.0.
|
|
8
|
+
- 本分支版本:**2.0.7**(2.0.0 是冻结基线;本文档是该版本的完整说明。2.0.0–2.0.3 的发布包已下架)
|
|
9
9
|
- 许可:MIT(见 `LICENSE`);上游必须保留的署名见 `THIRD_PARTY_NOTICES.md`
|
|
10
10
|
- 开发位置:`C:\Users\<你>\DSH\plugins\open-sea-skin-lab`,profile 用 `link:` 安装
|
|
11
11
|
- 默认档:**画质「省电」(0.85× 像素比)+ 30 fps 上限**,夜间核显占用多数时候低于 20%
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
```
|
|
45
45
|
3. 重启桌面端或按 F5 刷新窗口。
|
|
46
46
|
|
|
47
|
-
**确认生效**:控制台第一行有 `[open-sea-skin] client lab-2.0.
|
|
47
|
+
**确认生效**:控制台第一行有 `[open-sea-skin] client lab-2.0.7` 与 `[open-sea-skin] ocean lab-2.0.7`。
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
package/native-dist/ocean.js
CHANGED
|
@@ -871,6 +871,17 @@ function applySunMessage(d) {
|
|
|
871
871
|
if (typeof d.quality === 'string') applyQuality(d.quality);
|
|
872
872
|
// 1.5.27: frame rate is a budget dial of its own, on top of the resolution one
|
|
873
873
|
if (Number.isFinite(Number(d.fps))) applyFpsCap(Number(d.fps));
|
|
874
|
+
// 2.0.7 performance switches (panel: "性能" group + advanced rows)
|
|
875
|
+
if (d.perfScheduler !== undefined) perfScheduler = d.perfScheduler !== false;
|
|
876
|
+
if (d.webgl2Adaptive !== undefined) webgl2Adaptive = d.webgl2Adaptive !== false;
|
|
877
|
+
if (d.resizeDebounce !== undefined) resizeDebounce = d.resizeDebounce !== false;
|
|
878
|
+
if (typeof d.pauseWhenObscured === 'string' && PAUSE_MODES.includes(d.pauseWhenObscured)) {
|
|
879
|
+
const modeChanged = pauseMode !== d.pauseWhenObscured;
|
|
880
|
+
pauseMode = d.pauseWhenObscured;
|
|
881
|
+
// the switch may flip while the window is already in the background, so the
|
|
882
|
+
// loop state has to be re-evaluated now, not on the next occlude event
|
|
883
|
+
if (modeChanged && loopStarted && renderer) applyObscuredLoop();
|
|
884
|
+
}
|
|
874
885
|
const t = Number(d.t);
|
|
875
886
|
if (sunMode === 'manual') {
|
|
876
887
|
manualTimeOfDay = d.auto !== true;
|
|
@@ -1400,6 +1411,13 @@ if (SKIN) {
|
|
|
1400
1411
|
// daylight is carried by oss-sun now; a sea tick must never move the sun
|
|
1401
1412
|
}
|
|
1402
1413
|
if (d && d.type === 'oss-sun') applySunMessage(d);
|
|
1414
|
+
if (d && d.type === 'oss-active') {
|
|
1415
|
+
// The panel watches its own window: a covered browser window never tells
|
|
1416
|
+
// the iframe (it is not a tab) and never takes focus, so this is the only
|
|
1417
|
+
// signal that the skin went to the background.
|
|
1418
|
+
obscured = d.active === false;
|
|
1419
|
+
applyObscuredLoop();
|
|
1420
|
+
}
|
|
1403
1421
|
if (d && d.type === 'oss-glass') applyGlassMessage(d);
|
|
1404
1422
|
if (d && d.type === 'oss-boats') applyBoatsMessage(d);
|
|
1405
1423
|
if (d && d.type === 'oss-fleet') {
|
|
@@ -1503,6 +1521,35 @@ let renderScale = 1;
|
|
|
1503
1521
|
// contract §3.4: the frosted glass caps the adaptive scale, it never raises it
|
|
1504
1522
|
let SCALE_CAP = 1;
|
|
1505
1523
|
|
|
1524
|
+
// 2.0.7 performance switches. Every one of them defaults to the shape the user
|
|
1525
|
+
// already knows, so a profile written by 2.0.6 keeps the 2.0.6 frame.
|
|
1526
|
+
let perfScheduler = true;
|
|
1527
|
+
let pauseMode = 'pause'; // pause | throttle | off
|
|
1528
|
+
let webgl2Adaptive = true;
|
|
1529
|
+
let resizeDebounce = true;
|
|
1530
|
+
let webglFallback = false;
|
|
1531
|
+
let webglLatchUntil = 0;
|
|
1532
|
+
let obscured = false;
|
|
1533
|
+
let nextDue = 0;
|
|
1534
|
+
let currentQuality = 'balanced';
|
|
1535
|
+
|
|
1536
|
+
// 2.0.7: under a frame cap the meter can never climb above the cap, so "did the
|
|
1537
|
+
// GPU have headroom?" cannot be read from fps alone. The governor therefore
|
|
1538
|
+
// spends one window at a higher scale and watches whether that window costs
|
|
1539
|
+
// frames; a failed probe walks the scale back and doubles the wait before the
|
|
1540
|
+
// next attempt, so a machine that is genuinely at its limit is not prodded.
|
|
1541
|
+
// Every verdict reallocates the drawing buffer (setPixelRatio + setSize), which
|
|
1542
|
+
// reads as a one-frame blink on the water. That blink is why a proven step is
|
|
1543
|
+
// followed by a long hold: the governor climbs in a few visible steps and then
|
|
1544
|
+
// stops, instead of pulsing the sharpness every couple of seconds.
|
|
1545
|
+
let probeState = 0; // 0 = idle, 1 = one window raised, waiting for the verdict
|
|
1546
|
+
let probeBackoff = 4; // windows to wait after a failed probe (4 → 64 s)
|
|
1547
|
+
let probeBaseScale = 1;
|
|
1548
|
+
const PROBE_STEP = 1.03;
|
|
1549
|
+
const PROBE_BACKOFF_MAX = 32;
|
|
1550
|
+
const PROBE_HOLD = 12; // 24 s of quiet after a step is kept
|
|
1551
|
+
const PROBE_CAP_EPS = 0.015; // do not hunt the last 1.5 % of the cap
|
|
1552
|
+
|
|
1506
1553
|
function applyResolutionScale(scale) {
|
|
1507
1554
|
renderer.setPixelRatio(BASE_PIXEL_RATIO * scale);
|
|
1508
1555
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
@@ -1520,6 +1567,7 @@ const QUALITY_PRESETS = {
|
|
|
1520
1567
|
// 60 fps, and every frame is a full-screen fill: halving the rate halves the
|
|
1521
1568
|
// GPU time. SKIN defaults to 30; the standalone demo keeps its old cap.
|
|
1522
1569
|
const FPS_OPTIONS = [30, 60, 90, 120];
|
|
1570
|
+
const PAUSE_MODES = ['pause', 'throttle', 'off'];
|
|
1523
1571
|
let fpsCap = SKIN ? 30 : 60;
|
|
1524
1572
|
|
|
1525
1573
|
function applyFpsCap(cap) {
|
|
@@ -1541,6 +1589,10 @@ function applyFpsCap(cap) {
|
|
|
1541
1589
|
function applyQuality(level) {
|
|
1542
1590
|
const preset = QUALITY_PRESETS[level];
|
|
1543
1591
|
if (!preset || !renderer) return;
|
|
1592
|
+
currentQuality = level;
|
|
1593
|
+
// a quality switch re-clamps the scale: any probe in flight is meaningless now
|
|
1594
|
+
probeState = 0;
|
|
1595
|
+
probeBackoff = 4;
|
|
1544
1596
|
PIXEL_RATIO_CAP = REDUCED_MOTION ? Math.min(preset.pixelRatio, 0.9) : preset.pixelRatio;
|
|
1545
1597
|
BASE_PIXEL_RATIO = Math.min(window.devicePixelRatio || 1, PIXEL_RATIO_CAP);
|
|
1546
1598
|
TARGET_FPS = REDUCED_MOTION ? 18 : preset.targetFps;
|
|
@@ -1551,7 +1603,40 @@ function applyQuality(level) {
|
|
|
1551
1603
|
applyFpsCap(fpsCap);
|
|
1552
1604
|
}
|
|
1553
1605
|
|
|
1606
|
+
// 2.0.7: one place that decides whether the loop should be running. `throttle`
|
|
1607
|
+
// keeps the loop alive and lets the gate in animate() stretch the step to 10 fps,
|
|
1608
|
+
// so the sky keeps advancing and coming back does not jump; `pause` stops it
|
|
1609
|
+
// outright, which is what actually drops the GPU to idle when DSH is covered.
|
|
1610
|
+
function applyObscuredLoop() {
|
|
1611
|
+
if (!loopStarted || !renderer) return;
|
|
1612
|
+
// Never pause before the first frame is on screen: the loader is only taken
|
|
1613
|
+
// down by a rendered frame, so a pause here would read as a broken skin.
|
|
1614
|
+
if (!firstFrameShown) return;
|
|
1615
|
+
if (obscured && pauseMode === 'pause') {
|
|
1616
|
+
renderer.setAnimationLoop(null);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (document.hidden) return;
|
|
1620
|
+
lastFrameTime = performance.now();
|
|
1621
|
+
lastRenderAt = 0;
|
|
1622
|
+
nextDue = 0;
|
|
1623
|
+
renderer.setAnimationLoop(animate);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1554
1626
|
function animate(now) {
|
|
1627
|
+
// 2.0.7: the whole tick is gated, not just the draw. On a 144 Hz panel the
|
|
1628
|
+
// star billboards, the orbit controls and the fleet used to run 144 times a
|
|
1629
|
+
// second so that 30 frames could be presented. The due time is advanced in
|
|
1630
|
+
// whole frame steps (nextDue += step) instead of being snapped to `now`, and
|
|
1631
|
+
// that matters: a 30 Hz budget sampled on a 144 Hz vsync grid lands on 6.9
|
|
1632
|
+
// ticks, and resetting the anchor to `now` turned that into an alternating
|
|
1633
|
+
// 2/1 cadence. A long stall resyncs instead of trying to catch up in a burst.
|
|
1634
|
+
if (perfScheduler) {
|
|
1635
|
+
const step = obscured && pauseMode === 'throttle' ? Math.max(FRAME_MS, 100) : FRAME_MS;
|
|
1636
|
+
if (now + 0.5 < nextDue) return;
|
|
1637
|
+
if (now - nextDue > step * 4) nextDue = now;
|
|
1638
|
+
nextDue += step;
|
|
1639
|
+
}
|
|
1555
1640
|
// high-resolution timestamp -> delta, clamped so hitches never cause jumps
|
|
1556
1641
|
const dt = Math.min((now - lastFrameTime) / 1000, 0.1);
|
|
1557
1642
|
lastFrameTime = now;
|
|
@@ -1588,8 +1673,11 @@ function animate(now) {
|
|
|
1588
1673
|
}
|
|
1589
1674
|
}
|
|
1590
1675
|
|
|
1591
|
-
// frame cap:
|
|
1592
|
-
|
|
1676
|
+
// frame cap: with the smart scheduler the work above is already gated, so
|
|
1677
|
+
// only the legacy path still needs the mid-tick early return
|
|
1678
|
+
if (!perfScheduler) {
|
|
1679
|
+
if (now - lastRenderAt < FRAME_MS - 0.5) return;
|
|
1680
|
+
}
|
|
1593
1681
|
lastRenderAt = now;
|
|
1594
1682
|
|
|
1595
1683
|
postProcessing.render();
|
|
@@ -1632,10 +1720,51 @@ function animate(now) {
|
|
|
1632
1720
|
renderScale = Math.max(MIN_SCALE, +(renderScale * 0.97).toFixed(3));
|
|
1633
1721
|
applyResolutionScale(renderScale);
|
|
1634
1722
|
adaptHold = 4;
|
|
1635
|
-
|
|
1723
|
+
// a real drop cancels any running probe and resets the backoff: next
|
|
1724
|
+
// time the frame is healthy we start probing from the near end again
|
|
1725
|
+
probeState = 0;
|
|
1726
|
+
probeBackoff = 4;
|
|
1727
|
+
// 2.0.7: a WebGL2 post chain is dearer than the WebGPU one, so once the
|
|
1728
|
+
// governor has walked down there it is not allowed to walk straight
|
|
1729
|
+
// back up and oscillate. The latch only gates the climb, never the drop.
|
|
1730
|
+
if (webglFallback) webglLatchUntil = now + 30000;
|
|
1731
|
+
} else if (
|
|
1732
|
+
fpsNow > TARGET_FPS + 6
|
|
1733
|
+
&& renderScale < SCALE_CAP
|
|
1734
|
+
&& (!webglFallback || now > webglLatchUntil)
|
|
1735
|
+
) {
|
|
1736
|
+
// uncapped headroom: the meter itself proved there is room
|
|
1636
1737
|
renderScale = Math.min(SCALE_CAP, +(renderScale * 1.015).toFixed(3));
|
|
1637
1738
|
applyResolutionScale(renderScale);
|
|
1638
1739
|
adaptHold = 4;
|
|
1740
|
+
probeState = 0;
|
|
1741
|
+
probeBackoff = 4;
|
|
1742
|
+
} else if (probeState === 1) {
|
|
1743
|
+
// verdict on the probe we started last window: holding the target under
|
|
1744
|
+
// a cap means the extra pixels were free, so the step is kept
|
|
1745
|
+
if (fpsNow >= TARGET_FPS - 4) {
|
|
1746
|
+
probeState = 0;
|
|
1747
|
+
probeBackoff = 4;
|
|
1748
|
+
adaptHold = PROBE_HOLD;
|
|
1749
|
+
} else {
|
|
1750
|
+
renderScale = probeBaseScale;
|
|
1751
|
+
applyResolutionScale(renderScale);
|
|
1752
|
+
probeState = 0;
|
|
1753
|
+
probeBackoff = Math.min(PROBE_BACKOFF_MAX, probeBackoff * 2);
|
|
1754
|
+
adaptHold = probeBackoff;
|
|
1755
|
+
}
|
|
1756
|
+
} else if (
|
|
1757
|
+
fpsNow >= TARGET_FPS - 2
|
|
1758
|
+
&& renderScale < SCALE_CAP - PROBE_CAP_EPS
|
|
1759
|
+
&& (!webglFallback || now > webglLatchUntil)
|
|
1760
|
+
) {
|
|
1761
|
+
probeBaseScale = renderScale;
|
|
1762
|
+
renderScale = Math.min(SCALE_CAP, +(renderScale * PROBE_STEP).toFixed(3));
|
|
1763
|
+
applyResolutionScale(renderScale);
|
|
1764
|
+
probeState = 1;
|
|
1765
|
+
adaptHold = 1; // judge it in the very next window
|
|
1766
|
+
} else {
|
|
1767
|
+
adaptHold = 1; // between the dead bands: look again in two seconds
|
|
1639
1768
|
}
|
|
1640
1769
|
}
|
|
1641
1770
|
}
|
|
@@ -1664,7 +1793,7 @@ function animate(now) {
|
|
|
1664
1793
|
}
|
|
1665
1794
|
}
|
|
1666
1795
|
|
|
1667
|
-
const OCEAN_BUILD = 'lab-2.0.
|
|
1796
|
+
const OCEAN_BUILD = 'lab-2.0.7';
|
|
1668
1797
|
|
|
1669
1798
|
// How often the frame reports its own state to the host page.
|
|
1670
1799
|
const STATE_POST_MS = 400;
|
|
@@ -1853,10 +1982,30 @@ async function boot() {
|
|
|
1853
1982
|
}
|
|
1854
1983
|
|
|
1855
1984
|
// --- lifecycle: resize ---------------------------------------------------
|
|
1856
|
-
|
|
1985
|
+
const rebuildForResize = () => {
|
|
1857
1986
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
1858
1987
|
camera.updateProjectionMatrix();
|
|
1859
1988
|
applyResolutionScale(renderScale);
|
|
1989
|
+
};
|
|
1990
|
+
// 2.0.7: dragging a window edge fires resize every pixel, and every one of
|
|
1991
|
+
// them reallocated the render targets. The old canvas is stretched over the
|
|
1992
|
+
// viewport while the drag is in flight (no gap, no black band) and the
|
|
1993
|
+
// targets are rebuilt once, 150 ms after the drag settles.
|
|
1994
|
+
let resizeTimer = 0;
|
|
1995
|
+
window.addEventListener('resize', () => {
|
|
1996
|
+
if (!resizeDebounce) {
|
|
1997
|
+
rebuildForResize();
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
if (!resizeTimer && renderer && renderer.domElement && renderer.domElement.style) {
|
|
2001
|
+
renderer.domElement.style.width = '100%';
|
|
2002
|
+
renderer.domElement.style.height = '100%';
|
|
2003
|
+
}
|
|
2004
|
+
if (resizeTimer) window.clearTimeout(resizeTimer);
|
|
2005
|
+
resizeTimer = window.setTimeout(() => {
|
|
2006
|
+
resizeTimer = 0;
|
|
2007
|
+
rebuildForResize();
|
|
2008
|
+
}, 150);
|
|
1860
2009
|
});
|
|
1861
2010
|
|
|
1862
2011
|
// --- lifecycle: pause when the tab is hidden -------------------------------
|
|
@@ -1864,11 +2013,11 @@ async function boot() {
|
|
|
1864
2013
|
if (!loopStarted) return;
|
|
1865
2014
|
if (document.hidden) {
|
|
1866
2015
|
renderer.setAnimationLoop(null);
|
|
1867
|
-
|
|
1868
|
-
lastFrameTime = performance.now();
|
|
1869
|
-
lastRenderAt = 0;
|
|
1870
|
-
renderer.setAnimationLoop(animate);
|
|
2016
|
+
return;
|
|
1871
2017
|
}
|
|
2018
|
+
// a minimized window is hidden first and covered second; coming back
|
|
2019
|
+
// re-runs the same decision the panel's activity signal uses
|
|
2020
|
+
applyObscuredLoop();
|
|
1872
2021
|
});
|
|
1873
2022
|
|
|
1874
2023
|
// --- initialize the GPU and start rendering --------------------------------
|
|
@@ -1877,13 +2026,23 @@ async function boot() {
|
|
|
1877
2026
|
if (renderer.backend.isWebGPUBackend !== true) {
|
|
1878
2027
|
// WebGPU 不可用时 three 自动回退 WebGL2 backend:两种 backend 都允许渲染
|
|
1879
2028
|
// (2026-08-23 webgui 冒烟测试:用户浏览器无 WebGPU → 纯 WebGPU 检查导致初始化失败)。
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2029
|
+
webglFallback = true;
|
|
2030
|
+
if (webgl2Adaptive) {
|
|
2031
|
+
// 2.0.7: no hardcoded 30 fps / 0.6 any more. The panel's tier is applied
|
|
2032
|
+
// as-is and the governor walks it down if WebGL2 cannot hold it; the
|
|
2033
|
+
// 30 s latch in the governor stops it from climbing back immediately.
|
|
2034
|
+
webglLatchUntil = performance.now() + 30000;
|
|
2035
|
+
if (QUALITY_PRESETS[currentQuality]) applyQuality(currentQuality);
|
|
2036
|
+
else applyQuality('balanced');
|
|
2037
|
+
} else {
|
|
2038
|
+
PIXEL_RATIO_CAP = 1.0;
|
|
2039
|
+
BASE_PIXEL_RATIO = Math.min(window.devicePixelRatio || 1, PIXEL_RATIO_CAP);
|
|
2040
|
+
FRAME_RATE_CAP = 30;
|
|
2041
|
+
FRAME_MS = 1000 / FRAME_RATE_CAP;
|
|
2042
|
+
TARGET_FPS = 28;
|
|
2043
|
+
renderScale = 0.6;
|
|
2044
|
+
applyResolutionScale(renderScale);
|
|
2045
|
+
}
|
|
1887
2046
|
}
|
|
1888
2047
|
|
|
1889
2048
|
loopStarted = true;
|
package/package.json
CHANGED
package/plugin/client.js
CHANGED
|
@@ -96,6 +96,13 @@ window.__ModuleLoader__.load({ id: "open-sea-skin-dsh", factory: (require) => {
|
|
|
96
96
|
colLeft: Object.freeze({ on: true, strength: 28 }),
|
|
97
97
|
colRight: Object.freeze({ on: true, strength: 28 }),
|
|
98
98
|
perf: false,
|
|
99
|
+
// 2.0.7 performance switches: every one of these can be turned off and the
|
|
100
|
+
// 2.0.6 frame comes back unchanged
|
|
101
|
+
perfScheduler: true,
|
|
102
|
+
pauseWhenObscured: 'pause',
|
|
103
|
+
webgl2Adaptive: true,
|
|
104
|
+
resizeDebounce: true,
|
|
105
|
+
glassBlurMode: 'live',
|
|
99
106
|
boats: true,
|
|
100
107
|
boatSpread: 2,
|
|
101
108
|
boatPace: 1,
|
|
@@ -158,6 +165,11 @@ window.__ModuleLoader__.load({ id: "open-sea-skin-dsh", factory: (require) => {
|
|
|
158
165
|
glassMode: '渲染器内 = 毛玻璃在 WebGPU 里做(可逐栏、可调半径);CSS 模糊 = 交给浏览器,省 GPU 但只有一档。',
|
|
159
166
|
glassStyle: '模糊的取样形状:高斯最自然、盒式最省、双层更厚、径向中间清楚边缘糊、磨砂有颗粒、玻璃砖成块。',
|
|
160
167
|
perf: '性能模式:一键关掉毛玻璃与额外采样,只保留海面与天空。',
|
|
168
|
+
perfScheduler: '智能帧调度:把整帧逻辑(星点朝向、船只、相机)也按帧率上限节流,不再在 144Hz 屏上空跑;时间仍按真实时钟推进,海面不会跳。',
|
|
169
|
+
pauseWhenObscured: '后台停止渲染:DSH 窗口被别的窗口盖住时停画或降频。停止最省电,降频保留 10fps 让天色继续走,关闭则一直满帧。',
|
|
170
|
+
webgl2Adaptive: '无 WebGPU 时自适应:没有 WebGPU 的机器不再被死钉在 30fps / 0.6×,而是按画质档位跑并由自适应调节;关掉即回到旧的固定降级。',
|
|
171
|
+
resizeDebounce: '窗口缩放防抖:拖动窗口边缘时先拉伸旧画面,停手 150ms 后再按新尺寸重建渲染目标,避免每像素重建一次。',
|
|
172
|
+
glassBlurMode: '毛玻璃刷新方式:实时 = backdrop-filter 每帧重新取样(最好看也最贵);静态 = 只保留底色、不再实时取样;关闭 = 连接底色一起去掉。仅在「CSS 模糊」模式下生效。',
|
|
161
173
|
frost: '背景模糊总开关。关掉后三栏与外壳都不再糊,但设置面板仍保留 9px 底模糊以便阅读。',
|
|
162
174
|
cols: {
|
|
163
175
|
left: '左侧栏(会话/项目列表)。',
|
|
@@ -255,6 +267,13 @@ window.__ModuleLoader__.load({ id: "open-sea-skin-dsh", factory: (require) => {
|
|
|
255
267
|
secMeteor: '流星',
|
|
256
268
|
secClouds: '云',
|
|
257
269
|
secAdvanced: '高级',
|
|
270
|
+
secPerf: '性能',
|
|
271
|
+
perfScheduler: '智能帧调度',
|
|
272
|
+
pauseWhenObscured: '后台停止渲染',
|
|
273
|
+
webgl2Adaptive: '无 WebGPU 时自适应',
|
|
274
|
+
resizeDebounce: '窗口缩放防抖',
|
|
275
|
+
glassBlurMode: '毛玻璃刷新方式',
|
|
276
|
+
perfNote: '这一组只管「什么时候不画」与「画多快」,不改画面本身;想改像素预算用下面的画质档位,想改帧率上限用帧率档。',
|
|
258
277
|
skyTone: '夜色倾向',
|
|
259
278
|
domeAmount: '穹顶弧度',
|
|
260
279
|
starSize: '星点大小',
|
|
@@ -316,6 +335,8 @@ window.__ModuleLoader__.load({ id: "open-sea-skin-dsh", factory: (require) => {
|
|
|
316
335
|
sunModes: { real: '真实时钟', manual: '手动' },
|
|
317
336
|
grades: { off: '关', low: '少', mid: '中', high: '多' },
|
|
318
337
|
glassModes: { render: '渲染器内', css: 'CSS 模糊' },
|
|
338
|
+
pauseModes: { pause: '停止', throttle: '降频', off: '关闭' },
|
|
339
|
+
glassBlurModes: { live: '实时', static: '静态', off: '关闭' },
|
|
319
340
|
levels: { off: '关', weak: '弱', medium: '中', strong: '强' },
|
|
320
341
|
},
|
|
321
342
|
en: {
|
|
@@ -364,6 +385,11 @@ window.__ModuleLoader__.load({ id: "open-sea-skin-dsh", factory: (require) => {
|
|
|
364
385
|
glassMode: 'Renderer = blur inside WebGPU (per column, real radius); CSS = the browser does it: cheaper, one radius only.',
|
|
365
386
|
glassStyle: 'Kernel shape: gaussian is the most natural, box the cheapest, double thicker, radial sharp centre, frost grainy, tile blocky.',
|
|
366
387
|
perf: 'Performance mode: drops the glass and extra taps, keeps water and sky.',
|
|
388
|
+
perfScheduler: 'Smart frame scheduler: the per-frame logic (star billboards, boats, camera) runs at the frame cap too, instead of 144 times a second on a 144 Hz panel to present 30 frames. Time still advances on the wall clock, so the sea never jumps.',
|
|
389
|
+
pauseWhenObscured: 'Stop in background: when another window covers DSH, stop drawing or slow down. Stop saves the most; throttle keeps 10 fps so the sky keeps moving; off always runs at full rate.',
|
|
390
|
+
webgl2Adaptive: 'Adaptive without WebGPU: a machine without WebGPU is no longer pinned to 30 fps / 0.6x; it runs the chosen tier and lets the governor adjust. Off restores the old fixed fallback.',
|
|
391
|
+
resizeDebounce: 'Debounce resize: while you drag a window edge the old frame is stretched; the render targets are rebuilt 150 ms after you let go, not once per pixel.',
|
|
392
|
+
glassBlurMode: 'Glass blur refresh: live = backdrop-filter re-samples every frame (best looking, most expensive); static = keep the fill, drop the live sample; off = drop the fill too. Only applies in CSS blur mode.',
|
|
367
393
|
frost: 'Master background blur. Off leaves all columns sharp, while the settings panel keeps a 9px floor so it stays readable.',
|
|
368
394
|
cols: {
|
|
369
395
|
left: 'Left column: the session and project list.',
|
|
@@ -461,6 +487,13 @@ window.__ModuleLoader__.load({ id: "open-sea-skin-dsh", factory: (require) => {
|
|
|
461
487
|
secMeteor: 'Meteors',
|
|
462
488
|
secClouds: 'Clouds',
|
|
463
489
|
secAdvanced: 'Advanced',
|
|
490
|
+
secPerf: 'Performance',
|
|
491
|
+
perfScheduler: 'Smart frame scheduler',
|
|
492
|
+
pauseWhenObscured: 'Stop in background',
|
|
493
|
+
webgl2Adaptive: 'Adaptive without WebGPU',
|
|
494
|
+
resizeDebounce: 'Debounce resize',
|
|
495
|
+
glassBlurMode: 'Glass blur refresh',
|
|
496
|
+
perfNote: 'This group only decides when to stop drawing and how fast to draw; it never changes the picture itself. Pixel budget stays on the quality tier below, the frame cap stays on its own dial.',
|
|
464
497
|
skyTone: 'Night tint',
|
|
465
498
|
domeAmount: 'Dome curve',
|
|
466
499
|
starSize: 'Star size',
|
|
@@ -522,6 +555,8 @@ window.__ModuleLoader__.load({ id: "open-sea-skin-dsh", factory: (require) => {
|
|
|
522
555
|
sunModes: { real: 'Real clock', manual: 'Manual' },
|
|
523
556
|
grades: { off: 'Off', low: 'Low', mid: 'Mid', high: 'High' },
|
|
524
557
|
glassModes: { render: 'In renderer', css: 'CSS blur' },
|
|
558
|
+
pauseModes: { pause: 'Stop', throttle: 'Throttle', off: 'Off' },
|
|
559
|
+
glassBlurModes: { live: 'Live', static: 'Static', off: 'Off' },
|
|
525
560
|
levels: { off: 'Off', weak: 'Weak', medium: 'Mid', strong: 'Strong' },
|
|
526
561
|
},
|
|
527
562
|
});
|
|
@@ -557,6 +592,10 @@ window.__ModuleLoader__.load({ id: "open-sea-skin-dsh", factory: (require) => {
|
|
|
557
592
|
const TZ_MODES = ['system', 'longitude', 'manual'];
|
|
558
593
|
const QUALITY_LEVELS = ['high', 'balanced', 'low'];
|
|
559
594
|
const FPS_LEVELS = [30, 60, 90, 120];
|
|
595
|
+
// 2.0.7: how the surface behaves while it is in the background, and how the CSS
|
|
596
|
+
// glass fallback refreshes itself
|
|
597
|
+
const PAUSE_MODES = ['pause', 'throttle', 'off'];
|
|
598
|
+
const GLASS_BLUR_MODES = ['live', 'static', 'off'];
|
|
560
599
|
|
|
561
600
|
// 2.0.4: a tier carries a suggested cap so the two dials never disagree on the
|
|
562
601
|
// first click. The cap stays its own dial: changing it afterwards does not move
|
|
@@ -649,6 +688,11 @@ const PRESET_FPS = { high: 90, balanced: 60, low: 30 };
|
|
|
649
688
|
colLeft: normalizeColumn(source.colLeft, DEFAULTS.colLeft),
|
|
650
689
|
colRight: normalizeColumn(source.colRight, DEFAULTS.colRight),
|
|
651
690
|
perf: source.perf === true,
|
|
691
|
+
perfScheduler: source.perfScheduler !== false,
|
|
692
|
+
pauseWhenObscured: PAUSE_MODES.includes(source.pauseWhenObscured) ? source.pauseWhenObscured : DEFAULTS.pauseWhenObscured,
|
|
693
|
+
webgl2Adaptive: source.webgl2Adaptive !== false,
|
|
694
|
+
resizeDebounce: source.resizeDebounce !== false,
|
|
695
|
+
glassBlurMode: GLASS_BLUR_MODES.includes(source.glassBlurMode) ? source.glassBlurMode : DEFAULTS.glassBlurMode,
|
|
652
696
|
boats: source.boats !== false,
|
|
653
697
|
boatSpread: clamp(source.boatSpread, 0.2, 3, DEFAULTS.boatSpread),
|
|
654
698
|
boatPace: clamp(source.boatPace, 0.3, 2.5, DEFAULTS.boatPace),
|
|
@@ -687,6 +731,19 @@ const PRESET_FPS = { high: 90, balanced: 60, low: 30 };
|
|
|
687
731
|
|
|
688
732
|
function blurCss(state) {
|
|
689
733
|
if (!state.frost || state.glassMode !== 'css' || state.perf) return '';
|
|
734
|
+
const columns = 'body [data-slot="sidebar"], body .dshDesktopSidebarSurface, body [data-rightbar-col], body .dshDesktopDetailsSurface';
|
|
735
|
+
// 2.0.7: a backdrop-filter is re-sampled by the compositor every frame the
|
|
736
|
+
// scene under it changes, which is every frame. `static` keeps the frosted
|
|
737
|
+
// fill (painted by glassCss) but stops re-sampling; `off` also drops the
|
|
738
|
+
// column fill, so the columns read as plain windows.
|
|
739
|
+
if (state.glassBlurMode === 'static') {
|
|
740
|
+
return `/* static glass: fill stays, the live backdrop sample does not */
|
|
741
|
+
${columns} { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }`;
|
|
742
|
+
}
|
|
743
|
+
if (state.glassBlurMode === 'off') {
|
|
744
|
+
return `/* glass blur off: no live sample and no column fill */
|
|
745
|
+
${columns} { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; background-color: transparent !important; }`;
|
|
746
|
+
}
|
|
690
747
|
const column = (selector, key) => {
|
|
691
748
|
const entry = state[key];
|
|
692
749
|
// one radius for the frame, each column takes a share of it
|
|
@@ -1075,6 +1132,12 @@ body:not([data-ds-dark-theme]) #${IDS.button}.active { color:#0a4f4d; border-col
|
|
|
1075
1132
|
overflow: seaOverflow(),
|
|
1076
1133
|
quality: state.quality,
|
|
1077
1134
|
fps: state.fps,
|
|
1135
|
+
// 2.0.7 performance switches; glassBlurMode stays on this side, it only
|
|
1136
|
+
// rewrites our own CSS and never reaches the renderer
|
|
1137
|
+
perfScheduler: state.perfScheduler,
|
|
1138
|
+
pauseWhenObscured: state.pauseWhenObscured,
|
|
1139
|
+
webgl2Adaptive: state.webgl2Adaptive,
|
|
1140
|
+
resizeDebounce: state.resizeDebounce,
|
|
1078
1141
|
glow: state.glow,
|
|
1079
1142
|
});
|
|
1080
1143
|
};
|
|
@@ -1709,6 +1772,11 @@ body:not([data-ds-dark-theme]) #${IDS.button}.active { color:#0a4f4d; border-col
|
|
|
1709
1772
|
frame.addEventListener('load', () => {
|
|
1710
1773
|
postSun();
|
|
1711
1774
|
postGlass();
|
|
1775
|
+
// a fresh frame boots believing it is on screen; correct that at once
|
|
1776
|
+
if (typeof postActive === 'function') {
|
|
1777
|
+
lastActive = null;
|
|
1778
|
+
postActive();
|
|
1779
|
+
}
|
|
1712
1780
|
postBoats();
|
|
1713
1781
|
postToOcean(state.waveMode === 'manual' ? state.sea : seaValue);
|
|
1714
1782
|
// The frame boots with one boat on the water; the poll below reposts the
|
|
@@ -1718,6 +1786,34 @@ body:not([data-ds-dark-theme]) #${IDS.button}.active { color:#0a4f4d; border-col
|
|
|
1718
1786
|
if (glassTimer) window.clearInterval(glassTimer);
|
|
1719
1787
|
glassTimer = window.setInterval(postGlass, 1500);
|
|
1720
1788
|
}, { once: true });
|
|
1789
|
+
// 2.0.7: forward whether this window is actually the one on screen. The
|
|
1790
|
+
// surface lives in an iframe, which is never a tab: it sees neither
|
|
1791
|
+
// visibilitychange (that one it does see) nor focus (that one it never
|
|
1792
|
+
// gets), so a covered DSH window looked identical to an idle one and the
|
|
1793
|
+
// sea kept drawing at full rate behind whatever was on top. Debounced,
|
|
1794
|
+
// because alt-tabbing flickers focus and stopping/starting per flicker
|
|
1795
|
+
// would cost more than it saves.
|
|
1796
|
+
let activeTimer = 0;
|
|
1797
|
+
let lastActive = null;
|
|
1798
|
+
const postActive = () => {
|
|
1799
|
+
const active = !document.hidden && document.hasFocus();
|
|
1800
|
+
if (active === lastActive) return;
|
|
1801
|
+
lastActive = active;
|
|
1802
|
+
post({ type: 'oss-active', active });
|
|
1803
|
+
};
|
|
1804
|
+
const scheduleActive = () => {
|
|
1805
|
+
if (activeTimer) window.clearTimeout(activeTimer);
|
|
1806
|
+
activeTimer = window.setTimeout(postActive, 500);
|
|
1807
|
+
};
|
|
1808
|
+
window.addEventListener('focus', scheduleActive);
|
|
1809
|
+
window.addEventListener('blur', scheduleActive);
|
|
1810
|
+
document.addEventListener('visibilitychange', scheduleActive);
|
|
1811
|
+
surfaceCleanups.push(() => {
|
|
1812
|
+
window.removeEventListener('focus', scheduleActive);
|
|
1813
|
+
window.removeEventListener('blur', scheduleActive);
|
|
1814
|
+
document.removeEventListener('visibilitychange', scheduleActive);
|
|
1815
|
+
if (activeTimer) window.clearTimeout(activeTimer);
|
|
1816
|
+
});
|
|
1721
1817
|
ownsSurface = true;
|
|
1722
1818
|
return true;
|
|
1723
1819
|
};
|
|
@@ -1836,11 +1932,18 @@ body:not([data-ds-dark-theme]) #${IDS.button}.active { color:#0a4f4d; border-col
|
|
|
1836
1932
|
<p class="oss-sub" data-copy="colsNote"></p>
|
|
1837
1933
|
${switchRow(pid('perf'), 'perf')}
|
|
1838
1934
|
<p class="oss-sub" data-copy="glassNote"></p>`)}
|
|
1935
|
+
${group('secPerf', `
|
|
1936
|
+
${switchRow(pid('perf-scheduler'), 'perfScheduler')}
|
|
1937
|
+
${segRow(pid('pause-mode'), 'pauseWhenObscured', [['pause', 'pauseModes.pause'], ['throttle', 'pauseModes.throttle'], ['off', 'pauseModes.off']])}
|
|
1938
|
+
${switchRow(pid('webgl2-adaptive'), 'webgl2Adaptive')}
|
|
1939
|
+
<p class="oss-sub" data-copy="perfNote"></p>`)}
|
|
1839
1940
|
${group('secAdvanced', `
|
|
1840
1941
|
<p class="oss-sub" data-copy="perfGroup"></p>
|
|
1841
1942
|
${segRow(pid('quality'), 'qualityMode', [['high', 'quality.high'], ['balanced', 'quality.balanced'], ['low', 'quality.low']])}
|
|
1842
1943
|
${rangeRow(pid('glow'), 'glow', 0, 100, 1)}
|
|
1843
1944
|
${segRow(pid('fps'), 'fpsMode', [['30', 'fps.30'], ['60', 'fps.60'], ['90', 'fps.90'], ['120', 'fps.120']])}
|
|
1945
|
+
${switchRow(pid('resize-debounce'), 'resizeDebounce')}
|
|
1946
|
+
${segRow(pid('glass-blur-mode'), 'glassBlurMode', [['live', 'glassBlurModes.live'], ['static', 'glassBlurModes.static'], ['off', 'glassBlurModes.off']])}
|
|
1844
1947
|
<p class="oss-sub" id="${pid('perf-out')}"></p>
|
|
1845
1948
|
<div class="oss-row"><button type="button" class="oss-reset" id="${pid('make-default')}" data-copy="makeDefault"></button></div>
|
|
1846
1949
|
<p class="oss-sub" id="${pid('default-out')}"></p>
|
|
@@ -1857,6 +1960,9 @@ ${switchRow(pid('perf'), 'perf')}
|
|
|
1857
1960
|
const moonBox = el('moon');
|
|
1858
1961
|
const perfBox = el('perf');
|
|
1859
1962
|
const frostBox = el('frost');
|
|
1963
|
+
const schedulerBox = el('perf-scheduler');
|
|
1964
|
+
const webgl2Box = el('webgl2-adaptive');
|
|
1965
|
+
const resizeDebounceBox = el('resize-debounce');
|
|
1860
1966
|
const milkyOut = el('milky-out');
|
|
1861
1967
|
const boatsBox = el('boats');
|
|
1862
1968
|
const meteorBox = el('meteor');
|
|
@@ -1970,6 +2076,9 @@ ${switchRow(pid('perf'), 'perf')}
|
|
|
1970
2076
|
moonBox.checked = state.moon;
|
|
1971
2077
|
perfBox.checked = state.perf;
|
|
1972
2078
|
if (frostBox) frostBox.checked = state.frost;
|
|
2079
|
+
if (schedulerBox) schedulerBox.checked = state.perfScheduler;
|
|
2080
|
+
if (webgl2Box) webgl2Box.checked = state.webgl2Adaptive;
|
|
2081
|
+
if (resizeDebounceBox) resizeDebounceBox.checked = state.resizeDebounce;
|
|
1973
2082
|
if (meteorBox) meteorBox.checked = state.meteor;
|
|
1974
2083
|
if (daylightBox) daylightBox.checked = state.starsDaylight;
|
|
1975
2084
|
if (boatsBox) boatsBox.checked = state.boats;
|
|
@@ -1984,6 +2093,8 @@ ${switchRow(pid('perf'), 'perf')}
|
|
|
1984
2093
|
// every performance button unlit even though one of them was in effect
|
|
1985
2094
|
setSeg(el('quality'), state.quality);
|
|
1986
2095
|
setSeg(el('fps'), String(state.fps));
|
|
2096
|
+
setSeg(el('pause-mode'), state.pauseWhenObscured);
|
|
2097
|
+
setSeg(el('glass-blur-mode'), state.glassBlurMode);
|
|
1987
2098
|
if (milkyOut) milkyOut.textContent = milkyText();
|
|
1988
2099
|
for (const [key, entry] of Object.entries(columns)) {
|
|
1989
2100
|
const entryState = state[key];
|
|
@@ -2159,6 +2270,8 @@ ${switchRow(pid('perf'), 'perf')}
|
|
|
2159
2270
|
case pid('glass-style'): return { glassStyle: value };
|
|
2160
2271
|
case pid('quality'): return { quality: value, fps: PRESET_FPS[value] || state.fps };
|
|
2161
2272
|
case pid('fps'): return { fps: Number(value) };
|
|
2273
|
+
case pid('pause-mode'): return { pauseWhenObscured: value };
|
|
2274
|
+
case pid('glass-blur-mode'): return { glassBlurMode: value };
|
|
2162
2275
|
default: return null;
|
|
2163
2276
|
}
|
|
2164
2277
|
})();
|
|
@@ -2225,6 +2338,24 @@ ${switchRow(pid('perf'), 'perf')}
|
|
|
2225
2338
|
updateGlass();
|
|
2226
2339
|
postGlass();
|
|
2227
2340
|
});
|
|
2341
|
+
if (schedulerBox) {
|
|
2342
|
+
on(schedulerBox, 'change', () => {
|
|
2343
|
+
void save({ perfScheduler: schedulerBox.checked });
|
|
2344
|
+
postSun();
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
if (webgl2Box) {
|
|
2348
|
+
on(webgl2Box, 'change', () => {
|
|
2349
|
+
void save({ webgl2Adaptive: webgl2Box.checked });
|
|
2350
|
+
postSun();
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
if (resizeDebounceBox) {
|
|
2354
|
+
on(resizeDebounceBox, 'change', () => {
|
|
2355
|
+
void save({ resizeDebounce: resizeDebounceBox.checked });
|
|
2356
|
+
postSun();
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2228
2359
|
const makeDefaultBtn = el('make-default');
|
|
2229
2360
|
const defaultOut = el('default-out');
|
|
2230
2361
|
const refreshDefaultOut = () => {
|
|
@@ -2568,7 +2699,7 @@ ${switchRow(pid('perf'), 'perf')}
|
|
|
2568
2699
|
// Stamped into both halves so a report can name which copy the browser is
|
|
2569
2700
|
// actually running: the panel and the frame are loaded by different paths and
|
|
2570
2701
|
// a stale one looks exactly like a bug.
|
|
2571
|
-
const SKIN_BUILD = 'lab-2.0.
|
|
2702
|
+
const SKIN_BUILD = 'lab-2.0.7';
|
|
2572
2703
|
|
|
2573
2704
|
exports.name = 'open-sea-skin-client';
|
|
2574
2705
|
exports.inject = ['slots'];
|