fix: a saved RTX choice falls back to a clear message where RTX is absent; a failed add-on install is offered again; RTX needs Turing or newer

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-10-14 22:45:41 +03:00
co-authored by Claude Sonnet 5.5
parent 30c4b62eb8
commit aef6e08e55
6 changed files with 13 additions and 9 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ export const modules = (app) => function mount() {
};
const addonRow = (a) => {
const pending = a.state === "pending";
const hint = [a.hint, pending ? RESTART_HINT : null, a.failed ? `Could not install: ${a.failed}\nSwitch it off and on to try again.` : null].filter(Boolean).join("\n");
const hint = [a.hint, pending ? RESTART_HINT : null, a.failed ? `Could not install: ${a.failed}\nSwitch it on to try again.` : null].filter(Boolean).join("\n");
return c.toggle.default({ label: a.title, hint, checked: a.state === "installed" || pending, onChange: (on) => flip(a, on) });
};
+5 -3
View File
@@ -35,7 +35,9 @@ export default {
}
async function run(media, pressed = false) {
const model = rtxOn() ? RTX : p.pref("upscaleModel", "");
// RTX only counts when this machine has it on right now: a project saved elsewhere may still say "rtx"
const rtxHere = rtxOn() && await app.api.upscaleModels().then((r) => Boolean(r.rtx)).catch(() => false);
const model = rtxOn() ? (rtxHere ? RTX : "") : p.pref("upscaleModel", "");
if (!media || media.subfolder === UPSCALED) return; // never upscale twice
if (!model) return pressed ? suggest() : c.toast.warn({ text: "Upscale: pick a model or NVIDIA RTX Super Resolution in Settings ▸ Editor." });
const key = keyOf(media), pid = p.project.id;
@@ -86,8 +88,8 @@ export default {
options: [{ value: "scale", label: "Scale by multiplier" }, { value: "size", label: "Target dimensions" }] }) }),
...(o.resize === "scale"
? [c.settingsRow.default({ label: "Scale", hint: "1 to 4 times.", control: c.slider.readout({ label: "Scale", value: o.scale, min: 1, max: 4, step: 0.05, precision: 2, onCommit: (v) => set("scale", v) }) })]
: [c.settingsRow.default({ label: "Width", hint: "In pixels, 64 to 8192.", control: c.number.md({ label: "Width", value: o.width, min: 64, max: 8192, step: 8, precision: 0, onChange: (v) => set("width", v) }) }),
c.settingsRow.default({ label: "Height", hint: "In pixels, 64 to 8192.", control: c.number.md({ label: "Height", value: o.height, min: 64, max: 8192, step: 8, precision: 0, onChange: (v) => set("height", v) }) })])]),
: [c.settingsRow.default({ label: "Width", hint: "In pixels, 64 to 8192. Rounded to a multiple of 8.", control: c.number.md({ label: "Width", value: o.width, min: 64, max: 8192, step: 8, precision: 0, onChange: (v) => set("width", v) }) }),
c.settingsRow.default({ label: "Height", hint: "In pixels, 64 to 8192. Rounded to a multiple of 8.", control: c.number.md({ label: "Height", value: o.height, min: 64, max: 8192, step: 8, precision: 0, onChange: (v) => set("height", v) }) })])]),
c.settingsRow.default({ label: "Strength", hint: "How much of the effect to apply.", control: c.slider.readout({ label: "Strength", value: o.strength, min: 0, max: 1, step: 0.05, precision: 2, onCommit: (v) => set("strength", v) }) }),
c.toggle.default({ label: "10-bit processing", hint: "More precise colour inside the upscale. The saved video stays 8-bit.", checked: o.tenbit, onChange: (v) => set("tenbit", v) }),
];
+2 -2
View File
@@ -33,7 +33,7 @@ def _nvidia_gpu() -> bool:
return False
try:
import torch
return bool(torch.cuda.is_available() and torch.version.cuda)
return bool(torch.cuda.is_available() and torch.version.cuda and torch.cuda.get_device_capability() >= (7, 5)) # RTX starts at Turing
except Exception: # noqa: BLE001
return False
@@ -94,7 +94,7 @@ def listing() -> list[dict]:
continue
here, want = _present(a), a["id"] in wanted
out.append({"id": a["id"], "title": a["title"], "hint": a["hint"],
"state": ("installed" if here else "pending") if want else ("disabled" if here else "available"),
"state": ("installed" if here else "available" if failed.get(a["id"]) else "pending") if want else ("disabled" if here else "available"), # a failed install is offered again, not "pending"
"failed": failed.get(a["id"]) if not here else None})
return out
+2 -1
View File
@@ -71,7 +71,8 @@ def test_a_failed_install_is_said_and_not_retried_until_switched_off_and_on(here
monkeypatch.setattr(update, "install_lines", lambda lines: ran.append(1) or {"ok": False, "detail": "no wheel for this Python"})
addons.set_wanted("nvidia_vsr", True)
addons.install_wanted()
assert next(a for a in addons.listing() if a["id"] == "nvidia_vsr")["failed"] == "no wheel for this Python"
row = next(a for a in addons.listing() if a["id"] == "nvidia_vsr")
assert row["failed"] == "no wheel for this Python" and row["state"] == "available", "a failed install must not read as waiting for a restart"
addons.install_wanted()
assert len(ran) == 1, "a broken install would be retried on every start"
addons.set_wanted("nvidia_vsr", False)
+2 -1
View File
@@ -32,7 +32,8 @@ def options(raw) -> dict:
for key, lo, hi in (("scale", 1.0, 4.0), ("strength", 0.0, 1.0), ("width", 64, 8192), ("height", 64, 8192)):
if not isinstance(o[key], (int, float)) or isinstance(o[key], bool) or not lo <= o[key] <= hi:
raise ValueError(f"RTX {key} must be a number from {lo} to {hi}.")
o["tenbit"] = bool(o["tenbit"])
if not isinstance(o["tenbit"], bool):
raise ValueError("RTX tenbit must be true or false.")
return o
+1 -1
View File
@@ -51,7 +51,7 @@ from modules.output.render import rtx # noqa: E402
def test_rtx_options_are_checked_by_name():
assert rtx.options("{}")["quality"] == "ULTRA"
assert rtx.options({"scale": 3, "quality": "DEBLUR_HIGH"})["scale"] == 3
for bad in ({"quality": "ULTRAA"}, {"scale": 5}, {"strength": -1}, {"resize": "huge"}, {"width": 8}):
for bad in ({"quality": "ULTRAA"}, {"scale": 5}, {"strength": -1}, {"resize": "huge"}, {"width": 8}, {"tenbit": "false"}):
with pytest.raises(ValueError):
rtx.options(bad)
with pytest.raises(ValueError):