v1.0.3 — Render fallback software pour environnements sans accel GPU

Bug rapporté : sur un poste client fraîchement installé, la fenêtre du
launcher reste TOUT BLANCHE. Aucune exception loguée, services en
arrière-plan fonctionnent (manifest fetché, LAN discovery active,
hosting alive), juste rien à l'écran. WebView2 confirmé installé, donc
ce n'était pas la cause.

Diagnostique : c'est un mode de panne classique de WPF sur certains
environnements où l'accélération matérielle DirectX échoue silencieusement.
La fenêtre est créée, la message loop tourne, mais rien ne se rend
visuellement. Aucune exception ne remonte parce que le pipeline graphique
échoue en dehors du try/catch managé.

Environnements à risque :
  - Sessions Bureau à distance (RDP) — accélération matérielle limitée
  - Machines virtuelles sans hardware accel (Hyper-V Gen 1, VirtualBox,
    VMware basic)
  - GPU Intel HD anciens avec drivers obsolètes
  - Windows Server sans Desktop Experience
  - PCs corporate avec GPU émulé (RDS, Citrix)

Fix : check au démarrage et fallback vers RenderMode.SoftwareOnly dans
3 cas :
  1. RenderCapability.Tier == 0 — WPF lui-même dit "pas d'accel possible"
  2. GetSystemMetrics(SM_REMOTESESSION) != 0 — session RDP détectée
  3. Variable d'env PSLAUNCHER_SOFTWARE_RENDER=1 — override manuel

Le mode choisi est logué au démarrage (Tier=X RDP=Y EnvOverride=Z →
SoftwareOnly=W), donc en cas de re-rapport on saura immédiatement si
c'est ce code qui s'est déclenché.

Trade-off : rendu software ~30% plus lent sur le scroll et les
animations, mais visible vs INVISIBLE. Le bon trade-off.

