Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f1c58c4
Show glass and frost in per-window captures
Fi3w0 Sep 24, 2026
8f3a8aa
Add shader engine Phase 7A: definitions and fragment contract
Fi3w0 Sep 24, 2026
8682041
Add shader engine Phase 7B-ii: rule-to-surface shader assignment
Fi3w0 Sep 24, 2026
f4382ba
Add shader engine Phase 7B-iii: passthrough custom shader element
Fi3w0 Sep 24, 2026
757f75f
Record the shader groundwork and per-window capture glass fix
Fi3w0 Sep 24, 2026
2baa205
Keep block_capture content out of per-window capture glass
Fi3w0 Sep 24, 2026
a2836f9
Bound custom shader captures and report them in tidectl perf
Fi3w0 Sep 24, 2026
a38773d
Show the driver's compile error for a custom shader
Fi3w0 Sep 24, 2026
8d2b92a
Add workspace_rule shader defaults
Fi3w0 Sep 24, 2026
56b6e9a
Run custom shader chains with save, get and texture bindings
Fi3w0 Sep 24, 2026
5513175
Share the software GLES test renderer across visual tests
Fi3w0 Sep 24, 2026
dc6a2ee
Fix layer ignore_alpha never masking frost
Fi3w0 Sep 24, 2026
a814538
Ship example custom shaders and keep them compiling
Fi3w0 Sep 24, 2026
763c932
Document custom shaders
Fi3w0 Sep 24, 2026
a3a2b2f
Fix shader engine issues found in review
Fi3w0 Sep 24, 2026
567107b
Keep focus cycling from raising tiled windows over floaters
Fi3w0 Sep 24, 2026
75ff5c0
Say which check refused a spring curve
Fi3w0 Sep 24, 2026
1a4ee6a
List custom shaders in the technical report's feature table
Fi3w0 Sep 24, 2026
24448f7
Let layer rules assign custom shaders
Fi3w0 Sep 24, 2026
7c410cb
Include layer glass in output screenshots and screencasts
Fi3w0 Sep 24, 2026
0b65d1e
Fix layer shader issues found in review
Fi3w0 Sep 24, 2026
5c63b90
Let a shader definition take another definition's output as its source
Fi3w0 Sep 24, 2026
c593da7
Make automatic attention depth opt-in
Fi3w0 Sep 24, 2026
2727de9
Implement client drag'n'drop
Fi3w0 Sep 25, 2026
3286393
Size the wallpaper in physical pixels inside captures
Fi3w0 Sep 25, 2026
182df1b
Accept zero for cursor_hide_after
Fi3w0 Sep 25, 2026
6517964
Let spawn take quoted arguments; fix tidectl flag and help parsing
Fi3w0 Sep 25, 2026
490df73
Report render and card nodes separately in doctor
Fi3w0 Sep 25, 2026
e4867b1
Answer IPC requests that end at EOF without a newline
Fi3w0 Sep 25, 2026
2830ab7
Document pointer_modifier and quoted spawn arguments
Fi3w0 Sep 25, 2026
8ebb259
Apply output blocks on config reload
Fi3w0 Sep 25, 2026
13b104e
Changelog entries for the reload, spawn, tidectl, IPC and doctor fixes
Fi3w0 Sep 25, 2026
c41a9a4
Record the first standalone Nvidia pass
Fi3w0 Sep 25, 2026
ad0ed6b
Record the screencast result on Nvidia
Fi3w0 Sep 25, 2026
e2385eb
Accept command-local JSON flags in tidectl
Fi3w0 Sep 25, 2026
df3a76c
Merge pull request #5 from Fi3w0/fiw/testing-fixes
Fi3w0 Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

140 changes: 133 additions & 7 deletions DOCUMENTATION.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Underneath the water is a real tiling window manager: fast keyboard-driven layou
- **It actually looks like something.** Ripples on open, wave transitions between workspaces, glass and shadow on your windows, floating windows that drift like they're sitting on water. Every bit of it is a config toggle, and none of it costs you performance you'll notice.
- **Two ways to work.** Stick with classic numbered workspaces, or switch to Ocean: one endless zoomable canvas where every window lives in real 2D space instead of a workspace number. Switch between them live, no restart.
- **Runs on what you already own.** Measured at ~60-70MB PSS at idle with the full water stack on, ~63MB with nine frosted-glass windows -- about half of Hyprland on the same hardware -- and it only climbs past that if you turn on more than the defaults.
- **Genuinely yours.** Every ripple, wave, border, and shadow is a value in a config file, hot-reloaded on save. Nothing about how it looks is hardcoded.
- **Genuinely yours.** Every ripple, wave, border, and shadow is a value in a config file, hot-reloaded on save. Nothing about how it looks is hardcoded. If that's not enough, write your own GLSL effect for a window and chain up to four passes of it (opt-in, see [Custom shaders](DOCUMENTATION.md#custom-shaders)).

## Try it

Expand Down
7 changes: 4 additions & 3 deletions TECHNICAL_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,23 @@ Current release: **0.90.104**, second major pre-release. 1.0 is intentionally re
| IPC: request/response plus an event-stream subscribe mode | Done |
| `tidectl` CLI, including `doctor`/`report` for bug triage | Done |
| Water/decoration render stack: impulse ripples, wave workspace transitions, tiled/floating water-glass and per-app liquid frost glass, analytical shadows, animated gradient borders, rounded clipping, configurable window animations, move/resize viscosity, connected-vessel BSP resizing, opt-in floating sway, automatic depth/buoyancy, ambient caustics | Done; tiled/liquid frost is release-GLES nested-verified in Classic/Ocean, standalone AMD remains, and the earlier stack is standalone-AMD-verified |
| Custom shaders (`shaders { }`, `shader "<name>" { }`): opt-in user `.frag` effects over a window's backdrop, up to four chained stages with `save`/`get` and extra texture bindings, per-window and per-workspace assignment, bounded budgets | Done through Phase 7B; compile/draw covered by Mesa software-GLES tests, not yet run in a nested or native session |
| Classic Depth Deck (tiled-window park/swap recall) | Done, standalone-AMD-verified |
| Ocean spatial engine: reefs, per-output cameras, sink/dredge/surface depth, bookmarks, freeform window detach, smart tiling, live Classic↔Ocean migration | Done, standalone-AMD-verified |
| Ocean compass (off-screen urgent/deep glow cues) and whole-world overview minimap | Done, nested-verified only |
| Continuous swim navigation (trackpad swipe between workspaces) | Done, real-touchpad-verified |
| Floating-window ocean physics (`float_physics { tier = light|full }`): disturbance-driven bob/drift, `full` adds mass/collisions/a traveling wave field | Done, nested-verified only |
| Ocean currents: bounded render-only downstream drift for visible unfocused floating windows, with focus/drag pause | Done, release-GLES nested-verified on AMD |
| Weighted buoyancy: per-app render-only mass/sink for Classic and Ocean floaters, with Ocean flow attenuation | Done, release-GLES nested-verified on AMD under Ocean; Classic shares the validated render path |
| Nvidia support | Nested (EGL/GLES) verified on a real RTX 3060; standalone DRM backend not yet run natively on Nvidia |
| Nvidia support | Standalone DRM backend verified on a real RTX 3060 (proprietary 595.99.02, two outputs at mixed scale, 2026-09-25); nested verified earlier |
| AUR package | Not yet |

## Hardware verification

