The rule was derived from two data points and generalised into a negation:
owner's Vulkan + mirror=false -> upside-down (Vulkan needs the mirror)
Xvfb Mesa GL + mirror=true -> upside-down (GL does not)
"mirror on everything EXCEPT OpenGL" fits both, and it was wrong about the one
API nobody had run. On the Mac mini M2 the Line Bed comes out upside-down, so
Metal wants mirror=false like OpenGL rather than =true like Vulkan — the
generalisation reached a third platform on the strength of a shape it had never
been tested against.
Kept as a NEGATION with Metal excluded rather than rewritten as "Vulkan ||
D3D". Naming the APIs that DO mirror would silently stop mirroring on any enum
this file failed to list: an unknown member is `undefined` in QML and compares
false, so a typo or a future backend would degrade to "no mirror" with no error.
This way only Metal's behaviour changes and every other backend keeps exactly
what it was verified with.
All four RHI scenes carry the line, because the convention belongs to the
graphics API and not to the scene — a per-scene split would be the surprising
result, not the expected one. Only LineBed made it observable: it is the one
directional scene, so a flip reads instantly, where Plasma and TunnelFlow are
near-radial and hide it. RIBBON IS THE ONE TO CHECK NEXT — it is a spectrogram
with a time axis, so if the convention really is per-API it should have been
flipped too, and if it now looks flipped the rule is per-scene after all.
The three sibling headers said "mirror on Vulkan/Metal/D3D" and were corrected
in place; they had become comments that contradict their own code.
Built and smoked on Linux (gate green). The observation this fixes is the
owner's, on the Mac.
cc
49 lines
2.1 KiB
QML
49 lines
2.1 KiB
QML
// Spectral Ribbon (immersive shader scene, mode 4) — the QML wrapper for
|
|
// the C++ `RibbonItem` (cxx/ribbon_item.*, the persistent-spectrogram
|
|
// QQuickRhiItem). Block A3 of the 2026-08-15 immersive-completion contract
|
|
// (spec 01 §2.4).
|
|
//
|
|
// Its own file (the LineBedScene.qml idiom) so the RHI item's contract is
|
|
// one screen of QML — and so the C++ type name only has to resolve when
|
|
// this file compiles, which the tier gate already ensures never happens
|
|
// where the RHI can't run it (the Loader in ShaderSceneLayer.qml activates
|
|
// only while `root.active && scene === 4`).
|
|
//
|
|
// THE PULSE LAW: the item repaints ONLY from `pulseTick()`, which
|
|
// ShaderSceneLayer calls on the shared shell pulse (QbzShell.pulseMs) with
|
|
// the ribbon frame it stashed on the viz tick. `frame` has no NOTIFY and
|
|
// nothing binds it.
|
|
|
|
import QtQuick
|
|
import QtQuick.Window
|
|
import com.blitzfc.qbz
|
|
|
|
RibbonItem {
|
|
id: root
|
|
|
|
// mirrorVertically: mirror on Vulkan/D3D, NOT on OpenGL and NOT on Metal.
|
|
// This line used to read "Vulkan/Metal/D3D" — Metal had never been run and
|
|
// was assumed to follow Vulkan because it is not OpenGL; on the Mac it
|
|
// flipped. The empirical matrix, and why the rule stays a negation, are in
|
|
// LineBedScene.qml.
|
|
// The feedback passes are self-consistent in texture space; this only
|
|
// affects the composite.
|
|
// Metal sides with OpenGL (2026-08-19) — the full matrix and the reason
|
|
// this stays a negation are in LineBedScene.qml.
|
|
mirrorVertically: GraphicsInfo.api !== GraphicsInfo.OpenGL
|
|
&& GraphicsInfo.api !== GraphicsInfo.Metal
|
|
|
|
// Called by ShaderSceneLayer on the pulse edge ONLY. `f` is the stashed
|
|
// 517-byte frame (QbzShaderScene.ribbonFrame, raw bytes) or null when
|
|
// the viz drain published nothing new (player paused); `peak` is the
|
|
// pack's energyHi (the real-time ceiling line). The repaint still runs
|
|
// on null so the item tracks resizes at the same cadence the sibling
|
|
// scenes tick their clocks.
|
|
function pulseTick(f, peak) {
|
|
if (f !== null)
|
|
frame = f
|
|
energyHi = peak
|
|
update()
|
|
}
|
|
}
|