Pour l'utilisateur déjà bloqué AVANT cette release : set la var
PSLAUNCHER_SOFTWARE_RENDER=1 dans les variables d'environnement Windows
de la session user (Paramètres → Système → À propos → Paramètres
système avancés → Variables d'environnement), puis relancer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 10:32:31 +02:00
parent 5b489dcfc5
commit e2d9171df1
2 changed files with 66 additions and 4 deletions

View File

@@ -120,6 +120,19 @@ public partial class App : Application
Log.Information("PSLauncher starting (logs in {Path})", LogsDirectory); Log.Information("PSLauncher starting (logs in {Path})", LogsDirectory);
// RENDER MODE : sur certains environnements (RDP, VMs sans hardware accel,
// GPU émulé, Intel HD anciens, Windows Server sans Desktop Experience…)
// l'accélération matérielle WPF plante en silence : la fenêtre s'affiche
// toute blanche, aucun contenu ne rend, aucune exception ne remonte. C'est
// un bug classique non rattrapable côté .NET parce que le pipeline graphique
// tombe entre les mailles du try/catch. On force le rendu software dans 3 cas :
// 1. RenderCapability.Tier == 0 (WPF lui-même détecte aucune accel possible)
// 2. Session Bureau à distance (RDP) détectée
// 3. Var d'env PSLAUNCHER_SOFTWARE_RENDER=1 (override manuel pour debug)
// Trade-off : rendu software plus lent (~30% impact sur le scrolling de la
// liste des versions), mais au moins ça affiche QUELQUE CHOSE.
ApplyRenderModeFallback();
AppDomain.CurrentDomain.UnhandledException += (_, args) => AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{ {
// CRITIQUE : on flush AVANT que le process meure. Sans ça, sur les // CRITIQUE : on flush AVANT que le process meure. Sans ça, sur les
@@ -317,6 +330,55 @@ public partial class App : Application
window.Show(); window.Show();
} }
/// <summary>
/// Décide entre rendu hardware (par défaut WPF) et rendu software, en fonction
/// de l'environnement détecté + override par variable d'environnement. Log
/// la décision pour faciliter le debug si l'utilisateur reporte un écran blanc.
/// </summary>
private static void ApplyRenderModeFallback()
{
try
{
// Override manuel : PSLAUNCHER_SOFTWARE_RENDER=1 force le software.
// Le user peut le set dans les variables d'environnement Windows OU dans
// le raccourci .lnk (champ Cible : "cmd /c set PSLAUNCHER_SOFTWARE_RENDER=1 && start PS_Launcher.exe")
var envOverride = Environment.GetEnvironmentVariable("PSLAUNCHER_SOFTWARE_RENDER");
var manualSoftware = !string.IsNullOrEmpty(envOverride)
&& (envOverride == "1" || envOverride.Equals("true", StringComparison.OrdinalIgnoreCase));
// RenderCapability.Tier renvoie un int packed : (tier << 16). Tier 0 = pas
// d'accélération matérielle possible. Tier 1 = partielle. Tier 2 = complète.
var tier = System.Windows.Media.RenderCapability.Tier >> 16;
// GetSystemMetrics(SM_REMOTESESSION) = 1 si la session est une session RDP.
// SM_REMOTESESSION constant = 0x1000 (4096).
var isRdp = GetSystemMetrics(0x1000) != 0;
var shouldUseSoftware = manualSoftware || tier == 0 || isRdp;
Log.Information("Render env : Tier={Tier} RDP={Rdp} EnvOverride={EnvOverride} → SoftwareOnly={Sw}",
tier, isRdp, manualSoftware, shouldUseSoftware);
if (shouldUseSoftware)
{
System.Windows.Media.RenderOptions.ProcessRenderMode =
System.Windows.Interop.RenderMode.SoftwareOnly;
Log.Warning("WPF render mode forced to SoftwareOnly (cause: " +
(manualSoftware ? "env override" : tier == 0 ? "Tier=0 no GPU accel" : "RDP session") + ")");
}
}
catch (Exception ex)
{
// Pas critique : si le fallback échoue, on continue avec le default
// hardware accel et on espère que ça marche. Le user verra un écran
// blanc si non, mais au moins l'app aura pas crashé.
Log.Warning(ex, "Failed to evaluate render-mode fallback, keeping default");
}
}
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
protected override void OnExit(ExitEventArgs e) protected override void OnExit(ExitEventArgs e)
{ {
Log.Information("PSLauncher shutting down"); Log.Information("PSLauncher shutting down");

View File

@@ -18,9 +18,9 @@
<Product>PROSERVE Launcher</Product> <Product>PROSERVE Launcher</Product>
<Copyright>© 2026 ASTERION VR — All rights reserved</Copyright> <Copyright>© 2026 ASTERION VR — All rights reserved</Copyright>
<RootNamespace>PSLauncher.App</RootNamespace> <RootNamespace>PSLauncher.App</RootNamespace>
<Version>1.0.2</Version> <Version>1.0.3</Version>
<AssemblyVersion>1.0.2.0</AssemblyVersion> <AssemblyVersion>1.0.3.0</AssemblyVersion>
<FileVersion>1.0.2.0</FileVersion> <FileVersion>1.0.3.0</FileVersion>
<!-- Single-file self-contained publish profile (used by `dotnet publish`) --> <!-- Single-file self-contained publish profile (used by `dotnet publish`) -->
<PublishSingleFile>true</PublishSingleFile> <PublishSingleFile>true</PublishSingleFile>
@@ -47,7 +47,7 @@
Le runtime est pré-installé sur Win11 et auto-mis-à-jour via Windows Le runtime est pré-installé sur Win11 et auto-mis-à-jour via Windows
Update sur Win10 récent. Si absent, le contrôle affiche une erreur Update sur Win10 récent. Si absent, le contrôle affiche une erreur
et on aiguille l'user vers https://go.microsoft.com/fwlink/p/?LinkId=2124703 --> et on aiguille l'user vers https://go.microsoft.com/fwlink/p/?LinkId=2124703 -->
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2792.45" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3792.45" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>