- **AMD**: primary development and test hardware. The standalone `udev`/DRM backend, the full water/decoration render stack, swim's real-touchpad gesture path, and Ocean's core navigation (reefs, cameras, freeform drag) are all verified here.
- **Latest standalone AMD health pass (2026-08-13)**: release 0.90.72 on Renoir completed full-output and cursor-overlay screencopy, reversible workspace transitions with ordered IPC events, invalid-action/batch/eval handling, and 100 one-shot plus 20 interrupted subscription connections without descriptor or PSS growth. Doctor remained all-pass with no TideWM panic/error or coredump. One-window PSS settled at 86.8 MiB after the reload/capture pass. The configured 24-fps caustics were the dominant continuous CPU/GPU cost; see `report.md` for the measured A/B and its whole-GPU caveat.
- **Nvidia**: nested backend verified on a real RTX 3060 (proprietary driver): clean EGL/GLES context, correct rendering, no crashes. The standalone DRM backend and its Nvidia overlay-plane workaround are unverified on real Nvidia hardware.
- **Latest standalone NVIDIA pass (2026-09-25)**: 0.90.106 (`13b104e`, `fiw/testing-fixes`) on the RTX 3060 above: doctor all-pass; full-output and cursor-overlay `grim`; unknown actions, an invalid mixed batch, eval syntax errors and a runaway eval loop (execution budget) all failed cleanly; oversized, garbage and truncated IPC requests left the compositor running; 100 one-shot queries plus 20 interrupted subscribers left PSS and descriptors unchanged. Idle ~1% of one core, 131 MiB TideWM VRAM; nine frost windows ~0-1% CPU and 139 MiB. PSS plateaus near 180 MiB after warm-up, of which ~60-95 MiB is `/dev/nvidia*` mappings; open/close cycles returned to the same value with a flat heap, so this is not a leak.
- **Nvidia**: standalone `udev`/DRM backend verified on a real RTX 3060 (proprietary 595.99.02 with the open kernel modules, Gentoo, kernel 7.2.7), logged in from the display manager as a real session. GLES renders on the dGPU with the Radeon iGPU excluded through `gpu { render = vendor:nvidia; exclude = [vendor:amd] }`; two outputs run together (2560×1440@144 at 1.25×, 1920×1080@180 at 1.0×). The overlay-plane workaround branch runs for every output on this driver and scanout is correct on both; no overlay-specific test was done. Frost glass, custom shaders, screencopy, xwayland-satellite, portal and drag and drop worked. Measured cost is in `report.md`. The nested backend was verified earlier on the same card.
- **Hybrid GPU**: selection/exclusion, render-node feedback, cross-device import, hot add/remove, and VT lifecycle compile and unit-test clean. The required end-to-end AMD+Nvidia PRIME/offload run is pending on the handoff laptop; do not call this hardware-verified until a non-primary game buffer displays correctly there at sustained frame rate.
- **Intel**: untested so far.
- **Still nested-only**: Ocean compass/overview, floating-window ocean physics (both tiers), `canvas_pan_button`, `modifier_pan_fingers`.
Expand Down Expand Up @@ -133,7 +135,6 @@ sudo cp share/xdg-desktop-portal/tidewm-portals.conf /usr/share/xdg-desktop-port
- **Non-water motion presets**: smooth window move/resize and workspace motion that works with `water_effects = false`, exposed as Wave-selectable presets and tunable fields. Exact feel and defaults require maintainer approval before implementation.
- **Feel-tuning** across viscosity, sway, depth timings, cascade's drag feel, floating-window ocean physics, and the transition/ripple presets. All ship with working defaults; the actual feel still gets refined against real use.
- **Standalone hardware pass** for what's still nested-only: the Ocean compass/overview, and both floating-window ocean physics tiers.
- **Nvidia native run**: the standalone DRM backend and its overlay-plane workaround still need a real TTY session on Nvidia.
- **AUR package**: build from source for now.
- **Design-pending identity features** (parking lot, needs a design conversation before pickup): tide contexts and workspace depth-moods. Currents, Cascade's pour/drain visual, and non-sorting weighted buoyancy are implemented with their approved contracts.

Expand Down
6 changes: 6 additions & 0 deletions report.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ The finding text below is the original audit evidence. It is intentionally retai

### Validation state

- Live standalone NVIDIA pass on installed release 0.90.106 (`13b104e`, `fiw/testing-fixes` on top of `5c63b90`; RTX 3060, proprietary 595.99.02 with the open kernel modules, Gentoo, kernel 7.2.7; udev, logged in from the display manager; HDMI-A-2 2560×1440@144 at 1.25× and DP-4 1920×1080@180 at 1.0× as reported by IPC; `gpu { render = vendor:nvidia; exclude = [vendor:amd] }` on a machine that also has a Radeon iGPU). This is the first standalone DRM run on Nvidia. `doctor` was all-pass. Full-output and cursor-overlay `grim` captures succeeded per output and for the combined 4960×1440 desktop. Unknown actions, an invalid mixed batch (nothing executed), an eval syntax error and a runaway `while true` eval (stopped by the execution budget) all failed cleanly; a 2 MB request was dropped at the size cap, random bytes got an `invalid request` error, and a truncated request got an error once EOF handling landed (`e4867b1`). 100 one-shot queries plus 20 interrupted subscribers left PSS unchanged (187,957→187,957 KiB) and descriptors flat (97→97). Measurements (20 untouched 1 Hz samples each; VRAM from `nvidia-smi -q -d PIDS`, GPU busy from `nvidia-smi`, which reads high at the 210 MHz idle clock and is not comparable to amdgpu `gpu_busy_percent`): idle with one window ~1% of one core, PSS 179.5 MiB, 131 MiB TideWM VRAM, 309 MiB card total; nine frost-glass Kitty windows plus one ~0-1% CPU, 139 MiB TideWM VRAM (card 653 MiB, the rest is client buffers as on AMD), 4.6 MiB of backdrop texture; back to idle 133 MiB. An earlier run on `5c63b90` with larger tiles measured 156→196 MiB TideWM VRAM for the same nine windows (15.3 MiB of backdrops).
Memory: PSS rises once to a plateau and stays there. `smaps` splits it into ~75-79 MiB heap and ~60-95 MiB of `/dev/nvidia*` mappings (driver-owned GPU buffers mapped into the process, which amdgpu does not show as PSS). Three cycles of opening and closing eight frost windows returned to exactly the same PSS with a flat heap, so this is a high-water mark, not a leak. The doctor's decorated-tier soft limit (280 MiB) was reached after about 4 hours of use on the older build; on Nvidia the tier limits should probably count the driver mappings separately.
Found and fixed on the same branch, each with regression tests: client drag and drop never worked (`WaylandDndGrabHandler` was an empty impl, so Smithay cancelled every source; `2727de9`); glass and other captures drew the wallpaper at `1/scale` size on fractional outputs (`3286393`); `output { }` blocks were ignored on reload (`8ebb259`); `spawn` could not pass arguments containing spaces and tidectl consumed flags after the command (`6517964`); `cursor_hide_after = 0` was rejected (`182df1b`); doctor mislabeled card nodes (`490df73`). Automatic attention depth became opt-in (`c593da7`).
Still open from this pass: user-reported tearing on the HDMI output only, unchanged at scale 1.0, not reproduced as a TideWM fault (cable or HDMI bandwidth at 1440p144 suspected); PDF text selection in Firefox reported as improved but still imprecise; layer glass and layer shaders were not exercised (no layer-shell client running yet).
Screencast: portal screen sharing from native Vesktop (vesktop-bin 1.6.7, Electron on the Wayland ozone backend) through xdg-desktop-portal to the TideWM ScreenCast backend and PipeWire 1.6.8 worked on this Nvidia setup, confirmed visually by the maintainer; the portal reported `AvailableSourceTypes = 7`. A successful session leaves no log line: `src/screencast` only logs failures, so a share is invisible in the session log unless it goes wrong.

- Live standalone AMD pass on installed release 0.90.101 (`384fe98`, Renoir, udev; all geometry and cadence read from IPC): `doctor --json` was all-pass with the expected TideWM portal, PipeWire, xwayland-satellite, and AMD render node. Full-output `grim` captures verified the normal desktop, an in-flight water transition, five-window BSP/master/cascade layouts, pseudo-tiling, floating conversion, invalid-config diagnostics/recovery, and an XWayland GLX client. Twenty rapid workspace reversals settled on workspace 1 with no transition texture retained; the original window remained tiled, focused, and non-fullscreen. Four disposable translucent Kitty clients mapped/unmapped cleanly; five-window frost/water measured 64.6 MiB PSS with 0.9 MiB of active backdrop texture. A 15-second static A/B disabling only 24 fps caustics and focused-border animation changed `animation_active` from true to false and compositor CPU from 8.30% to 1.55%; the three touched Wave files were restored byte-for-byte. An invalid Wave edit kept the previous live config, showed the exact file/line in the first-party overlay, and cleared after restoration. Two identical rounds of 700 one-shot queries plus 100 interrupted subscribers converged to the same 91,006 KiB PSS high-water mark, zero swap, and exactly 93 descriptors, showing bounded first-use/cache growth rather than continued leakage. `glxgears` through xwayland-satellite mapped, rendered at 58.53/58.32 fps against the live 60 Hz mode, and unmapped cleanly. No new TideWM panic/error or coredump appeared. P-12's real-DRM wake path is reconfirmed, but this stripped release exposes neither a caustics-present counter nor a usable external perf/BPF/AMD busy counter, so exact presented-caustics cadence remains pending.

