mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 11:31:08 +00:00
fix(game-env): OpenGL games stop waiting for each screen refresh (#1715)
With no frame limit, AIO's Direct3D 7 (DirectDraw) test ran at exactly 144 fps on a 144 Hz panel: every frame waited one refresh (6.94 ms). Wine asks for a swap interval of 1 on every OpenGL window, and WineD3D's front-buffer drawing used by DirectDraw and Direct3D 1-7 never lowers it; it only sets the interval in its present path. Mesa's Zink then presents in FIFO. droiddeck-game-env sets vblank_mode=0 for games launched through Proton before the game's environment edits, so a value set in the launch options or the editor wins and an editor Remove leaves Mesa's default. Steam's own runs in compatdata/0 are unchanged. Steam's Frame Limit still holds a game: gamescope's WSI layer presents every swapchain in FIFO while its limiter is on. On the AYANEO Pocket FIT, AIO Direct3D 7 went from 144 to 2,345 and 2,265 fps (1 % low 99 -> 620) with no limit; with the Quick Access limit at 144 and 72 it held at 143.76 and 71.92.
This commit is contained in:
1 parent
213c2da57e
commit
9d82df6981
4 files changed
+58
-1
No files matched your search
@@ -96,6 +96,15 @@ A Unity game under FEX (`UnityPlayer.dll` for Windows builds under Proton,
|
||||
turns it off: without TSO such games can deadlock or crash (seen with Megabonk
|
||||
and Crab Game). A FEX preset chosen for the game itself still applies.
|
||||
|
||||
Games launched through Proton get `vblank_mode=0`, so OpenGL does not wait for
|
||||
each screen refresh. Wine asks for a swap interval of 1 on every OpenGL window, and WineD3D's front-buffer
|
||||
drawing (DirectDraw, Direct3D 1–7) never lowers it, so those games ran at the
|
||||
refresh rate even with no frame limit (AIO Direct3D 7: 144 fps at 144 Hz, about
|
||||
2,260 with the wait off). Steam's Frame Limit still holds a game: while it is on,
|
||||
gamescope's WSI layer presents every swapchain in FIFO at the limit. A
|
||||
`vblank_mode` from the launch options or the environment editor is kept, and
|
||||
**Remove** in the editor leaves Mesa's own default.
|
||||
|
||||
## Defaults and available suggestions
|
||||
|
||||
The initial configuration enables Mesa shader caching with
|
||||
|
||||
@@ -432,6 +432,16 @@ def cpu_topology(env, command=()):
|
||||
print("droiddeck-game-env: Windows sees %d cores (%s)" % (len(cores), env["WINE_CPU_TOPOLOGY"]), file=sys.stderr)
|
||||
|
||||
|
||||
def gl_vsync(env):
|
||||
"""OpenGL stops waiting for each screen refresh; Steam's Frame Limit still holds the game through gamescope."""
|
||||
# Wine asks for interval 1 on every GL window, and WineD3D's front-buffer drawing (DirectDraw,
|
||||
# Direct3D 1-7) never lowers it, so those games ran at the refresh rate with no limit set.
|
||||
if "vblank_mode" in env or game_id(env) is None:
|
||||
return False
|
||||
env["vblank_mode"] = "0"
|
||||
return True
|
||||
|
||||
|
||||
def apply_config(env, config, app_id, fixes=None):
|
||||
if config.get("version") != 1:
|
||||
raise ValueError("unsupported environment configuration")
|
||||
@@ -729,6 +739,7 @@ def main():
|
||||
digest = None
|
||||
app_id = game_id(env)
|
||||
if app_id:
|
||||
gl_default = gl_vsync(env)
|
||||
waiting = len(command) > 1 and command[1] == "waitforexitandrun"
|
||||
fixes = {}
|
||||
if waiting:
|
||||
@@ -761,6 +772,8 @@ def main():
|
||||
env = apply_game_debug_logs(env, config, debug_dir)
|
||||
if env is not before_debug:
|
||||
print("droiddeck-game-env: game debug logs enabled for this launch", file=sys.stderr)
|
||||
if gl_default and env.get("vblank_mode") == "0":
|
||||
print("droiddeck-game-env: OpenGL does not wait for the screen refresh (vblank_mode=0)", file=sys.stderr)
|
||||
if app_id and len(command) > 1 and command[1] == "waitforexitandrun":
|
||||
command = offline_without_anticheat(command, env)
|
||||
if mic_enabled == "0":
|
||||
|
||||
@@ -1085,7 +1085,8 @@ class LauncherTest(SyncTestCase):
|
||||
self.assertEqual([call[:2] for call in calls], [(str(dist / "proton"), [str(dist / "proton")] + command[1:]),
|
||||
(str(tool / "proton"), command)])
|
||||
self.assertEqual((calls[0][2]["WINEESYNC"], calls[0][2]["PROTON_NO_ESYNC"], calls[0][2]["BL_SYNC_PACK"]), ("0", "1", pack["id"]))
|
||||
self.assertEqual(calls[1][2], self.env(STEAM_COMPAT_APP_ID="2630", PROTON_NO_NTSYNC="1", BL_SYNC="0", WINEESYNC="1"))
|
||||
self.assertEqual(calls[1][2], self.env(STEAM_COMPAT_APP_ID="2630", PROTON_NO_NTSYNC="1", BL_SYNC="0", WINEESYNC="1",
|
||||
vblank_mode="0"))
|
||||
self.assertIn("could not start (", said.getvalue())
|
||||
|
||||
def test_generated_launchers_run_the_dist_proton(self):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -586,6 +587,39 @@ class GameEnvironmentTest(unittest.TestCase):
|
||||
self.assertEqual(env.get("WINE_CPU_TOPOLOGY"), expected, env)
|
||||
self.assertEqual(env.get("PROTON_CPU_TOPOLOGY"), proton, env)
|
||||
|
||||
def test_opengl_games_do_not_wait_for_the_screen_refresh(self):
|
||||
cases = [
|
||||
({}, "0"),
|
||||
({"STEAM_COMPAT_DATA_PATH": "/steam/compatdata/-686078602"}, "0"),
|
||||
({"vblank_mode": "2"}, "2"),
|
||||
({"vblank_mode": ""}, ""),
|
||||
({"STEAM_COMPAT_DATA_PATH": "/steam/compatdata/0"}, None),
|
||||
({"STEAM_COMPAT_DATA_PATH": ""}, None),
|
||||
]
|
||||
for env, expected in cases:
|
||||
env.setdefault("STEAM_COMPAT_DATA_PATH", "/steam/compatdata/3608888694")
|
||||
added = expected == "0" and "vblank_mode" not in env
|
||||
self.assertEqual(MODULE["gl_vsync"](env), added, env)
|
||||
self.assertEqual(env.get("vblank_mode"), expected, env)
|
||||
said = "OpenGL does not wait for the screen refresh"
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
probe = Path(tmp) / "probe"
|
||||
probe.write_text("#!" + sys.executable + "\nimport os\nprint(os.environ.get('vblank_mode', 'unset'))\n")
|
||||
probe.chmod(0o755)
|
||||
config = Path(tmp) / ".config/droiddeck/game-environment.json"
|
||||
config.parent.mkdir(parents=True)
|
||||
for inherited, edits, expected in (({}, None, "0"), ({"vblank_mode": "3"}, None, "3"),
|
||||
({}, {"vblank_mode": None}, "unset"), ({}, {"vblank_mode": "2"}, "2"),
|
||||
({"vblank_mode": "3"}, {"vblank_mode": None}, "unset")):
|
||||
with self.subTest(inherited=inherited, edits=edits):
|
||||
config.write_text(json.dumps({"version": 1, "games": {"42": edits or {}}}))
|
||||
env = {name: value for name, value in os.environ.items() if name != "vblank_mode"}
|
||||
env.update(HOME=tmp, STEAM_COMPAT_DATA_PATH="/compatdata/42", **inherited)
|
||||
result = subprocess.run([sys.executable, str(BIN / "droiddeck-game-env"), str(probe), "waitforexitandrun"],
|
||||
env=env, text=True, capture_output=True, check=True)
|
||||
self.assertEqual(result.stdout.strip(), expected)
|
||||
self.assertEqual(said in result.stderr, expected == "0" and not inherited, result.stderr)
|
||||
|
||||
def test_windows_cpu_0_is_the_fastest_core(self):
|
||||
os_mod = MODULE["os"]
|
||||
pocket_fit = {0: 379, 1: 379, 2: 923, 3: 923, 4: 923, 5: 867, 6: 867, 7: 1024}
|
||||
|
||||
Reference in new issue
Block a user