diff --git a/README.md b/README.md index 369edf0..5ca1d97 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,7 @@ Windows 10 1809 or newer. Grab the release, or build it yourself. ### Download -Take `featherwall-v0.1.0-win-x64.zip` from the +Take `featherwall-v0.2.0-win-x64.zip` from the [latest release](https://github.com/RitvikDayal/featherwall/releases/latest). It unzips to a single `featherwall.exe`, so there is no installer and nothing to uninstall. Run it and you are done. @@ -248,7 +248,7 @@ release to 7 MB instead of shipping a copy of .NET with it. Verify the download if you like: ```powershell -Get-FileHash .\featherwall-v0.1.0-win-x64.zip -Algorithm SHA256 +Get-FileHash .\featherwall-v0.2.0-win-x64.zip -Algorithm SHA256 # 300f59a28269fc2fc034ad86ee64d6fd320cd17f8c2e7a31c789bcc28844dfd7 ``` @@ -314,9 +314,10 @@ the accent colour rather than a broken box; pausing dims it rather than emptying **The record is the one thing in FeatherWall that runs on a frame clock**, and it is deliberately hard to leave running: it turns only while something is actually playing *and* the desktop is uncovered *and* the display is on. Any of those going false stops it dead — not throttled, stopped. -Turning off "Spin while playing" creates no timer at all, and you keep the record, the artwork and -the progress ring as a still image. Everything else in the app still repaints only when Windows -pushes it an event: a battery percentage moving, a track changing. +Turning off "Spin while playing" leaves its timer permanently disarmed, and you keep the record, +the artwork and the progress ring as a still image. The battery and now-playing sources repaint +only when Windows pushes an event — a battery percentage moving, a track changing — and the clock +ticks once a second, as it has since v0.1.0. Everything else: per-monitor wallpapers, Fill, Fit and Stretch modes, DPI-aware rendering, a full tray menu, JSON config for anything the UI does not expose, and auto-pause on fullscreen apps, diff --git a/docs/architecture.md b/docs/architecture.md index 1975bcf..c1cbae0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -38,7 +38,9 @@ That gets a window into the right z-slot. It does **not** get pixels on screen. DPI does still matter, just one layer up. Sizes a *human* authored — the clock's font size and its margins — are in config as plain pixels, so on a second monitor at a different DPI the widget would be the same pixel count and therefore the wrong physical size. `MonitorTracker.DpiScale` resolves that per monitor from `GetDpiForMonitor(MDT_EFFECTIVE_DPI)`, **relative to the primary monitor rather than to 96**, so a single-DPI machine scales by exactly 1.0 and renders identically to v0.1.0. `featherwall --diag` prints each monitor's DPI and its computed widget scale. -One more practical discovery: DWM did not reliably compose a *second* per-widget window target inside the wallpaper layer, so the clock is not a window at all — it is an **overlay visual** in the same composition tree as the wallpaper content. Fewer HWNDs, trivial z-order, click-through by construction. The clock is rendered in a Mond-inspired style — a large light-weight time, a hairline separator, and a small dimmed date — as a GDI+ bitmap drawn onto the overlay surface at most once per second (boundary-aligned timer, re-armed each tick so it never drifts). +One more practical discovery: DWM did not reliably compose a *second* per-widget window target inside the wallpaper layer, so the widgets are not windows at all — each is an **overlay visual** in the same composition tree as the wallpaper content, held by key (`clock`, `info`, `halo`, `nowplaying`) so several can coexist above one content surface. Fewer HWNDs, trivial z-order, click-through by construction. + +The widgets are event-driven with one deliberate exception. The clock re-arms a boundary-aligned timer; the battery and now-playing *sources* repaint only when Windows pushes a power-setting notification or a media-session event. The **now-playing record** is the exception: it runs a 15 fps frame clock to turn, gated on playing **and** desktop-uncovered **and** display-on, and when `disc.rotate` is false its timer is constructed but never armed, so nothing ever fires. The clock is rendered in a Mond-inspired style — a large light-weight time, a hairline separator, and a small dimmed date — as a GDI+ bitmap drawn onto the overlay surface at most once per second (boundary-aligned timer, re-armed each tick so it never drifts). ## Settings panel diff --git a/docs/benchmark.md b/docs/benchmark.md index 4bace10..221fd9a 100644 --- a/docs/benchmark.md +++ b/docs/benchmark.md @@ -45,7 +45,9 @@ The check exists because it caught that. | **Video 4K60 playing** | **1** | **199.4** | **188.6** | **220.6** | **58.5** | **0.318 %** | **7.6 %** | **5.8 % (videodecode)** | playing, full window | | **Video 4K60, auto-paused** | **1** | **219.5** | **204.2** | **231.6** | **68.1** | **0.034 %** | **0.8 %** | **0 % (copy)** | paused, full window | | Settings panel open | | | | | | | | | | -| Still image + info widget | | | | | | | | | | +| **Still image + widgets** | **1** | **93.0** | **127.8** | **31.5** | **2.2** | **0.135 %** | **3.3 %** | **0.1 % (copy)** | playing, full window | +| **Video 4K60 + widgets** | **1** | **208.5** | **209.9** | **220.6** | **63.5** | **0.988 %** | **23.7 %** | **6.6 % (videodecode)** | playing, full window | +| **Video 4K60 paused + widgets** | **1** | **210.0** | **212.6** | **220.6** | **63.5** | **0.052 %** | **1.2 %** | **0 % (3d)** | paused, full window | *Playing rows measured 2026-08-22 on build 26200, 24 cores, Intel Arc, single 2560×1600 display at 150 %. Source `scripts/bench/results/featherwall-playing-verified.json`. Paused row measured @@ -84,29 +86,57 @@ refused rather than measured. An interactive run is *expected* to produce it, by that produced the auto-paused row — but expected is not verified, no run has yet produced it, and this file should not read as though one has. -**The widget rows have not been measured, and the design said they should be.** The battery halo -was built to a stated cost budget — no timer, no poll, one small bitmap per change — and the -now-playing record then added the app's first frame clock on top of it. The way to check both is -to run the still-image row twice, once plain and once with `-Widgets`, and see whether it moves. +## What the widgets cost — measured 2026-08-29 -**"No timer anywhere" is no longer true of FeatherWall as a whole, and this file used to say it -was.** The record turns at 15 fps while music plays and the desktop is visible, and stops -completely otherwise. That cost has never been measured, which is exactly why the row below -matters more now than it did when the claim was safe. Two attempts were lost to a full-screen browser that ignores `MinimizeAll` and +Two runs, back to back in one session, on the same machine and the same media. Comparing across +sessions would be meaningless: this machine's CPU figure moves two- to threefold run to run. + +| Still image | Without widgets | With widgets | Change | +|---|---|---|---| +| CPU, whole machine | 0.026 % | **0.135 %** | **x5.2** | +| CPU, one core | 0.6 % | **3.3 %** | **x5.5** | +| Working set | 110.7 MB | 127.8 MB | +17 MB | + +| 4K60 playing | Without widgets | With widgets | Change | +|---|---|---|---| +| CPU, whole machine | 0.721 % | **0.988 %** | x1.37 | +| CPU, one core | 17.3 % | **23.7 %** | x1.37 | +| Busiest GPU engine | 6.4 % decode | 6.6 % decode | unchanged | + +**The still-image row moved, and a lot in relative terms.** Five times is far outside the two- to +threefold spread this machine shows between runs, so it is a real signal rather than noise. + +That is the record's frame clock, and it is the trade the design accepted knowingly. The still +image is the state where FeatherWall previously did *nothing at all* after first paint, so any +number above zero is an infinite proportional increase and a small absolute one: **0.135 % of a +24-core machine is about a thirtieth of one core.** Set `disc.rotate` to false and no timer is +created — the record, its artwork and the progress ring stay as a still image. + +On a video wallpaper the same widgets cost proportionally far less, because the decoder already +dominates, and the GPU figure does not move at all. + +**Sources:** `scripts/bench/results/featherwall-baseline-verified.json` and +`featherwall-widgets-verified.json`. The record was confirmed turning during the sampling window +from FeatherWall's own log, so this measures the timer running rather than a static widget. + +**"No timer anywhere" was never quite true, and this file used to say it was.** The clock widget +has re-armed a 1 Hz timer since v0.1.0. What that claim actually meant, and still means, is that the +*battery and now-playing sources* repaint only on pushed events. The record is the new exception and +the only continuous one: 15 fps while music plays and the desktop is visible, nothing otherwise. Two attempts were lost to a full-screen browser that ignores `MinimizeAll` and takes the foreground back between samples, so FeatherWall correctly paused and the harness correctly refused every playing row. The harness now names that window and stops before running rather than discovering it four rows later. -To fill the row, from an interactive session with nothing in full screen: +To reproduce, from a session with nothing in full screen: ```powershell .\scripts\bench\Run-Bench.ps1 -Video -Image .\docs\media\hero.jpg -Seconds 30 .\scripts\bench\Run-Bench.ps1 -Video -Image .\docs\media\hero.jpg -Seconds 30 -Widgets ``` -Compare the two still-image rows. **CPU spans roughly 2x run to run on this machine**, as the note -above records, so a difference inside that spread is not evidence of anything and should be -reported as inconclusive rather than as a pass. +Compare the two still-image rows, and only ever from a single session. **CPU spans roughly two- to +threefold run to run on this machine**, so a difference inside that spread is not evidence of +anything. What *is* verified is which parts have a timer and which do not. Every repaint is logged. Over an hour of running, the battery and info widgets repainted only at startup, when a media session diff --git a/docs/index.html b/docs/index.html index bf22639..f2467a4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,9 +4,9 @@ FeatherWall — live wallpapers for Windows - + - + @@ -479,7 +479,7 @@
FeatherWall - v0.1.0  /  MIT  /  Windows 10 & 11 + v0.2.0  /  MIT  /  Windows 10 & 11
Source @@ -535,8 +535,8 @@

FEATHER
WALL

Live wallpapers for Windows that don't eat your laptop.

@@ -545,7 +545,7 @@

Live wallpapers for Windows that don't eat your laptop.

- Open source live wallpaper engine  /  v0.1.0  /  7.5 MB  /  MIT  /  100% C# + Open source live wallpaper engine  /  v0.2.0  /  7.2 MB  /  MIT  /  100% C# Scroll to lay it flat ↓
@@ -560,8 +560,8 @@

Live wallpapers for Windows that don't eat your laptop.

0Process, not five
-
0MB for a still wallpaper
-
0Percent CPU at rest
+
0MB for a still wallpaper
+
0Percent CPU at rest
0Accounts or trackers
@@ -604,12 +604,12 @@

It gets out of the way

02Measured

What it actually costs

-

25 one-second samples on a running instance, with the pause state checked before and after each window so a silently paused wallpaper could never be recorded as a cheap one. Pick a state and watch what moves.

+

Produced by scripts/bench/Run-Bench.ps1, which reads FeatherWall's own log and refuses to report a row whose pause state changed inside the sampling window — because measuring a silently paused wallpaper is the easiest way to publish a flattering lie. Pick a state and watch what moves.

- - + +
@@ -620,38 +620,45 @@

What it actually costs

Name
CPU
Memory
GPU
GPU engine
featherwall.exe
-
0.79%
-
212 MB
-
29.4%
-
GPU 0 – Video Processing
+
0.32%
+
199 MB
+
5.8%
+
GPU 0 – Video Decode
GPU engines · this process
-
Video processing29.4%
-
Video decode6.8%
-
3D7.5%
-
Graphics memory122 MB
+
Video processing0%
+
Video decode5.8%
+
3D0%
+
Graphics memory221 MB
Adapters
-
GPU 0 · Intel Arc, integrated29.4%
+
GPU 0 · Intel Arc, integrated5.8%
GPU 1 · NVIDIA RTX 5090 Laptop0%

All of this lands on the integrated GPU. nvidia-smi lists no FeatherWall process on the RTX 5090, which stayed at 0% throughout. The compositor lives on whichever adapter drives your display, so the wallpaper never competes with a game on the discrete card.

-

Two things the numbers do not say on their own. GPU percentages are per engine and do not sum, which is why the table shows the busiest one, the same as Task Manager. And graphics memory is a separate allocation from the working set: dedicated VRAM reads 0 MB because integrated graphics borrow system RAM, so a 4K60 wallpaper really costs about 480 MB of system memory rather than 248 MB. Paused is the largest memory row because buffers are retained so resuming is instant, while CPU and both video engines drop to nothing.

+

Two things the numbers do not say on their own. GPU percentages are per engine and do not sum, which is why the table shows the busiest one, the same as Task Manager. And graphics memory is a separate allocation from the working set, so the honest total for a wallpaper is the working set plus both graphics figures. Paused is the largest memory row because buffers are retained so resuming is instant, while CPU and the video engines drop to nothing.

+ +

Two corrections this page owes you. It used to say dedicated VRAM reads 0 MB “because integrated graphics borrow system RAM”. That is wrong: this machine's integrated Arc reports 2048 MB of adapter RAM and Windows attributes 232 MB of dedicated usage to FeatherWall on it. The old explanation made the footprint look smaller than it is. It also published 0.004% CPU for the paused state; the harness measures 0.034%. Both numbers here now come from the script rather than from reading Task Manager, and the correction goes against us on the paused row.

+ +

1080p60 has never been measured, so its tab shows dashes rather than a number. An empty cell means nobody has run it — not zero, and not unknown-but-probably-fine. CPU also moves two- to threefold between runs of the same clip on the same machine, so treat any single figure as one sample.

+ +

What the widgets cost. Measured across two runs in one session. On a still image the widgets take CPU from 0.026% to 0.135% of the machine — about a thirtieth of one core, but roughly five times the baseline, because a still wallpaper previously did nothing at all after first paint. That is the record's frame clock, and switching off “spin while playing” gives it back. On a 4K60 video the same widgets move CPU from 0.721% to 0.988% and the GPU not at all, because the decoder already dominates.

- 03The widget -

Any font you own

+ 03The widgets +

A clock, a charge, a record

-

Nine positions, any installed typeface, adjustable size, colour and opacity. The settings panel previews it with the same renderer that paints your desktop, so what you pick is what lands.

+

The clock takes any typeface you own, at any of nine positions, with adjustable size, colour and opacity — previewed in the settings panel by the same renderer that paints your desktop. Alongside it: a battery halo whose ring is the charge and whose colour steps with it, and a record that shows what you are listening to, with the real album artwork on its label, turning while the music plays.

+

The record is the only part that runs on a frame clock, and it stops the moment anything covers the desktop, the display sleeps, or the music does. Turning off “spin while playing” leaves its timer permanently disarmed and the artwork and progress ring as a still image. Everything else repaints only when Windows pushes an event — a battery percentage moving, a track changing.