Skip to content

shaders: the glass written once, its Metal and GLSL generated - #424

Merged
foxnne merged 10 commits into
mainfrom
shaders/one-source
Oct 16, 2026
Merged

foxnne merged 10 commits into
mainfrom
shaders/one-source

Conversation

@foxnne

@foxnne foxnne commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

The glass written once, in HLSL; its Metal and WebGL GLSL generated from it and checked by the shader job #405 added, instead of three programs kept in step by hand. Keeping them in step is where the shader bugs came from: #373's float3(x) was valid Metal and GLSL and broke the HLSL, and the compiled forms went stale twice.

What changes

  • scripts/compile-shaders.sh: every X.hlsl with compiled forms committed beside it, to each of them. SPIR-V, DXIL and Metal by shadercross (the Metal SDL_GPU takes, as the backend's own default shaders already shipped). GLSL ES 3.00 for WebGL2 by spirv-cross from the SPIR-V, with NaN checks relaxed (HLSL's min/max otherwise come out wrapped in isnan tests at every pixel), float precision high, and the names the web binds (vColor, vTextureCoord, uSampler, uTex1, uData). The workflow runs it and compares every form.
  • The glass loads the generated Metal and GLSL (LiquidField); the hand-written liquid_glass.metal and liquid_glass.glsl are gone. The HLSL is the one source and carries the explanations the GLSL had.
  • The web compiles a complete generated source as it is (web/web.js, a #version line), on WebGL2 only: on WebGL1 the program fails and the glass falls back to its meshes (generated loops are not WebGL1's constant-bounded ones).
  • program_example's committed Metal, hand-written to show the interface, is now generated like the rest (same interface).

Verified

  • CI's compile check: the backend's default shaders reproduce byte for byte in all three of SPIR-V, DXIL and Metal with this toolchain.
  • macOS: the same in-app About dialog captured through the agent with the hand-written Metal (ci: compiled shaders are built in CI and checked against their HLSL #405's build) and the generated one (this build): plain glass differs by a mean of 0.18 of 255, at most 2 (the dither); elsewhere only edges, where the dialog's contents sat a fraction of a pixel apart between the two captures.
  • Web (Chromium, WebGL2): the generated GLSL compiles and links against the web's own vertex shader, and uData, uSampler, uTex1 and uMatrix all resolve; this build's menus draw their glass through it, no program failed, and it looks as main's did.
  • zig build, zig build test (91/91 steps), zig build test-integration (53/53 steps), zig build check-web.
  • Not seen: Windows (D3D12) and Linux (Vulkan), as for ci: compiled shaders are built in CI and checked against their HLSL #405; Firefox and Safari on the web.

🤖 Generated with Claude Code

foxnne and others added 9 commits October 14, 2026 08:14
`float3(x)` from one scalar is Metal and GLSL, not HLSL: `(float3)x`. The workflow goes on past a shader that does not compile, uploads what did, and says where a stale one differs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Compiled by this workflow (shadercross 1ff05bec, DXC v1.9.2602), the toolchain the backend's default shaders reproduce with byte for byte. The DXIL's interface (signatures, pipeline state, flags) is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…GLSL included

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… directory

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, high precision

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en ones go

The HLSL is the one source, carrying the GLSL's explanations. On macOS the generated Metal draws the same glass as the hand-written one did (plain glass in a dialog: mean difference 0.18 of 255, at most 2, the dither).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne marked this pull request as ready for review October 16, 2026 15:41
@foxnne
foxnne enabled auto-merge (squash) October 16, 2026 15:46

foxnne commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

This conflicts with main (bd3c719 against today's main), but in one file only: .github/workflows/shaders.yml. #405, which this was stacked on, was squash-merged, so main holds #405's workflow while this branch still carries #405's commits and then rewrites them. In every conflicting hunk this branch's side is the one to keep:

  • Header comment: keep this branch's wording ("compiled forms of it (compiled/{spv,dxil,msl,glsl}/X.*) is compiled by scripts/compile-shaders.sh").
  • Both paths: lists (pull_request and push): keep the added - "scripts/compile-shaders.sh".
  • "Compile and compare": keep the scripts/compile-shaders.sh out run and the find out comparison loop, which replace main's per-file shadercross loop over spv/dxil.

Nothing else conflicts. The compiled SPIR-V and DXIL here are byte-identical to main's, and the HLSL merges to this branch's version, so the committed binaries still match their source after the merge. The other main-side change since you branched (#373's float3(...) → (float3) fix) is already in this branch.

One stale sentence for this PR to fix: LiquidField.publishLook's comment (from #242) still says "the GLSL, the Metal, and the SPIR-V and DXIL compiled from the HLSL — as long as the last two are compiled again whenever the HLSL changes". With this PR all four are compiled from the HLSL by scripts/compile-shaders.sh and checked by the workflow, so it could say that instead.


Generated by Claude Code

#405 landed on main; the workflow keeps this branch's version, which is #405's with the compile script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne merged commit 357a063 into main Oct 16, 2026
12 checks passed
@foxnne
foxnne deleted the shaders/one-source branch October 16, 2026 16:07
@foxnne foxnne mentioned this pull request Oct 16, 2026
2 of 8 tasks
foxnne added a commit that referenced this pull request Oct 16, 2026
## What changes

`sdk_version` goes from 0.2.27 to 0.2.28. Merging this tags
`sdk-v0.2.28` and publishes its tarball.

**Why now:**
- **Agents:** fizzyedit/agent#16 (an agent's edits don't lock the
document, and a document an agent makes opens beside the person) needs
#442's `OpenOptions.beside`.
- **Pixi's CI:** fizzy `main` dropped `setExplorerNewFilePath` in #449,
so every pixi PR fails the `fizzy-main` check until pixi repins to a
release that has `files.reveal`.
- **Untitled sprites:** #447's `loadUntitledDocument` lets a plugin's
untitled documents come back from a crash. Pixi needs a release carrying
it to implement that.

## SDK impact

- [ ] None
- [ ] Core-only or additive: reaches plugins at the next SDK release
- [ ] Fingerprint moved: recorded in `sdk/src/version.zig`,
`sdk_version` untouched, PR labelled `sdk`
- [x] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the
last `sdk-v*` tag

**`sdk` PRs since `sdk-v0.2.27`:**
- **#442:** a plugin can open a document beside the person.
`EditorAPI.OpenOptions.beside` (one new defaulted field), and
`Host.openBeside` / `endOpenBeside`, so a command run on someone's
behalf opens what it makes beside them.
- **#447:** an untitled document survives a crash or a restart. A new
optional vtable member, `loadUntitledDocument`, for an owner whose
untitled documents should come back. `Plugin.bytesPath` is the path to
hand `documentBytes` for a document's bytes outside a save.
- **#456:** owners report a document's steps, and apply recorded ones
(`Step`, a vtable `step_format` and `applyStep`, Host events). All
additive: `step_format` defaults to 0 and `applyStep` to null, so an
owner that records nothing changes nothing.
- **#449:** the file tree keeps its own folders, and `files.reveal`
shows new files. `Host.setExplorerNewFilePath(path)` becomes
`host.getServiceTyped(sdk.services.files.Api)` then `.reveal(path)`. The
`files` service is now version 2. `explorerBranchIsOpen` and
`setExplorerBranchOpen` are gone, with no replacement.

**Reaching plugins, no fingerprint move:**
- **#444:** `core`: a canvas the person hasn't moved stays fitted to its
pane, refitting live as the pane changes size. `fitToView` animates
there.
- **#446:** `core`: `core.widgets.glass_button`, round glass buttons and
viewport bubbles.
- **#450:** `core`: a kept frost reads again under a region's swap.
- **#451:** `core`: a row carried out of a tree is the OS's glass, and
stays still.
- **#453:** `tape`: steps of a document's history, and a compressed
binary form. **`tape.binary.encode` takes a third argument, `Options`**
(`.{}` keeps the old output).
- **#457:** `tape`: a live tape with no ops finishes instead of
crashing.
- **#424:** `core`: the glass shader written once in HLSL, its Metal and
GLSL generated.
- **#458:** `sdk/src/dylib.zig`: a refused plugin says whether its build
mode or its SDK shape differs (it blamed the mode for both).
- **#435:** `tape`, `replay`: `builtin.target.os`, `.cpu` and `.abi` in
place of `builtin.os`.

Not in this release: #437 (the Zig 0.17 switch, labelled `sdk`) merged
into the `zig/0.17` integration branch, not `main`. `main` still builds
with Zig 0.16.

## Verified

- [x] macOS:
- On this head, `zig build test-sdk-version` passes, and
`scripts/pack-sdk.sh` packs `fizzy-sdk-v0.2.28.tar.gz`.
- Rebased on `main` after #456, #453, #457, #424 and #458. Re-checked on
the new head: `zig build test-sdk-version` passes and
`scripts/pack-sdk.sh` packs.
- fizzyedit/agent `main` doesn't build against it:
`src/Recording.zig:127` calls `tape.binary.encode(gpa, t)`, and #453
added an `Options` argument (its notes say no plugin calls it; the
agent's recording does). The fix is `encode(self.gpa, t, .{})`, in the
agent's repin.
- fizzyedit/pixi `main` doesn't build against it:
`src/dialogs/NewFile.zig:230` calls the removed
`setExplorerNewFilePath`. Expected; see Follow-ups.
- [ ] Windows:
- [ ] Linux: CI.
- [ ] Web:

## Follow-ups

- The repin automation opens `sdk: repin to fizzy SDK 0.2.28` PRs in the
store plugins whose build needs it.
- **pixi's** opens as a draft, since its build breaks. It needs #449's
change: `NewFile.zig` calls `files.reveal`. #449's notes say that change
is ready locally, to push with this repin. **atlas** needs the same, for
New Note.
- **agent's** repin opens as a draft too: it needs
`tape.binary.encode(self.gpa, t, .{})` in `src/Recording.zig` (#453).
- **Then:** once agent repins, fizzyedit/agent#16 can go ready, and pixi
can implement `loadUntitledDocument`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant