From 64f114c96f2e556f0ded7dcde37e5acbe901beb1 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:03:08 -0500 Subject: [PATCH] fix: detect graphics capabilities and drop redundant title overrides --- .../launcher/core/GameEnvironment.kt | 2 - .../launcher/core/GameEnvironmentTest.kt | 10 +- .../session/GameEnvironmentStoreTest.kt | 15 ++- docs/development/game-environment.md | 17 ++- .../overlay/usr/local/bin/droiddeck-game-env | 35 ------ tools/tests/test_game_environment.py | 106 +++++------------- 6 files changed, 55 insertions(+), 130 deletions(-) diff --git a/app/src/main/java/com/droiddeck/launcher/core/GameEnvironment.kt b/app/src/main/java/com/droiddeck/launcher/core/GameEnvironment.kt index 4672cc651..26ffb499c 100644 --- a/app/src/main/java/com/droiddeck/launcher/core/GameEnvironment.kt +++ b/app/src/main/java/com/droiddeck/launcher/core/GameEnvironment.kt @@ -30,8 +30,6 @@ object GameEnvironment { fun defaults(preset: String, forceSsbs: Boolean = false): Map = linkedMapOf( "MESA_SHADER_CACHE_DISABLE" to "false", "DROIDDECK_ANTICHEAT_OFFLINE" to "1", - "VKD3D_FEATURE_LEVEL" to "12_2", - "VKD3D_SHADER_MODEL" to "6_9", "DOTNET_GCRegionRange" to "800000000", "DOTNET_EnableWriteXorExecute" to "0", ).apply { diff --git a/app/src/test/java/com/droiddeck/launcher/core/GameEnvironmentTest.kt b/app/src/test/java/com/droiddeck/launcher/core/GameEnvironmentTest.kt index 281a78224..cd9d785d8 100644 --- a/app/src/test/java/com/droiddeck/launcher/core/GameEnvironmentTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/core/GameEnvironmentTest.kt @@ -4,12 +4,12 @@ import org.junit.Assert.* import org.junit.Test class GameEnvironmentTest { - @Test fun defaultsIncludeRequestedGraphicsLevelsAndKeepCaching() { + @Test fun defaultsDetectGraphicsCapabilitiesAndKeepCaching() { val values = GameEnvironment.defaults("") assertEquals("false", values["MESA_SHADER_CACHE_DISABLE"]) assertEquals("1", values["DROIDDECK_ANTICHEAT_OFFLINE"]) - assertEquals("12_2", values["VKD3D_FEATURE_LEVEL"]) - assertEquals("6_9", values["VKD3D_SHADER_MODEL"]) + assertFalse(values.containsKey("VKD3D_FEATURE_LEVEL")) + assertFalse(values.containsKey("VKD3D_SHADER_MODEL")) } @Test fun forceSsbsIsADefaultAGameCanTurnOff() { @@ -35,14 +35,14 @@ class GameEnvironmentTest { } @Test fun graphicsDefaultsRespectExistingEditsAndRemovals() { - val config = GameEnvironment.Config(shared = mapOf("VKD3D_FEATURE_LEVEL" to "12_0")) + val config = GameEnvironment.Config(shared = mapOf("VKD3D_FEATURE_LEVEL" to "12_0", "VKD3D_SHADER_MODEL" to "6_5")) .withEntries("42", mapOf("VKD3D_SHADER_MODEL" to null)) val game = GameEnvironment.effective(config, "", "42") assertEquals("12_0", game["VKD3D_FEATURE_LEVEL"]) assertTrue(game.containsKey("VKD3D_SHADER_MODEL")) assertNull(game["VKD3D_SHADER_MODEL"]) val restored = GameEnvironment.effective(config.withEntries("42", emptyMap()), "", "42") - assertEquals("6_9", restored["VKD3D_SHADER_MODEL"]) + assertEquals("6_5", restored["VKD3D_SHADER_MODEL"]) } @Test fun dotnetRuntimeFitsAndroidAddressSpaceForEveryGame() { diff --git a/app/src/test/java/com/droiddeck/launcher/session/GameEnvironmentStoreTest.kt b/app/src/test/java/com/droiddeck/launcher/session/GameEnvironmentStoreTest.kt index 197512e5b..da16928da 100644 --- a/app/src/test/java/com/droiddeck/launcher/session/GameEnvironmentStoreTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/session/GameEnvironmentStoreTest.kt @@ -164,13 +164,24 @@ class GameEnvironmentStoreTest { assertEquals(config, GameEnvironmentStore.read(context)) val published = GameEnvironmentStore.decode(JSONObject(guest.readText())) assertEquals("false", published.shared["MESA_SHADER_CACHE_DISABLE"]) - assertEquals("12_2", published.shared["VKD3D_FEATURE_LEVEL"]) - assertEquals("6_9", published.shared["VKD3D_SHADER_MODEL"]) + assertFalse(published.shared.containsKey("VKD3D_FEATURE_LEVEL")) + assertFalse(published.shared.containsKey("VKD3D_SHADER_MODEL")) assertEquals(config.games, published.games) assertEquals(config.shared["LITERAL"], published.shared["LITERAL"]) assertEquals("", published.shared["EMPTY"]) } + @Test fun publishingPreservesExplicitSharedGraphicsCapabilities() { + val config = GameEnvironment.Config(shared = mapOf( + "VKD3D_FEATURE_LEVEL" to "12_1", "VKD3D_SHADER_MODEL" to "6_6", + )) + GameEnvironmentStore.save(context, config) + val published = GameEnvironmentStore.decode(JSONObject(guest.readText())) + assertEquals(config, GameEnvironmentStore.read(context)) + assertEquals("12_1", published.shared["VKD3D_FEATURE_LEVEL"]) + assertEquals("6_6", published.shared["VKD3D_SHADER_MODEL"]) + } + @Test fun changingFexPresetPublishesForNextGameLaunch() { GameEnvironmentStore.save(context, GameEnvironment.Config()) SessionPrefs.setFexPreset(context, "EXTREME") diff --git a/docs/development/game-environment.md b/docs/development/game-environment.md index e7c7ca210..c1607ec5f 100644 --- a/docs/development/game-environment.md +++ b/docs/development/game-environment.md @@ -191,9 +191,10 @@ per-game value in the environment editor still wins. ## Defaults and available suggestions The initial configuration enables Mesa shader caching with -`MESA_SHADER_CACHE_DISABLE=false`, sets `VKD3D_FEATURE_LEVEL=12_2` and -`VKD3D_SHADER_MODEL=6_9`, and preserves the selected FEX preset. Existing edits -and explicit removals take precedence over these defaults. FEX's own default +`MESA_SHADER_CACHE_DISABLE=false` and preserves the selected FEX preset. +`VKD3D_FEATURE_LEVEL` and `VKD3D_SHADER_MODEL` are unset by default, so VKD3D +detects capabilities from the active Vulkan driver. Existing shared and per-game +overrides, including explicit removals, are preserved. FEX's own default remains the default preset. Shader caching in VKD3D/DXVK, synchronization, ray tracing and diagnostic logging otherwise retain the runtime's defaults. @@ -209,8 +210,8 @@ values. Custom variables have editable names and literal values. | Variable | Use | | --- | --- | -| `VKD3D_FEATURE_LEVEL` | D3D12 capability override; defaults to `12_2`. | -| `VKD3D_SHADER_MODEL` | Shader-model override; defaults to `6_9`. | +| `VKD3D_FEATURE_LEVEL` | Optional D3D12 capability override; automatic detection by default. | +| `VKD3D_SHADER_MODEL` | Optional shader-model override; automatic detection by default. | | `VKD3D_CONFIG` | Per-game workarounds such as `nodxr`; not a universal performance preset. | | `MESA_SHADER_CACHE_MAX_SIZE` | Storage budget for Mesa's shader cache, for example `1G`. | | `mesa_glthread` | OpenGL threading; test with the affected game. | @@ -221,7 +222,11 @@ values. Custom variables have editable names and literal values. | `PROTON_USE_XALIA` | Proton's gamepad-navigation helper toggle. | Forcing `12_2` or `6_9` changes reported capabilities; it cannot implement a missing -Vulkan feature. Remove either entry to use automatic detection for that capability. +Vulkan feature. Remove or unset either entry to use automatic detection for that capability. +The former feature-level rules for Caravan SandWitch, RoboCop: Rogue City, +ReSkate and Cyberpunk 2077 compensated for the global `12_2` override and are +removed. Stellar Blade also uses automatic detection. The separate Deep Rock +Galactic GDK ray-tracing opt-out and Mali bridge capability cap remain. Other picker suggestions are editable starting values and are enabled only when added and saved. Older or custom components can support a different set of options. Android Wine wrapper, diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env b/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env index d85b1bc51..886bf10bd 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env @@ -153,41 +153,6 @@ def little_nightmares_threads(game): return {"PROTONFIXES_DISABLE": "1"}, [], "Little Nightmares: umu-protonfixes off (its -onethread puts the game and renderer on one thread)" -@fix -def caravan_sandwitch_feature_level(game): - if "caravansandwitch.exe" in game.files or game.stem == "caravansandwitch-win64-shipping": - return {"VKD3D_FEATURE_LEVEL": "12_1"}, [], "Caravan SandWitch: D3D12 feature level 12_1 (12_2 hands it mesh shader pipelines vkd3d-proton cannot build)" - - -@fix -def robocop_rogue_city_feature_level(game): - if "robocop.exe" in game.files or game.stem == "robocop-win64-shipping": - return {"VKD3D_FEATURE_LEVEL": "12_1"}, [], "RoboCop: Rogue City: D3D12 feature level 12_1" - - -@fix -def reskate_feature_level(game): - if "skate.exe" in game.files and "reskate.dll" in game.files: - return {"VKD3D_FEATURE_LEVEL": "12_1"}, [], "ReSkate (skate.): D3D12 feature level 12_1" - - -@fix -def stellar_blade_feature_level(game): - if game.stem == "sb-win64-shipping" or ( - "sb.exe" in game.files - and os.path.isfile(os.path.join(os.path.dirname(game.exe), "SB", "Binaries", "Win64", "SB-Win64-Shipping.exe")) - ): - return {"VKD3D_FEATURE_LEVEL": None}, [], "Stellar Blade: use the driver's D3D12 feature level (12_2 advertises unsupported ray tracing)" - - -@fix -def cyberpunk_feature_level(game): - if game.stem == "cyberpunk2077" or ( - game.stem == "redprelauncher" - and os.path.isfile(os.path.join(os.path.dirname(game.exe), "bin", "x64", "Cyberpunk2077.exe")) - ): - return {"VKD3D_FEATURE_LEVEL": "12_1"}, [], "Cyberpunk 2077: D3D12 feature level 12_1" - def skip_red_launcher(command): if len(command) > 2 and os.path.basename(command[2]).lower() == "redprelauncher.exe": game = os.path.join(os.path.dirname(command[2]), "bin", "x64", "Cyberpunk2077.exe") diff --git a/tools/tests/test_game_environment.py b/tools/tests/test_game_environment.py index 093c5e843..cd58fba4d 100644 --- a/tools/tests/test_game_environment.py +++ b/tools/tests/test_game_environment.py @@ -1170,87 +1170,33 @@ class GameEnvironmentTest(unittest.TestCase): self.assertEqual(speeds([0, 1], tmp), {0: 2265600, 1: 3302400}) self.assertIsNone(speeds([0, 2], tmp)) - def test_reskate_runs_at_feature_level_12_1(self): + def test_titles_with_former_feature_level_workarounds_keep_detected_capabilities(self): with tempfile.TemporaryDirectory() as tmp: - reskate = Path(tmp) / "ReSkate" - reskate.mkdir() - for name in ("Skate.exe", "ReSkate.dll", "ReSkateLauncher.exe", "EAAntiCheat.GameServiceLauncher.exe"): - (reskate / name).touch() - for exe in ("ReSkateLauncher.exe", "Skate.exe"): - env, extra, notes = MODULE["engine_fixes"](str(reskate / exe), []) - self.assertEqual((env, extra), ({"VKD3D_FEATURE_LEVEL": "12_1"}, []), exe) - self.assertTrue(notes) - config = {"version": 1, "shared": {"VKD3D_FEATURE_LEVEL": "12_2"}} - self.assertEqual(MODULE["apply_config"]({}, config, "3354750", env)["VKD3D_FEATURE_LEVEL"], "12_1") - config["games"] = {"3354750": {"VKD3D_FEATURE_LEVEL": "12_2"}} - self.assertEqual(MODULE["apply_config"]({}, config, "3354750", env)["VKD3D_FEATURE_LEVEL"], "12_2") - skate = Path(tmp) / "Skate" - skate.mkdir() - for name in ("Skate.exe", "EAAntiCheat.GameServiceLauncher.exe"): - (skate / name).touch() - self.assertEqual(MODULE["engine_fixes"](str(skate / "EAAntiCheat.GameServiceLauncher.exe"), [])[:2], ({}, [])) - - def test_stellar_blade_uses_detected_feature_level_without_overriding_game_settings(self): - with tempfile.TemporaryDirectory() as tmp: - root = Path(tmp) / "StellarBlade" - shipping = root / "SB/Binaries/Win64/SB-Win64-Shipping.exe" - shipping.parent.mkdir(parents=True) - shipping.touch() - (root / "SB.exe").touch() - config = {"version": 1, "shared": {"VKD3D_FEATURE_LEVEL": "12_2"}} - for exe in (root / "SB.exe", root / ".droiddeck-launch.bat", shipping): - env, extra, notes = MODULE["engine_fixes"](str(exe), []) - self.assertEqual((env, extra), ({"VKD3D_FEATURE_LEVEL": None}, []), exe) - self.assertTrue(notes) - inherited = {"VKD3D_FEATURE_LEVEL": "12_2", "KEEP": "unchanged"} - effective = MODULE["apply_config"](inherited, config, "3489700", env) - self.assertNotIn("VKD3D_FEATURE_LEVEL", effective) - self.assertEqual(effective["KEEP"], "unchanged") - for value in ("12_1", "12_2", None): - explicit = dict(config, games={"3489700": {"VKD3D_FEATURE_LEVEL": value}}) - actual = MODULE["apply_config"](inherited, explicit, "3489700", env) - self.assertEqual(actual.get("VKD3D_FEATURE_LEVEL"), value) - other = Path(tmp) / "Other" - other.mkdir() - (other / "SB.exe").touch() - self.assertEqual(MODULE["engine_fixes"](str(other / "SB.exe"), []), ({}, [], [])) - - def test_caravan_sandwitch_robocop_and_cyberpunk_run_at_feature_level_12_1(self): - with tempfile.TemporaryDirectory() as tmp: - caravan = Path(tmp) / "Caravan SandWitch" - shipping = caravan / "CaravanSandWitch/Binaries/Win64" - shipping.mkdir(parents=True) - for name in ("CaravanSandWitch.exe", ".droiddeck-launch.bat"): - (caravan / name).touch() - (shipping / "CaravanSandWitch-Win64-Shipping.exe").touch() - cyberpunk = Path(tmp) / "Cyberpunk 2077" - (cyberpunk / "bin/x64").mkdir(parents=True) - for name in ("REDprelauncher.exe", "bin/x64/Cyberpunk2077.exe"): - (cyberpunk / name).touch() - robocop = Path(tmp) / "RoboCop Rogue City" - (robocop / "RoboCop/Binaries/Win64").mkdir(parents=True) - for name in ("RoboCop.exe", "RoboCop/Binaries/Win64/RoboCop-Win64-Shipping.exe"): - (robocop / name).touch() - games = [caravan / "CaravanSandWitch.exe", caravan / ".droiddeck-launch.bat", - shipping / "CaravanSandWitch-Win64-Shipping.exe", - robocop / "RoboCop.exe", robocop / "RoboCop/Binaries/Win64/RoboCop-Win64-Shipping.exe", - cyberpunk / "REDprelauncher.exe", cyberpunk / "bin/x64/Cyberpunk2077.exe"] - for exe in games: - env, extra, notes = MODULE["engine_fixes"](str(exe), []) - self.assertEqual((env, extra), ({"VKD3D_FEATURE_LEVEL": "12_1"}, []), exe) - self.assertTrue(notes) - config = {"version": 1, "shared": {"VKD3D_FEATURE_LEVEL": "12_2"}} - self.assertEqual(MODULE["apply_config"]({}, config, "1582650", env)["VKD3D_FEATURE_LEVEL"], "12_1") - config["games"] = {"1582650": {"VKD3D_FEATURE_LEVEL": "12_2"}} - self.assertEqual(MODULE["apply_config"]({}, config, "1582650", env)["VKD3D_FEATURE_LEVEL"], "12_2") - other = Path(tmp) / "Other/Binaries/Win64" - other.mkdir(parents=True) - (other / "Other-Win64-Shipping.exe").touch() - self.assertEqual(MODULE["engine_fixes"](str(other / "Other-Win64-Shipping.exe"), []), ({}, [], [])) - witcher = Path(tmp) / "The Witcher 3" - witcher.mkdir() - (witcher / "REDprelauncher.exe").touch() - self.assertNotIn("VKD3D_FEATURE_LEVEL", MODULE["engine_fixes"](str(witcher / "REDprelauncher.exe"), [])[0]) + root = Path(tmp) + names = ( + "StellarBlade/SB.exe", "StellarBlade/.droiddeck-launch.bat", + "StellarBlade/SB/Binaries/Win64/SB-Win64-Shipping.exe", + "Caravan/CaravanSandWitch.exe", "Caravan/.droiddeck-launch.bat", + "Caravan/CaravanSandWitch/Binaries/Win64/CaravanSandWitch-Win64-Shipping.exe", + "RoboCop/RoboCop.exe", "RoboCop/RoboCop/Binaries/Win64/RoboCop-Win64-Shipping.exe", + "ReSkate/Skate.exe", "ReSkate/ReSkate.dll", "ReSkate/ReSkateLauncher.exe", + "Cyberpunk/REDprelauncher.exe", "Cyberpunk/bin/x64/Cyberpunk2077.exe", + ) + for name in names: + p = root / name + p.parent.mkdir(parents=True, exist_ok=True) + p.touch() + for name in names: + if name.endswith(".dll"): + continue + env, extra, notes = MODULE["engine_fixes"](str(root / name), []) + self.assertNotIn("VKD3D_FEATURE_LEVEL", env, name) + self.assertNotIn("VKD3D_SHADER_MODEL", env, name) + for values in ({}, {"VKD3D_FEATURE_LEVEL": "12_2", "VKD3D_SHADER_MODEL": "6_9"}): + config = {"version": 1, "shared": values} + self.assertEqual(MODULE["apply_config"]({}, config, "42", env), values, name) + config = {"version": 1, "shared": {}, "games": {"42": values}} + self.assertEqual(MODULE["apply_config"]({}, config, "42", env), values, name) def test_unity_players_keep_tso_over_the_preset(self): with tempfile.TemporaryDirectory() as tmp: