Skip to content

Commit 85b3eb8

Browse files
Make Auto launch policy conservative and stop aggressive environment injection
- Refactor `WineTkgRunner` to make `Auto` graphics policies conservative: - `GraphicsBackendPolicy::Auto` now defaults to baseline WineD3D unless manual overrides are set. - `D3D12ProviderPolicy::Auto` now defaults to None unless explicitly requested. - GPU selection now defaults to system default (no forced offload) if unset. - Stop unconditional injection of DXVK/VKD3D paths and overrides based only on detection. - Filter `WINEDLLPATH` to only include directories for components actively selected for use. - Refine `populate_effective_graphics_stack` to distinguish between "default/unset" and "forced" states. - Update `LaunchInvariantValidator` to enforce that no injection occurs for unset components. - Ensure diagnostics clearly separate "Detected" availability from "Applied" runtime usage. Co-authored-by: weter11 <14630689+weter11@users.noreply.github.com>
1 parent 18d9838 commit 85b3eb8

4 files changed

Lines changed: 124 additions & 37 deletions

File tree

src/infra/runners/tests.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,26 @@ mod tests {
106106
target: LaunchTarget::WindowsProton,
107107
},
108108
launcher_config: config,
109-
user_config: Some(user_config),
109+
user_config: Some(user_config.clone()),
110110
proton_path: Some(runner_path.to_string_lossy().to_string()),
111111
dll_resolutions: Vec::new(),
112112
};
113113

114114
let runner = WineTkgRunner;
115115
let env = runner.build_env(&ctx).await.unwrap();
116116

117-
// Since we simulated DXVK, Auto should have enabled it
117+
// Auto is now CONSERVATIVE: it should NOT have enabled DXVK even if simulated on disk
118118
let overrides = env.get("WINEDLLOVERRIDES").unwrap();
119-
assert!(overrides.contains("d3d11=n,b"));
119+
assert!(!overrides.contains("d3d11=n,b"));
120+
121+
// Explicitly requesting DXVK should still enable it
122+
let mut user_config_dxvk = user_config.clone();
123+
user_config_dxvk.graphics_layers.graphics_backend_policy = GraphicsBackendPolicy::DXVK;
124+
let mut ctx_dxvk = ctx.clone();
125+
ctx_dxvk.user_config = Some(user_config_dxvk);
126+
let env_dxvk = runner.build_env(&ctx_dxvk).await.unwrap();
127+
let overrides_dxvk = env_dxvk.get("WINEDLLOVERRIDES").unwrap();
128+
assert!(overrides_dxvk.contains("d3d11=n,b"));
120129
}
121130

122131
#[tokio::test]

src/infra/runners/wine_tkg.rs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -308,14 +308,16 @@ impl Runner for WineTkgRunner {
308308
.unwrap_or("wine")
309309
};
310310

