summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--py_modules/lsfg_vk/config_schema.py33
-rw-r--r--py_modules/lsfg_vk/config_schema_generated.py14
-rw-r--r--py_modules/lsfg_vk/configuration.py25
-rw-r--r--py_modules/lsfg_vk/flatpak_service.py12
-rw-r--r--py_modules/lsfg_vk/installation.py34
-rw-r--r--scripts/generate_python_boilerplate.py1
-rw-r--r--shared_config.py28
-rw-r--r--src/components/ConfigurationSection.tsx42
-rw-r--r--src/config/configSchema.ts4
-rw-r--r--src/config/generatedConfigSchema.ts40
-rw-r--r--tests/test_lsfg_v2_migration.py98
12 files changed, 61 insertions, 278 deletions
diff --git a/README.md b/README.md
index a3b4257..91eda92 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ A Decky plugin that streamlines the installation of **lsfg-vk** ([Lossless Scali
1. **Purchase and install** [Lossless Scaling](https://store.steampowered.com/app/993090/Lossless_Scaling/) from Steam
2. **Open the plugin** from the Decky menu
3. **Click "Install lsfg-vk"** to automatically set up the lsfg-vk vulkan layer
-4. **Configure settings** using the plugin's UI - adjust FPS multiplier, flow scale, performance mode, FP16, automatic profile matching, and optional GPU selection
+4. **Configure settings** using the plugin's UI - adjust FPS multiplier, flow scale, performance mode, FP16, and launch workarounds
5. **Apply launch option** to games you want to use frame generation with:
- Add `~/lsfg %command%` to your game's launch options in Steam Properties
- Or use the "Launch Option Clipboard" button in the plugin to copy the command
@@ -50,9 +50,6 @@ The plugin provides several configuration options to optimize frame generation f
- **Flow Scale**: Adjust motion estimation quality (lower = better performance, higher = better quality)
- **Performance Mode**: Uses a lighter processing model
- **Allow FP16**: Enable half-precision acceleration; disable it for older NVIDIA GPUs
-- **Active In**: Optionally match a profile to executable names, Wine executables, or process names
-- **Automatic Profile Matching**: Explicitly let lsfg-vk choose an `Active In` profile; otherwise the Decky-selected profile is always forced
-- **GPU**: Optionally select the GPU by name, vendor/device ID, or PCI bus ID
- **Disable Frame Generation**: Disable the layer for the next launch without creating an invalid 1x profile
## Feedback and Support
@@ -65,7 +62,6 @@ For per-game feedback and community support, please join the [decky-lsfg-vk Disc
- Ensure you've added `~/lsfg %command%` to your game's launch options
- Check that the Lossless Scaling DLL was detected correctly in the plugin
- Try enabling Performance Mode if you're experiencing crashes
-- Disable **Automatic Profile Matching** to force the profile selected in Decky, even when it has an `Active In` value
- Make sure your game is running in fullscreen mode for best results
**Performance issues?**
@@ -88,7 +84,7 @@ The plugin:
- **FPS Multiplier**: Choose 2x, 3x, or 4x frame generation
- **Flow Scale**: Adjust motion estimation quality vs performance
- **Performance Mode**: Use lighter processing for better performance
- - **FP16, Active In, GPU, and explicit automatic matching**: Use v2 profile parameters without ambiguous profile selection
+ - **FP16 and explicit Decky profile selection**: Use v2 profile parameters without ambiguous profile selection
- **Launch workarounds**: Preserve existing DXVK, Gamescope, Zink, MangoHud, and vkBasalt launch options
- **Hot-reloading**: Multiplier, flow scale, and performance-mode changes are reloaded while games run; other changes may require a swapchain recreation or restart
- Easy uninstallation that removes all installed files when no longer needed
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py
index f8a9fe6..184d27a 100644
--- a/py_modules/lsfg_vk/config_schema.py
+++ b/py_modules/lsfg_vk/config_schema.py
@@ -1,7 +1,6 @@
"""lsfg-vk v2 configuration and Decky profile management."""
import json
-import logging
import re
import sys
import tomllib
@@ -31,12 +30,12 @@ CONFIG_SCHEMA: Dict[str, ConfigField] = {
)
for name, definition in CONFIG_SCHEMA_DEF.items()
}
-GLOBAL_SECTION_FIELDS = {"dll", "allow_fp16"}
+GLOBAL_SECTION_FIELDS = {"allow_fp16"}
SCRIPT_ONLY_FIELDS = {
name for name, definition in CONFIG_SCHEMA_DEF.items()
if definition["location"] == "script"
}
-PROFILE_TOML_FIELDS = {"active_in", "gpu", "multiplier", "flow_scale", "performance_mode", "pacing"}
+PROFILE_TOML_FIELDS = {"multiplier", "flow_scale", "performance_mode", "pacing"}
DEFAULT_PROFILE_NAME = "decky-lsfg-vk"
CURRENT_PROFILE_COMMENT = re.compile(r'^\s*#\s*decky-current-profile\s*=\s*"([^"]+)"\s*$')
@@ -59,18 +58,6 @@ class ConfigurationManager:
return cast(ConfigurationData, dict(get_defaults()))
@staticmethod
- def get_defaults_with_dll_detection(dll_detection_service=None) -> ConfigurationData:
- defaults = ConfigurationManager.get_defaults()
- if dll_detection_service:
- try:
- result = dll_detection_service.check_lossless_scaling_dll()
- if result.get("detected") and result.get("path"):
- defaults["dll"] = result["path"]
- except (OSError, IOError, KeyError, TypeError) as error:
- logging.getLogger(__name__).debug("DLL detection failed: %s", error)
- return defaults
-
- @staticmethod
def get_field_names() -> list[str]:
return list(CONFIG_SCHEMA)
@@ -108,8 +95,6 @@ class ConfigurationManager:
if field not in profile:
continue
value = profile[field]
- if field == "active_in" and isinstance(value, list):
- value = ", ".join(str(item) for item in value)
config[field] = value
for field in GLOBAL_SECTION_FIELDS:
if field in global_config:
@@ -134,20 +119,11 @@ class ConfigurationManager:
"",
"[global]",
]
- if global_config.get("dll"):
- lines.append(f"dll = {_toml_string(str(global_config['dll']))}")
lines.append(f"allow_fp16 = {str(bool(global_config.get('allow_fp16', True))).lower()}")
for profile_name, raw_config in profile_data["profiles"].items():
config = ConfigurationManager.validate_config({**raw_config, **global_config})
lines.extend(["", "[[profile]]", f"name = {_toml_string(profile_name)}"])
- active_in = [entry.strip() for entry in config["active_in"].split(",") if entry.strip()]
- if len(active_in) == 1:
- lines.append(f"active_in = {_toml_string(active_in[0])}")
- elif active_in:
- lines.append("active_in = [" + ", ".join(_toml_string(entry) for entry in active_in) + "]")
- if config["gpu"]:
- lines.append(f"gpu = {_toml_string(config['gpu'])}")
lines.extend([
f"multiplier = {config['multiplier']}",
f"flow_scale = {config['flow_scale']}",
@@ -160,7 +136,6 @@ class ConfigurationManager:
def _profile_data_from_v1(data: Dict[str, Any]) -> ProfileData:
old_global = data.get("global", {})
global_config = {
- "dll": str(old_global.get("dll", "")),
"allow_fp16": not bool(old_global.get("no_fp16", False)),
}
profiles: Dict[str, ConfigurationData] = {}
@@ -199,7 +174,6 @@ class ConfigurationManager:
raw_global = dict(data.get("global", {}))
global_config = {
- "dll": str(raw_global.get("dll", "")),
"allow_fp16": bool(raw_global.get("allow_fp16", True)),
}
profiles: Dict[str, ConfigurationData] = {}
@@ -260,9 +234,6 @@ class ConfigurationManager:
raise ValueError(f"Profile '{normalized}' already exists")
source = source_profile if source_profile in profile_data["profiles"] else profile_data["current_profile"]
new_profile = dict(profile_data["profiles"][source])
- # A cloned profile may inherit Active In values that collide with its source.
- # Force the newly selected profile until the user explicitly enables native matching.
- new_profile["use_native_matching"] = False
return ProfileData(
current_profile=profile_data["current_profile"],
profiles={**profile_data["profiles"], normalized: new_profile},
diff --git a/py_modules/lsfg_vk/config_schema_generated.py b/py_modules/lsfg_vk/config_schema_generated.py
index 9cfda51..db337fe 100644
--- a/py_modules/lsfg_vk/config_schema_generated.py
+++ b/py_modules/lsfg_vk/config_schema_generated.py
@@ -13,15 +13,11 @@ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from shared_config import CONFIG_SCHEMA_DEF, ConfigFieldType
# Field name constants for type-safe access
-DLL = "dll"
ALLOW_FP16 = "allow_fp16"
MULTIPLIER = "multiplier"
FLOW_SCALE = "flow_scale"
PERFORMANCE_MODE = "performance_mode"
PACING = "pacing"
-ACTIVE_IN = "active_in"
-USE_NATIVE_MATCHING = "use_native_matching"
-GPU = "gpu"
DISABLE_LSFGVK = "disable_lsfgvk"
DXVK_FRAME_RATE = "dxvk_frame_rate"
ENABLE_WOW64 = "enable_wow64"
@@ -35,15 +31,11 @@ ENABLE_ZINK = "enable_zink"
class ConfigurationData(TypedDict):
"""Type-safe configuration data structure - AUTO-GENERATED"""
- dll: str
allow_fp16: bool
multiplier: int
flow_scale: float
performance_mode: bool
pacing: str
- active_in: str
- use_native_matching: bool
- gpu: str
disable_lsfgvk: bool
dxvk_frame_rate: int
enable_wow64: bool
@@ -70,8 +62,6 @@ def get_script_parsing_logic():
value = value.strip()
# Auto-generated parsing logic:
- if key == "DECKY_LSFGVK_AUTO_PROFILE":
- script_values["use_native_matching"] = value == "1"
if key == "DISABLE_LSFGVK":
script_values["disable_lsfgvk"] = value == "1"
if key == "DXVK_FRAME_RATE":
@@ -106,8 +96,6 @@ def get_script_generation_logic():
"""Return the script generation logic as a callable"""
def generate_script_lines(config):
lines = []
- if config.get("use_native_matching", False):
- lines.append("export DECKY_LSFGVK_AUTO_PROFILE=1")
if config.get("disable_lsfgvk", False):
lines.append("export DISABLE_LSFGVK=1")
dxvk_frame_rate = config.get("dxvk_frame_rate", 0)
@@ -133,4 +121,4 @@ def get_script_generation_logic():
return generate_script_lines
-ALL_FIELDS = ['dll', 'allow_fp16', 'multiplier', 'flow_scale', 'performance_mode', 'pacing', 'active_in', 'use_native_matching', 'gpu', 'disable_lsfgvk', 'dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt', 'force_enable_vkbasalt', 'enable_wsi', 'enable_zink']
+ALL_FIELDS = ['allow_fp16', 'multiplier', 'flow_scale', 'performance_mode', 'pacing', 'disable_lsfgvk', 'dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt', 'force_enable_vkbasalt', 'enable_wsi', 'enable_zink']
diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py
index 6511745..490689c 100644
--- a/py_modules/lsfg_vk/configuration.py
+++ b/py_modules/lsfg_vk/configuration.py
@@ -17,10 +17,8 @@ class ConfigurationService(BaseService):
"""Service for managing TOML-based lsfg configuration"""
@staticmethod
- def _profile_selection_lines(profile_name: str, config: ConfigurationData) -> list[str]:
- """Force the selected profile unless the user explicitly opts into native matching."""
- if config.get("use_native_matching", False):
- return ["# lsfg-vk will select a matching profile from Active In."]
+ def _profile_selection_lines(profile_name: str) -> list[str]:
+ """Force the profile selected in Decky for every launch."""
return [f"export LSFGVK_PROFILE={shlex.quote(profile_name)}"]
def get_config(self) -> ConfigurationResponse:
@@ -31,9 +29,7 @@ class ConfigurationService(BaseService):
"""
try:
if not self.config_file_path.exists():
- from .dll_detection import DllDetectionService
- dll_service = DllDetectionService(self.log)
- toml_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
+ toml_config = ConfigurationManager.get_defaults()
else:
content = self.config_file_path.read_text(encoding='utf-8')
toml_config = ConfigurationManager.parse_toml_content(content)
@@ -58,9 +54,7 @@ class ConfigurationService(BaseService):
except Exception as e:
error_msg = f"Error parsing config file: {str(e)}"
self.log.error(error_msg)
- from .dll_detection import DllDetectionService
- dll_service = DllDetectionService(self.log)
- config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
+ config = ConfigurationManager.get_defaults()
return self._success_response(ConfigurationResponse,
f"Using default configuration due to parse error: {str(e)}",
config=config)
@@ -134,7 +128,7 @@ class ConfigurationService(BaseService):
lines.extend([
f"export LSFGVK_CONFIG={shlex.quote(str(self.config_file_path))}",
- *self._profile_selection_lines(DEFAULT_PROFILE_NAME, config),
+ *self._profile_selection_lines(DEFAULT_PROFILE_NAME),
])
lines.extend(self._generate_game_launch_lines())
@@ -166,7 +160,7 @@ class ConfigurationService(BaseService):
lines.extend([
f"export LSFGVK_CONFIG={shlex.quote(str(self.config_file_path))}",
- *self._profile_selection_lines(current_profile, cast(ConfigurationData, merged_config)),
+ *self._profile_selection_lines(current_profile),
])
lines.extend(self._generate_game_launch_lines())
@@ -193,14 +187,11 @@ class ConfigurationService(BaseService):
def _get_profile_data(self) -> ProfileData:
"""Get current profile data from config file"""
if not self.config_file_path.exists():
- from .dll_detection import DllDetectionService
- dll_service = DllDetectionService(self.log)
- default_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
+ default_config = ConfigurationManager.get_defaults()
return ProfileData(
current_profile=DEFAULT_PROFILE_NAME,
profiles={DEFAULT_PROFILE_NAME: default_config},
global_config={
- "dll": default_config.get("dll", ""),
"allow_fp16": default_config.get("allow_fp16", True)
}
)
@@ -416,7 +407,7 @@ class ConfigurationService(BaseService):
profile_data["profiles"][profile_name] = config
# Update global config fields if they're in the config
- for field_name in ["dll", "allow_fp16"]:
+ for field_name in ["allow_fp16"]:
if field_name in config:
profile_data["global_config"][field_name] = config[field_name]
diff --git a/py_modules/lsfg_vk/flatpak_service.py b/py_modules/lsfg_vk/flatpak_service.py
index 8410496..da01d8b 100644
--- a/py_modules/lsfg_vk/flatpak_service.py
+++ b/py_modules/lsfg_vk/flatpak_service.py
@@ -8,7 +8,6 @@ from pathlib import Path
from typing import Dict, Any, List, Optional
from .base_service import BaseService
-from .config_schema import ConfigurationManager
from .constants import (
BIN_DIR,
FLATPAK_23_08_FILENAME,
@@ -63,17 +62,6 @@ class FlatpakService(BaseService):
"""Return the v2 config directory and directory containing Lossless.dll."""
config_path = str(self.config_dir)
dll_directory = str(self.user_home / ".local/share/Steam/steamapps/common")
- if not self.config_file_path.exists():
- return config_path, dll_directory
- try:
- profile_data = ConfigurationManager.parse_toml_content_multi_profile(
- self.config_file_path.read_text(encoding="utf-8")
- )
- configured_dll = profile_data["global_config"].get("dll", "")
- if configured_dll:
- dll_directory = str(Path(str(configured_dll)).parent)
- except (OSError, ValueError, KeyError, TypeError) as error:
- self.log.debug("Could not read configured DLL path for Flatpak override: %s", error)
return config_path, dll_directory
def _get_bundled_extension_path(self, version: str) -> Path:
diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py
index 1ba091a..899804b 100644
--- a/py_modules/lsfg_vk/installation.py
+++ b/py_modules/lsfg_vk/installation.py
@@ -183,9 +183,6 @@ class InstallationService(BaseService):
def _create_config_file(self) -> ProfileData:
"""Migrate v1 once, normalize v2, and retain Decky launch workarounds."""
- from .dll_detection import DllDetectionService
-
- dll_service = DllDetectionService(self.log)
profile_data: ProfileData
was_legacy = False
self._config_recovery_backup = None
@@ -201,23 +198,23 @@ class InstallationService(BaseService):
self._config_recovery_backup,
error,
)
- default = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
+ default = ConfigurationManager.get_defaults()
profile_data = ProfileData(
current_profile="decky-lsfg-vk",
profiles={"decky-lsfg-vk": default},
- global_config={"dll": default["dll"], "allow_fp16": default["allow_fp16"]},
+ global_config={"allow_fp16": default["allow_fp16"]},
)
if was_legacy:
self._backup_legacy_config(content)
else:
- default = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
+ default = ConfigurationManager.get_defaults()
profile_data = ProfileData(
current_profile="decky-lsfg-vk",
profiles={"decky-lsfg-vk": default},
- global_config={"dll": default["dll"], "allow_fp16": default["allow_fp16"]},
+ global_config={"allow_fp16": default["allow_fp16"]},
)
- profile_data = self._merge_config_with_defaults(profile_data, dll_service)
+ profile_data = self._merge_config_with_defaults(profile_data)
script_values = self._read_script_values()
current_profile = profile_data["current_profile"]
profile_data["profiles"][current_profile] = ConfigurationManager.merge_config_with_script(
@@ -267,21 +264,12 @@ class InstallationService(BaseService):
script = configuration_service._generate_script_content_for_profile(profile_data)
self._write_file(self.lsfg_launch_script_path, script, 0o755)
- def _merge_config_with_defaults(self, existing: ProfileData, dll_service) -> ProfileData:
- defaults = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
- global_config = dict(existing.get("global_config", {}))
- configured_dll = str(global_config.get("dll", "") or "").strip()
- detected_dll = str(defaults.get("dll", "") or "").strip()
- if configured_dll and Path(configured_dll).is_file():
- global_config["dll"] = configured_dll
- elif detected_dll and Path(detected_dll).is_file():
- self.log.warning("Replacing stale configured Lossless.dll path %s with %s", configured_dll, detected_dll)
- global_config["dll"] = detected_dll
- else:
- if configured_dll:
- self.log.warning("Clearing stale configured Lossless.dll path %s", configured_dll)
- global_config["dll"] = ""
- global_config.setdefault("allow_fp16", defaults["allow_fp16"])
+ def _merge_config_with_defaults(self, existing: ProfileData) -> ProfileData:
+ defaults = ConfigurationManager.get_defaults()
+ existing_global = existing.get("global_config", {})
+ global_config = {
+ "allow_fp16": existing_global.get("allow_fp16", defaults["allow_fp16"]),
+ }
profiles: Dict[str, Any] = {}
for name, existing_config in existing.get("profiles", {}).items():
diff --git a/scripts/generate_python_boilerplate.py b/scripts/generate_python_boilerplate.py
index 5027023..1445b79 100644
--- a/scripts/generate_python_boilerplate.py
+++ b/scripts/generate_python_boilerplate.py
@@ -35,7 +35,6 @@ def get_env_var_name(field_name: str) -> str:
"disable_steamdeck_mode": "SteamDeck",
"mangohud_workaround": "MANGOHUD",
"disable_lsfgvk": "DISABLE_LSFGVK",
- "use_native_matching": "DECKY_LSFGVK_AUTO_PROFILE",
"disable_vkbasalt": "DISABLE_VKBASALT",
"force_enable_vkbasalt": "ENABLE_VKBASALT",
"enable_wsi": "ENABLE_GAMESCOPE_WSI",
diff --git a/shared_config.py b/shared_config.py
index 77b4c01..f89272f 100644
--- a/shared_config.py
+++ b/shared_config.py
@@ -12,13 +12,6 @@ class ConfigFieldType(str, Enum):
CONFIG_SCHEMA_DEF = {
- "dll": {
- "name": "dll",
- "fieldType": ConfigFieldType.STRING,
- "default": "",
- "description": "optional full path to Lossless.dll; leave blank for automatic discovery",
- "location": "global",
- },
"allow_fp16": {
"name": "allow_fp16",
"fieldType": ConfigFieldType.BOOLEAN,
@@ -54,27 +47,6 @@ CONFIG_SCHEMA_DEF = {
"description": "frame pacing mode (currently only none is supported)",
"location": "profile",
},
- "active_in": {
- "name": "active_in",
- "fieldType": ConfigFieldType.STRING,
- "default": "",
- "description": "optional executable or process names, separated by commas",
- "location": "profile",
- },
- "use_native_matching": {
- "name": "use_native_matching",
- "fieldType": ConfigFieldType.BOOLEAN,
- "default": False,
- "description": "let lsfg-vk choose a profile from Active In instead of forcing the selected profile",
- "location": "script",
- },
- "gpu": {
- "name": "gpu",
- "fieldType": ConfigFieldType.STRING,
- "default": "",
- "description": "optional GPU name, vendor:device ID, or PCI bus ID",
- "location": "profile",
- },
"disable_lsfgvk": {
"name": "disable_lsfgvk",
"fieldType": ConfigFieldType.BOOLEAN,
diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx
index 538975e..1ab570b 100644
--- a/src/components/ConfigurationSection.tsx
+++ b/src/components/ConfigurationSection.tsx
@@ -1,10 +1,10 @@
-import { PanelSectionRow, ToggleField, SliderField, ButtonItem, TextField } from "@decky/ui";
+import { PanelSectionRow, ToggleField, SliderField, ButtonItem } from "@decky/ui";
import { useState, useEffect } from "react";
import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri";
import { ConfigurationData } from "../config/configSchema";
import {
- ACTIVE_IN, ALLOW_FP16, DISABLE_LSFGVK, DLL, FLOW_SCALE, GPU,
- PERFORMANCE_MODE, USE_NATIVE_MATCHING, DXVK_FRAME_RATE, DISABLE_STEAMDECK_MODE,
+ ALLOW_FP16, DISABLE_LSFGVK, FLOW_SCALE,
+ PERFORMANCE_MODE, DXVK_FRAME_RATE, DISABLE_STEAMDECK_MODE,
MANGOHUD_WORKAROUND, DISABLE_VKBASALT, FORCE_ENABLE_VKBASALT, ENABLE_WSI, ENABLE_ZINK
} from "../config/generatedConfigSchema";
@@ -114,15 +114,6 @@ export function ConfigurationSection({
{!configCollapsed && (
<>
<PanelSectionRow>
- <TextField
- label="Lossless.dll Path"
- description="Optional full path to Lossless.dll. Leave blank for lsfg-vk automatic discovery."
- value={config.dll}
- onChange={(event) => onConfigChange(DLL, event.currentTarget.value)}
- />
- </PanelSectionRow>
-
- <PanelSectionRow>
<SliderField
label={`Flow Scale (${Math.round(config.flow_scale * 100)}%)`}
description="Lowers internal motion estimation resolution, improving performance slightly"
@@ -153,33 +144,6 @@ export function ConfigurationSection({
</PanelSectionRow>
<PanelSectionRow>
- <TextField
- label="GPU"
- description="Optional GPU name, vendor:device ID, or PCI bus ID."
- value={config.gpu}
- onChange={(event) => onConfigChange(GPU, event.currentTarget.value)}
- />
- </PanelSectionRow>
-
- <PanelSectionRow>
- <TextField
- label="Active In"
- description="Executable/process names separated by commas."
- value={config.active_in}
- onChange={(event) => onConfigChange(ACTIVE_IN, event.currentTarget.value)}
- />
- </PanelSectionRow>
-
- <PanelSectionRow>
- <ToggleField
- label="Use Automatic Profile Matching"
- description="Let lsfg-vk choose a matching Active In profile instead of forcing the profile selected in Decky."
- checked={config.use_native_matching}
- onChange={(value) => onConfigChange(USE_NATIVE_MATCHING, value)}
- />
- </PanelSectionRow>
-
- <PanelSectionRow>
<SliderField
label={`Base FPS Cap${config.dxvk_frame_rate > 0 ? ` (${config.dxvk_frame_rate} FPS)` : " (Off)"}`}
description="Base framerate cap for DirectX games, before frame multiplier. (Requires game restart to apply)"
diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts
index 7b608c3..2054758 100644
--- a/src/config/configSchema.ts
+++ b/src/config/configSchema.ts
@@ -6,8 +6,8 @@ export {
getFieldNames,
getDefaults,
getFieldTypes,
- DLL, ALLOW_FP16, MULTIPLIER, FLOW_SCALE, PERFORMANCE_MODE, PACING,
- ACTIVE_IN, USE_NATIVE_MATCHING, GPU, DISABLE_LSFGVK, DXVK_FRAME_RATE, ENABLE_WOW64,
+ ALLOW_FP16, MULTIPLIER, FLOW_SCALE, PERFORMANCE_MODE, PACING,
+ DISABLE_LSFGVK, DXVK_FRAME_RATE, ENABLE_WOW64,
DISABLE_STEAMDECK_MODE, MANGOHUD_WORKAROUND, DISABLE_VKBASALT,
FORCE_ENABLE_VKBASALT, ENABLE_WSI, ENABLE_ZINK
} from './generatedConfigSchema';
diff --git a/src/config/generatedConfigSchema.ts b/src/config/generatedConfigSchema.ts
index 5c5df39..7c0c8b0 100644
--- a/src/config/generatedConfigSchema.ts
+++ b/src/config/generatedConfigSchema.ts
@@ -8,15 +8,11 @@ export enum ConfigFieldType {
}
// Field name constants for type-safe access
-export const DLL = "dll" as const;
export const ALLOW_FP16 = "allow_fp16" as const;
export const MULTIPLIER = "multiplier" as const;
export const FLOW_SCALE = "flow_scale" as const;
export const PERFORMANCE_MODE = "performance_mode" as const;
export const PACING = "pacing" as const;
-export const ACTIVE_IN = "active_in" as const;
-export const USE_NATIVE_MATCHING = "use_native_matching" as const;
-export const GPU = "gpu" as const;
export const DISABLE_LSFGVK = "disable_lsfgvk" as const;
export const DXVK_FRAME_RATE = "dxvk_frame_rate" as const;
export const ENABLE_WOW64 = "enable_wow64" as const;
@@ -37,12 +33,6 @@ export interface ConfigField {
// Configuration schema - auto-generated from Python
export const CONFIG_SCHEMA: Record<string, ConfigField> = {
- dll: {
- name: "dll",
- fieldType: ConfigFieldType.STRING,
- default: "",
- description: "optional full path to Lossless.dll; leave blank for automatic discovery"
- },
allow_fp16: {
name: "allow_fp16",
fieldType: ConfigFieldType.BOOLEAN,
@@ -73,24 +63,6 @@ export const CONFIG_SCHEMA: Record<string, ConfigField> = {
default: "none",
description: "frame pacing mode (currently only none is supported)"
},
- active_in: {
- name: "active_in",
- fieldType: ConfigFieldType.STRING,
- default: "",
- description: "optional executable or process names, separated by commas"
- },
- use_native_matching: {
- name: "use_native_matching",
- fieldType: ConfigFieldType.BOOLEAN,
- default: false,
- description: "let lsfg-vk choose a profile from Active In instead of forcing the selected profile"
- },
- gpu: {
- name: "gpu",
- fieldType: ConfigFieldType.STRING,
- default: "",
- description: "optional GPU name, vendor:device ID, or PCI bus ID"
- },
disable_lsfgvk: {
name: "disable_lsfgvk",
fieldType: ConfigFieldType.BOOLEAN,
@@ -149,15 +121,11 @@ export const CONFIG_SCHEMA: Record<string, ConfigField> = {
// Type-safe configuration data structure
export interface ConfigurationData {
- dll: string;
allow_fp16: boolean;
multiplier: number;
flow_scale: number;
performance_mode: boolean;
pacing: string;
- active_in: string;
- use_native_matching: boolean;
- gpu: string;
disable_lsfgvk: boolean;
dxvk_frame_rate: number;
enable_wow64: boolean;
@@ -176,15 +144,11 @@ export function getFieldNames(): string[] {
export function getDefaults(): ConfigurationData {
return {
- dll: "",
allow_fp16: true,
multiplier: 2,
flow_scale: 0.9,
performance_mode: false,
pacing: "none",
- active_in: "",
- use_native_matching: false,
- gpu: "",
disable_lsfgvk: false,
dxvk_frame_rate: 0,
enable_wow64: false,
@@ -199,15 +163,11 @@ export function getDefaults(): ConfigurationData {
export function getFieldTypes(): Record<string, ConfigFieldType> {
return {
- dll: ConfigFieldType.STRING,
allow_fp16: ConfigFieldType.BOOLEAN,
multiplier: ConfigFieldType.INTEGER,
flow_scale: ConfigFieldType.FLOAT,
performance_mode: ConfigFieldType.BOOLEAN,
pacing: ConfigFieldType.STRING,
- active_in: ConfigFieldType.STRING,
- use_native_matching: ConfigFieldType.BOOLEAN,
- gpu: ConfigFieldType.STRING,
disable_lsfgvk: ConfigFieldType.BOOLEAN,
dxvk_frame_rate: ConfigFieldType.INTEGER,
enable_wow64: ConfigFieldType.BOOLEAN,
diff --git a/tests/test_lsfg_v2_migration.py b/tests/test_lsfg_v2_migration.py
index 8b2e2a3..242bf85 100644
--- a/tests/test_lsfg_v2_migration.py
+++ b/tests/test_lsfg_v2_migration.py
@@ -48,6 +48,35 @@ class ConfigurationMigrationTests(unittest.TestCase):
self.assertNotIn("no_fp16", serialized)
self.assertNotIn("hdr_mode", serialized)
self.assertNotIn("experimental_present_mode", serialized)
+ self.assertNotIn("dll =", serialized)
+ self.assertNotIn("active_in", serialized)
+ self.assertNotIn("gpu", serialized)
+
+ def test_removed_v2_controls_are_not_reintroduced_when_normalizing(self):
+ content = '''\
+version = 2
+
+[global]
+dll = "/tmp/Lossless.dll"
+allow_fp16 = true
+
+[[profile]]
+name = "decky-lsfg-vk"
+active_in = "Game.exe"
+gpu = "0x10DE:0x2684"
+multiplier = 2
+flow_scale = 0.9
+performance_mode = false
+pacing = "none"
+'''
+
+ serialized = ConfigurationManager.generate_toml_content_multi_profile(
+ ConfigurationManager.parse_toml_content_multi_profile(content)
+ )
+
+ self.assertNotIn("dll =", serialized)
+ self.assertNotIn("active_in", serialized)
+ self.assertNotIn("gpu", serialized)
def test_v2_normalization_is_idempotent(self):
migrated = ConfigurationManager.parse_toml_content_multi_profile(LEGACY_CONFIG)
@@ -66,7 +95,7 @@ class ConfigurationMigrationTests(unittest.TestCase):
profile_data = {
"current_profile": "decky-lsfg-vk",
"profiles": {"decky-lsfg-vk": ConfigurationManager.get_defaults()},
- "global_config": {"dll": "/games/Lossless Scaling/Lossless.dll", "allow_fp16": True},
+ "global_config": {"allow_fp16": True},
}
script = service._generate_script_content_for_profile(profile_data)
@@ -74,38 +103,13 @@ class ConfigurationMigrationTests(unittest.TestCase):
self.assertIn("LSFGVK_PROFILE=decky-lsfg-vk", script)
self.assertNotIn("LSFG_PROCESS", script)
- def test_active_in_does_not_implicitly_override_the_selected_profile(self):
+ def test_launch_script_always_forces_selected_profile(self):
config = ConfigurationManager.get_defaults()
- config["active_in"] = "Game.exe, GameThread"
self.assertEqual(
- ConfigurationService._profile_selection_lines("decky-lsfg-vk", config),
+ ConfigurationService._profile_selection_lines("decky-lsfg-vk"),
["export LSFGVK_PROFILE=decky-lsfg-vk"],
)
- config["use_native_matching"] = True
- self.assertEqual(
- ConfigurationService._profile_selection_lines("decky-lsfg-vk", config),
- ["# lsfg-vk will select a matching profile from Active In."],
- )
- self.assertEqual(
- ConfigurationManager.parse_script_content("export DECKY_LSFGVK_AUTO_PROFILE=1\n"),
- {"use_native_matching": True},
- )
-
- def test_cloned_profile_disables_native_matching_until_explicitly_enabled(self):
- config = ConfigurationManager.get_defaults()
- config["active_in"] = "Game.exe"
- config["use_native_matching"] = True
- profile_data = {
- "current_profile": "decky-lsfg-vk",
- "profiles": {"decky-lsfg-vk": config},
- "global_config": {"dll": "", "allow_fp16": True},
- }
-
- cloned = ConfigurationManager.create_profile(profile_data, "Other")
- self.assertEqual(cloned["profiles"]["Other"]["active_in"], "Game.exe")
- self.assertFalse(cloned["profiles"]["Other"]["use_native_matching"])
-
class InstallerInfrastructureTests(unittest.TestCase):
def test_layer_archive_selection_has_no_legacy_arm_override(self):
@@ -192,44 +196,6 @@ class InstallerInfrastructureTests(unittest.TestCase):
self.assertIn("version = 2", config_path.read_text(encoding="utf-8"))
self.assertEqual(service._config_recovery_backup, config_dir / "conf.toml.unrecognized.bak")
- def test_stale_dll_path_is_replaced_when_detection_finds_a_valid_file(self):
- from lsfg_vk.installation import InstallationService
-
- with tempfile.TemporaryDirectory() as directory:
- home = Path(directory)
- config_dir = home / ".config" / "lsfg-vk"
- config_dir.mkdir(parents=True)
- config_path = config_dir / "conf.toml"
- detected_dll = home / "Lossless.dll"
- detected_dll.write_bytes(b"dll")
- profile_data = {
- "current_profile": "decky-lsfg-vk",
- "profiles": {"decky-lsfg-vk": ConfigurationManager.get_defaults()},
- "global_config": {"dll": str(home / "missing" / "Lossless.dll"), "allow_fp16": True},
- }
- config_path.write_text(
- ConfigurationManager.generate_toml_content_multi_profile(profile_data),
- encoding="utf-8",
- )
-
- service = InstallationService.__new__(InstallationService)
- service.log = mock.Mock()
- service.user_home = home
- service.config_dir = config_dir
- service.config_file_path = config_path
- service.lsfg_script_path = home / "lsfg"
- service.lsfg_launch_script_path = home / "lsfg"
-
- with mock.patch("lsfg_vk.dll_detection.DllDetectionService") as detection_service:
- detection_service.return_value.check_lossless_scaling_dll.return_value = {
- "detected": True,
- "path": str(detected_dll),
- }
- migrated = service._create_config_file()
-
- self.assertEqual(migrated["global_config"]["dll"], str(detected_dll))
- self.assertIn(str(detected_dll), config_path.read_text(encoding="utf-8"))
-
def test_invalid_layer_manifest_fails_without_copying_unmodified_json(self):
from lsfg_vk.installation import InstallationService