- Live standalone AMD pass on installed release 0.90.72 (`dfc00b7`, Renoir, udev, 1920×1080@60 as reported by IPC; no geometry or mode was supplied by the test): full-output `grim` and `grim -c` captures succeeded; a reversible workspace 1→2→1 switch emitted the expected focus/workspace sequence and left the original window state intact; unknown actions, an invalid mixed batch, and invalid Wave eval all failed cleanly; 100 one-shot queries plus 20 interrupted subscribers left PSS flat at 88,840→88,838 KiB, swap at zero, and descriptors non-growing (91→90). Post-test doctor remained all-pass with no TideWM panic/error or coredump. The live log exposed two config-lowering warnings (`workspace_motion_delay = 0ms`, bracket-list ripple triggers) and `tidectl --socket … report` exposed a global-argument parsing bug; 0.90.101 fixes all three with exact regression tests and an end-to-end release-`tidectl` report run. The original live config values were restored after the performance A/B, and workspace 1 was restored after transition testing.
Expand Down
11 changes: 11 additions & 0 deletions share/shaders/blur-h.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Horizontal half of a separable Gaussian blur. Pair it with blur-v.frag
// as the next stage. Params: radius (float, texels between taps).
vec4 tide_effect(vec2 uv) {
vec2 offset = vec2(u_texel.x * radius, 0.0);
vec4 sum = texture2D(tex, uv) * 0.2270270270;
sum += texture2D(tex, uv + offset * 1.3846153846) * 0.3162162162;
sum += texture2D(tex, uv - offset * 1.3846153846) * 0.3162162162;
sum += texture2D(tex, uv + offset * 3.2307692308) * 0.0702702703;
sum += texture2D(tex, uv - offset * 3.2307692308) * 0.0702702703;
return sum;
}
11 changes: 11 additions & 0 deletions share/shaders/blur-v.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Vertical half of a separable Gaussian blur, run after blur-h.frag.
// Params: radius (float, texels between taps).
vec4 tide_effect(vec2 uv) {
vec2 offset = vec2(0.0, u_texel.y * radius);
vec4 sum = texture2D(tex, uv) * 0.2270270270;
sum += texture2D(tex, uv + offset * 1.3846153846) * 0.3162162162;
sum += texture2D(tex, uv - offset * 1.3846153846) * 0.3162162162;
sum += texture2D(tex, uv + offset * 3.2307692308) * 0.0702702703;
sum += texture2D(tex, uv - offset * 3.2307692308) * 0.0702702703;
return sum;
}
7 changes: 7 additions & 0 deletions share/shaders/edge-mix.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Shows `original` (a textures binding) near the window's edges and the
// stage input in the middle. Params: margin (float, fraction of the size).
vec4 tide_effect(vec2 uv) {
vec2 edge = min(uv, 1.0 - uv);
float inner = smoothstep(0.0, margin, min(edge.x, edge.y));
return mix(texture2D(original, uv), texture2D(tex, uv), inner);
}
7 changes: 7 additions & 0 deletions share/shaders/tint.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Pulls the backdrop toward `tint_color` by `strength`.
// Params: strength (float), tint_color (color).
vec4 tide_effect(vec2 uv) {
vec4 color = texture2D(tex, uv);
vec3 tinted = mix(color.rgb, tint_color.rgb * color.a, strength);
return vec4(tinted, color.a);
}
Loading
Loading