311-
let components = crate::utils::detect_runner_components(
311+
let _components = crate::utils::detect_runner_components(
312312
&crate::utils::resolve_runner(proton, &library_root),
313313
Some(&game_wineprefix),
314314
);
315315

316-
// 1. Resolve DX8-11 policy (GraphicsBackendPolicy)
316+
// 1. Resolve DX8-11 policy (GraphicsBackendPolicy) - CONSERVATIVE
317317
let (policy_dxvk, force_builtin) = match glc.graphics_backend_policy {
318-
crate::models::GraphicsBackendPolicy::Auto => (components.dxvk.is_some(), false),
318+
// Auto is now conservative: it does NOT automatically enable DXVK
319+
// even if detected on disk. It prefers default Wine behavior.
320+
crate::models::GraphicsBackendPolicy::Auto => (false, false),
319321
crate::models::GraphicsBackendPolicy::WineD3D => (false, true),
320322
crate::models::GraphicsBackendPolicy::DXVK => (true, false),
321323
};
@@ -326,17 +328,10 @@ impl Runner for WineTkgRunner {
326328
// If user explicitly selected WineD3D and didn't force DXVK, we use builtins.
327329
let force_builtin_d3d = force_builtin && !effective_dxvk;
328330

329-
// 2. Resolve DX12 policy (D3D12ProviderPolicy)
331+
// 2. Resolve DX12 policy (D3D12ProviderPolicy) - CONSERVATIVE
330332
let (policy_vkd3dp, policy_vkd3dw) = match glc.d3d12_policy {
331-
crate::models::D3D12ProviderPolicy::Auto => {
332-
if components.vkd3d_proton.is_some() {
333-
(true, false)
334-
} else if components.vkd3d.is_some() {
335-
(false, true)
336-
} else {
337-
(false, false)
338-
}
339-
}
333+
// Auto is now conservative: no forced D3D12 provider unless explicitly requested.
334+
crate::models::D3D12ProviderPolicy::Auto => (false, false),
340335
crate::models::D3D12ProviderPolicy::Vkd3dProton => (true, false),
341336
crate::models::D3D12ProviderPolicy::Vkd3dWine => (false, true),
342337
};
@@ -371,11 +366,23 @@ impl Runner for WineTkgRunner {
371366

372367
// Translate Runner-resolved DLL paths into WINEDLLPATH so Wine can
373368
// actually find the bundled DLLs (VKD3D-Proton, DXVK, etc.) in the runner.
374-
// Without this, d3d12=n,b finds whatever is in the prefix's system32 instead.
369+
// WITHOUT THIS, d3d12=n,b finds whatever is in the prefix's system32 instead.
370+
// CONSERVATIVE: only include paths for DLLs that are actually requested to be native.
375371
let mut wine_dll_dirs: Vec<String> = Vec::new();
376372

377373
for res in &ctx.dll_resolutions {
378374
if let crate::launch::dll_provider_resolver::DllProvider::Runner = res.chosen_provider {
375+
// Check if this DLL is actually selected for use by the current policy/overrides
376+
let name = res.name.to_lowercase();
377+
let is_dxvk_dll = matches!(name.as_str(), "d3d8" | "d3d9" | "d3d10" | "d3d10_1" | "d3d10core" | "d3d11" | "dxgi");
378+
let is_d3d12_dll = matches!(name.as_str(), "d3d12" | "d3d12core" | "libvkd3d-1" | "libvkd3d-shader-1");
379+
380+
let selected = (is_dxvk_dll && effective_dxvk) || (is_d3d12_dll && (effective_vkd3d_proton || effective_vkd3d));
381+
382+
if !selected {
383+
continue;
384+
}
385+
379386
if let Some(path) = &res.chosen_path {
380387
if let Some(parent) = path.parent() {
381388
let dir = parent.to_string_lossy().to_string();
@@ -458,7 +465,7 @@ impl Runner for WineTkgRunner {
458465
env.insert("XDG_RUNTIME_DIR".to_string(), xdg_runtime);
459466
}
460467

461-
// Apply GPU preference if specified, otherwise fall back to auto-detecting PRIME/Optimus
468+
// Apply GPU preference if specified. CONSERVATIVE: No forced offload if unset.
462469
if let Some(gpu_pref) = ctx.user_config.as_ref().and_then(|c| c.gpu_preference.as_ref()) {
463470
let available_gpus = crate::utils::list_available_gpus();
464471
if let Some(gpu) = available_gpus.iter().find(|g| &g.name == gpu_pref) {
@@ -486,11 +493,6 @@ impl Runner for WineTkgRunner {
486493
}
487494
}
488495
}
489-
} else {
490-
// Auto-inject PRIME/Optimus GPU selection
491-
for (k, v) in crate::utils::detect_prime_env() {
492-
env.entry(k).or_insert(v);
493-
}
494496
}
495497

496498
if let Some(config) = &ctx.user_config {
@@ -499,12 +501,12 @@ impl Runner for WineTkgRunner {
499501
}
500502

501503
// Add debug toggles
502-
if config.graphics_layers.dxvk_enabled {
504+
if effective_dxvk {
503505
if !env.contains_key("DXVK_HUD") {
504506
env.insert("DXVK_HUD".to_string(), "compiler".to_string());
505507
}
506508
}
507-
if config.graphics_layers.vkd3d_proton_enabled || config.graphics_layers.vkd3d_enabled {
509+
if effective_vkd3d_proton || effective_vkd3d {
508510
if !env.contains_key("VKD3D_DEBUG") {
509511
env.insert("VKD3D_DEBUG".to_string(), "warn".to_string());
510512
}

src/launch/pipeline.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,17 +447,21 @@ impl LaunchPipeline {
447447

448448
ctx.graphics_stack.effective_backend = if has_dxvk { "DXVK" } else { "WineD3D (Baseline)" }.to_string();
449449
ctx.graphics_stack.effective_d3d12_provider = if has_vkd3dp { "vkd3d-proton" } else if has_vkd3dw { "vkd3d" } else { "None" }.to_string();
450+
} else {
451+
ctx.graphics_stack.effective_backend = "WineD3D (Baseline)".to_string();
452+
ctx.graphics_stack.effective_d3d12_provider = "None".to_string();
450453
}
451454

452455
// GPU Selection
456+
ctx.graphics_stack.effective_gpu = None;
453457
if let Some(val) = spec.env.get("__NV_PRIME_RENDER_OFFLOAD") {
454458
if val == "1" {
455459
ctx.graphics_stack.effective_gpu = Some("NVIDIA Discrete GPU".to_string());
456460
}
457461
} else if let Some(val) = spec.env.get("DRI_PRIME") {
458462
if val == "1" {
459463
ctx.graphics_stack.effective_gpu = Some("Secondary GPU (DRI_PRIME=1)".to_string());
460-
} else {
464+
} else if val == "0" {
461465
ctx.graphics_stack.effective_gpu = Some("Primary GPU (DRI_PRIME=0)".to_string());
462466
}
463467
}

src/launch/validators/invariants.rs

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,86 @@ impl LaunchValidator for LaunchInvariantValidator {
99
fn validate(&self, ctx: &mut PipelineContext) {
1010
let mut warnings = Vec::new();
1111

12-
// 1. Invariant B: baseline/WineD3D must not keep forced native DXVK/VKD3D overrides
13-
if ctx.graphics_stack.effective_backend == "WineD3D (Baseline)" {
12+
// Invariant A: If effective GPU is unset/default, there must be no forced GPU-selection env vars.
13+
if ctx.graphics_stack.effective_gpu.is_none() {
14+
if let Some(spec) = &ctx.command_spec {
15+
for key in &[
16+
"DRI_PRIME",
17+
"__NV_PRIME_RENDER_OFFLOAD",
18+
"__NV_PRIME_RENDER_OFFLOAD_PROVIDER",
19+
"__GLX_VENDOR_LIBRARY_NAME",
20+
"__VK_LAYER_NV_optimus",
21+
] {
22+
if spec.env.contains_key(*key) {
23+
warnings.push((
24+
"INVARIANT_A_VIOLATION",
25+
format!("Effective GPU is unset but found GPU-forcing env var: {}", key),
26+
));
27+
}
28+
}
29+
}
30+
}
31+
32+
// Invariant B: If effective backend is not DXVK, there must be no DXVK-forcing overrides or DXVK-only DLL path injection.
33+
if ctx.graphics_stack.effective_backend != "DXVK" {
1434
if let Some(spec) = &ctx.command_spec {
1535
if let Some(overrides) = spec.env.get("WINEDLLOVERRIDES") {
16-
let conflicts = ["d3d8", "d3d9", "d3d10", "d3d10_1", "d3d10core", "d3d11", "dxgi", "d3d12", "d3d12core"];
36+
let dxvk_dlls = ["d3d8", "d3d9", "d3d10", "d3d10core", "d3d11", "dxgi"];
1737
for part in overrides.split(';') {
1838
if let Some((dll, mode)) = part.split_once('=') {
1939
let dll_trimmed = dll.trim().to_lowercase();
20-
if conflicts.contains(&dll_trimmed.as_str()) && mode.contains('n') {
40+
if dxvk_dlls.contains(&dll_trimmed.as_str()) && mode.contains('n') {
2141
warnings.push((
22-
"INVARIANT_B_CONFLICT",
23-
format!("Forced native override '{dll}={mode}' found in baseline mode. Graphics may fail.")
42+
"INVARIANT_B_VIOLATION",
43+
format!("Effective backend is not DXVK but found native override for DXVK DLL: {}", dll),
2444
));
2545
}
2646
}
2747
}
2848
}
49+
if let Some(dll_path) = spec.env.get("WINEDLLPATH") {
50+
if dll_path.contains("dxvk") {
51+
warnings.push((
52+
"INVARIANT_B_VIOLATION",
53+
"Effective backend is not DXVK but WINEDLLPATH contains 'dxvk'".into(),
54+
));
55+
}
56+
}
2957
}
3058
}
3159

32-
// 2. Invariant C: effective D3D12 provider must match resolved provider paths
33-
if !ctx.graphics_stack.effective_d3d12_provider.is_empty() {
60+
// Invariant C: If effective D3D12 provider is unset/default/not-selected, there must be no forced D3D12 provider injection.
61+
if ctx.graphics_stack.effective_d3d12_provider == "None" {
62+
if let Some(spec) = &ctx.command_spec {
63+
if let Some(overrides) = spec.env.get("WINEDLLOVERRIDES") {
64+
let d3d12_dlls = ["d3d12", "d3d12core"];
65+
for part in overrides.split(';') {
66+
if let Some((dll, mode)) = part.split_once('=') {
67+
let dll_trimmed = dll.trim().to_lowercase();
68+
if d3d12_dlls.contains(&dll_trimmed.as_str()) && mode.contains('n') {
69+
warnings.push((
70+
"INVARIANT_C_VIOLATION",
71+
format!("Effective D3D12 provider is None but found native override for DLL: {}", dll),
72+
));
73+
}
74+
}
75+
}
76+
}
77+
if let Some(dll_path) = spec.env.get("WINEDLLPATH") {
78+
if dll_path.contains("vkd3d") {
79+
warnings.push((
80+
"INVARIANT_C_VIOLATION",
81+
"Effective D3D12 provider is None but WINEDLLPATH contains 'vkd3d'".into(),
82+
));
83+
}
84+
}
85+
}
86+
}
87+
88+
// Detailed Invariant C: Effective D3D12 provider must match resolved provider paths if one is active.
89+
// If effective is "vkd3d-proton", we expect to see it in the path.
90+
// If effective is "vkd3d", we expect to see vkd3d but NOT vkd3d-proton in the path.
91+
if ctx.graphics_stack.effective_d3d12_provider != "None" {
3492
let provider = &ctx.graphics_stack.effective_d3d12_provider;
3593
for res in &ctx.dll_resolutions {
3694
if res.name == "d3d12" || res.name == "d3d12core" {
@@ -52,9 +110,15 @@ impl LaunchValidator for LaunchInvariantValidator {
52110
}
53111
}
54112

55-
// 3. Invariant D: explicit user setting must not be silently overwritten
113+
// Invariant D: explicit user setting must not be silently overwritten
56114
if !ctx.graphics_stack.requested_backend.is_empty() && ctx.graphics_stack.requested_backend != "Auto" {
57-
if ctx.graphics_stack.requested_backend != ctx.graphics_stack.effective_backend {
115+
// requested_backend is likely Debug string of enum, e.g. "DXVK" or "WineD3D"
116+
let req = &ctx.graphics_stack.requested_backend;
117+
let eff = &ctx.graphics_stack.effective_backend;
118+
119+
let mismatch = (req == "DXVK" && eff != "DXVK") || (req == "WineD3D" && eff != "WineD3D (Baseline)");
120+
121+
if mismatch {
58122
let reason = ctx.graphics_stack.fallback_reasons.get("graphics_backend").cloned().unwrap_or_else(|| "unknown".into());
59123
warnings.push((
60124
"INVARIANT_D_BACKEND_MISMATCH",
@@ -76,7 +140,15 @@ impl LaunchValidator for LaunchInvariantValidator {
76140
}
77141

78142
if let Some(requested_gpu) = &ctx.graphics_stack.requested_gpu {
79-
if ctx.graphics_stack.effective_gpu.as_ref() != Some(requested_gpu) {
143+
let mut mismatch = true;
144+
if let Some(effective_gpu) = &ctx.graphics_stack.effective_gpu {
145+
// Check for partial match since effective names are synthesized
146+
if effective_gpu.contains(requested_gpu) || requested_gpu.contains("NVIDIA") && effective_gpu.contains("NVIDIA") {
147+
mismatch = false;
148+
}
149+
}
150+
151+
if mismatch {
80152
let reason = ctx.graphics_stack.fallback_reasons.get("gpu").cloned().unwrap_or_else(|| "unknown".into());
81153
warnings.push((
82154
"INVARIANT_D_GPU_MISMATCH",

0 commit comments

Comments
 (0)