diff --git a/app/ui/features/settings/modules.js b/app/ui/features/settings/modules.js index 6354cfa7..05c59f08 100644 --- a/app/ui/features/settings/modules.js +++ b/app/ui/features/settings/modules.js @@ -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) }); }; diff --git a/app/ui/features/upscale/index.js b/app/ui/features/upscale/index.js index c73a2845..d8b90483 100644 --- a/app/ui/features/upscale/index.js +++ b/app/ui/features/upscale/index.js @@ -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) }), ]; diff --git a/core/addons.py b/core/addons.py index ccbd9462..7448cbb2 100644 --- a/core/addons.py +++ b/core/addons.py @@ -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 diff --git a/core/tests/test_addons.py b/core/tests/test_addons.py index 8882e037..2dd086c6 100644 --- a/core/tests/test_addons.py +++ b/core/tests/test_addons.py @@ -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) diff --git a/modules/output/render/rtx.py b/modules/output/render/rtx.py index cba39c38..4a88d0e4 100644 --- a/modules/output/render/rtx.py +++ b/modules/output/render/rtx.py @@ -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 diff --git a/modules/output/render/tests/test_upscale.py b/modules/output/render/tests/test_upscale.py index 0956cbaa..a00a1b7b 100644 --- a/modules/output/render/tests/test_upscale.py +++ b/modules/output/render/tests/test_upscale.py @@ -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):