177 lines
17 KiB
Markdown
177 lines
17 KiB
Markdown
|
|
Fiche U7b-T1 — Niveau N3 — Modèle prévu lp-cart | utilisé claude-opus-5-5 — Agent lp-cart — Skills sk-protocol, lp-paths (préchargés aussi : ue-cpp-build, lp-commandlets)
|
|||
|
|
|
|||
|
|
# U7b-T1 : cartographie de la capture des planches de la tournée Mesure (fenêtre minimisée, UE 5.8)
|
|||
|
|
|
|||
|
|
Date : 2026-10-09. Lecture seule : rien n'a été modifié, lancé ni commité. Addon = worktree `D:\Game\LifePlay-Sandbox-5.8`,
|
|||
|
|
branche `ue5.8`, HEAD `d1efdd1`, `git status --porcelain` vide. Moteur = `D:\Game\Launcher\Engine\UE_5.8`, 5.8.3, CL 58210709
|
|||
|
|
(`Engine/Build/Build.version`), sources présentes. Dans ce document, `E58` = `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime`.
|
|||
|
|
|
|||
|
|
**Écart à la fiche (méthode § 4)** : aucun outil de recherche web (WebSearch / WebFetch) n'est disponible dans cette session.
|
|||
|
|
Les sources moteur 5.8 installées (source primaire, règle d'or de `ue-cpp-build`) les remplacent. Je ne cite aucune URL
|
|||
|
|
parce que je n'en ai consulté aucune. Si l'orchestrateur veut un recoupement par la documentation Epic, il faut une session
|
|||
|
|
qui a un accès web.
|
|||
|
|
|
|||
|
|
## 1. Où et comment la tournée capture aujourd'hui (établi)
|
|||
|
|
|
|||
|
|
### 1.1 Lancement
|
|||
|
|
- Pilote : `Tools\anim\AnimPipeline.ps1`, fonction `Invoke-Sandbox` (l. 300-356). Il écrit `Saved\sandbox_mode.txt = Mesure`
|
|||
|
|
et `Saved\sandbox_tour.txt = vignettes planches quitter` (l. 329-331), puis lance
|
|||
|
|
`Tools\debug\LifePlay_interaction_sandbox.bat` (l. 306 et 334, `Start-Process cmd.exe ... -WindowStyle Hidden`). Ensuite,
|
|||
|
|
il attend que le processus `UnrealEditor` se termine, avec un plafond de 40 min (l. 338-340).
|
|||
|
|
- Lanceur `Tools\debug\LifePlay_interaction_sandbox.bat:9` :
|
|||
|
|
`UnrealEditor.exe <uproject> "/Engine/Maps/Entry?game=/Script/LPSandbox.LPPairSandboxGameMode" -game -windowed -ResX=1600 -ResY=900 "-ini:Engine:[...]:GameInstanceClass=..."`.
|
|||
|
|
Le .bat ne transmet pas d'arguments supplémentaires (`%*` absent).
|
|||
|
|
- Le même .bat sert aussi à **l'ouverture interactive** : `Tools\BuildMod.ps1:165` (`Open-AnimTarget -Which sandbox`) et
|
|||
|
|
`README.md:53`. Changer son comportement par défaut touche donc aussi l'usage manuel.
|
|||
|
|
|
|||
|
|
### 1.2 Capture (fichier `Plugins\LPSandbox\Source\LPSandbox\LPSandboxMeasureTour.cpp`)
|
|||
|
|
- Le mode de capture est unique : `FScreenshotRequest::RequestScreenshot(<chemin>, false, false)`, c'est-à-dire sans
|
|||
|
|
interface et sans suffixe. Il y a **4 appels** (`grep -c` = 4) :
|
|||
|
|
- l. 127 : passages automatiques (`TickAuto`) ;
|
|||
|
|
- l. 541 : planches (`TickTour`, `bPlancheTour`) ;
|
|||
|
|
- l. 561 : vignettes ;
|
|||
|
|
- l. 566 : tournée simple.
|
|||
|
|
- Les images ne sont pas écrites telles quelles. Elles passent par le délégué `UGameViewportClient::OnScreenshotCaptured()`,
|
|||
|
|
branché l. 427 (`SavePlancheFrame`) et l. 433 (`SaveVignette`), débranché l. 458 et 468 (4 occurrences, `grep -c` = 4).
|
|||
|
|
Ensuite :
|
|||
|
|
- `SavePlancheFrame` (l. 611-619) empile les `TArray<FColor>` ;
|
|||
|
|
- `WritePlanche` (l. 621-681) recadre le carré commun, ramène chaque image à 256 px, encode en JPEG qualité 85 et écrit
|
|||
|
|
`<Clip>.jpg` et `<Clip>.json`. Il trace `PLANCHE ... (carre N px)` à la l. 679.
|
|||
|
|
- Le cadrage se fait dans l'espace écran du viewport : `ProjectWorldLocationToScreen` des os (l. 533-539 pour les planches,
|
|||
|
|
l. 552-558 pour les vignettes), appliqué ensuite sur l'image reçue (l. 632-642, 581-591). **Toute solution doit donc garder
|
|||
|
|
une image de la même taille que le viewport (1600×900)**, sinon le carré ne tombe plus au même endroit.
|
|||
|
|
- L'addon n'a aucune autre voie de capture : `HighResShot`, `SceneCapture` et `ReadPixels` n'apparaissent dans aucun
|
|||
|
|
fichier de `Plugins\LPSandbox\Source` (`grep -rln`, code 1). Aucune garde « fenêtre minimisée » n'existe non plus :
|
|||
|
|
`IsWindowMinimized`, `RenderOffScreen` et `bAllowMinimize` n'apparaissent ni dans les sources ni dans les outils de
|
|||
|
|
l'addon, ni dans `Config` du jeu 5.8.
|
|||
|
|
- `LPSandboxMeasureTour.cpp` est **identique** entre `ue5.8` et la branche addon 5.5 `lot/DC3-atelier`
|
|||
|
|
(`git diff --stat ue5.8 lot/DC3-atelier -- <fichier>` : seuls le .bat et `AnimPipeline.ps1` diffèrent).
|
|||
|
|
|
|||
|
|
### 1.3 Chemin moteur d'une capture (5.8)
|
|||
|
|
1. `FViewport::Draw` continue tant que `SizeX > 0 && SizeY > 0` (`E58\Engine\Private\UnrealClient.cpp:1789`, test l. 1820). Pendant
|
|||
|
|
la minimisation, c'est la taille de `FSceneViewport` qui compte (1600×900), pas celle de la fenêtre.
|
|||
|
|
2. `UGameViewportClient::ProcessScreenShots` (`E58\Engine\Private\GameViewportClient.cpp:2369`) passe par la branche
|
|||
|
|
sans interface → `GetViewportScreenShot` (`UnrealClient.cpp:2407-2416`) → `FRenderTarget::ReadPixels`
|
|||
|
|
(`UnrealClient.cpp:61-79`). Comme le rectangle demandé est vide, il est remplacé par `(0,0,GetSizeXY())`, soit
|
|||
|
|
**1600×900**. La lecture porte sur `GetShaderResourceTexture()`.
|
|||
|
|
3. En rendu direct à la fenêtre, la cible du viewport est le back buffer Slate :
|
|||
|
|
`Target = ViewportProvider->GetBackBufferResource()` (`E58\Engine\Private\Slate\SceneViewport.cpp:2289-2300`).
|
|||
|
|
4. Les images arrivent ensuite dans le délégué (`GameViewportClient.cpp:2543-2552`).
|
|||
|
|
|
|||
|
|
## 2. Pourquoi la fenêtre minimisée plante
|
|||
|
|
|
|||
|
|
### 2.1 Chaîne établie (sources 5.8 et journal)
|
|||
|
|
1. Minimisation → `WM_SIZE` avec `SIZE_MINIMIZED`, zone client 0×0 → `MessageHandler->OnSizeChanged(..., 0, 0, bWasMinimized)`
|
|||
|
|
(`E58\ApplicationCore\Private\Windows\WindowsApplication.cpp:3943-3966`).
|
|||
|
|
2. `FSlateApplication::OnSizeChanged` → `Renderer->RequestResize(Window, 0, 0)`
|
|||
|
|
(`E58\Slate\Private\Framework\Application\SlateApplication.cpp:6960-6970`).
|
|||
|
|
3. `FSlateRHIRenderer::RequestResize` → `FSlateViewportInfo::ResizeViewport` (`E58\SlateRHIRenderer\Private\SlateRHIRenderer.cpp:2114-2123`).
|
|||
|
|
`ClampExtent` porte la taille à `MIN_VIEWPORT_SIZE` = **8** (l. 78, 287), puis `RHIResizeViewport(8, 8)` (l. 537, 576).
|
|||
|
|
Le back buffer `BackBuffer0` mesure alors 8×8.
|
|||
|
|
4. Le viewport de jeu reste à 1600×900 et la capture demande donc une copie de 1600×900 depuis un back buffer de 8×8.
|
|||
|
|
`FD3D12DynamicRHI::GetStagingTexture` appelle `CopyTextureRegionChecked` (`E58\D3D12RHI\Private\D3D12RenderTarget.cpp:472`).
|
|||
|
|
Le contrôle `RHI_BREADCRUMB_CHECK_SHIPPINGF(CopyBox.right <= SubresourceWidth)` échoue
|
|||
|
|
(`E58\D3D12RHI\Private\D3D12Texture.cpp:1551`).
|
|||
|
|
5. Le journal de la 1re tentative U4-T6 (`D:\Game\LifePlay_Backup_5.5\U4-T6\E4_quaternius_echec1.log`) le confirme :
|
|||
|
|
- l. 6721-6722 : `Source width out of bounds for: 'BackBuffer0' -> 'None' ... dimensions [8, 8, 1], copying from [0, 0, 0] to [1600, 900, 1]` ;
|
|||
|
|
- l. 6739 : callstack `GetStagingTexture ... D3D12RenderTarget.cpp:483` (attribution de ligne d'un appel sur plusieurs lignes, l. 472-483).
|
|||
|
|
|
|||
|
|
### 2.2 Pourquoi 5.5 ne plantait pas
|
|||
|
|
- **Établi** : en 5.5, `GetStagingTexture` appelle directement `GraphicsCommandList()->CopyTextureRegion`
|
|||
|
|
(`UE_5.5\...\D3D12RenderTarget.cpp:472`). Le texte « out of bounds » ne figure nulle part dans
|
|||
|
|
`UE_5.5\...\D3D12Texture.cpp` (`grep -c` = 0). Le contrôle de bornes est donc **nouveau en 5.8**.
|
|||
|
|
- **Estimé** : en 5.5, une fenêtre minimisée aurait produit une copie invalide sans erreur (images fausses ou noires)
|
|||
|
|
plutôt qu'un plantage. L'assertion 5.8 transforme une corruption silencieuse en arrêt net.
|
|||
|
|
- **Estimé** : une fenêtre seulement recouverte, ou un écran verrouillé, ne déclenche pas `WM_SIZE`/`SIZE_MINIMIZED`. Le back
|
|||
|
|
buffer garde alors sa taille et la capture ne plante pas. Seule la minimisation est en cause (non essayé).
|
|||
|
|
- **Établi** (relance T6-bis, `U4-T6-VAL.md:110-120`) : avec la fenêtre visible, il n'y a aucune assertion et les codes sont 0.
|
|||
|
|
|
|||
|
|
### 2.3 Pas de réglage moteur « rendre même minimisé »
|
|||
|
|
- `grep -rli "ForceRenderingWhenMinimized|RenderWhenMinimized"` sur `E58` (*.cpp) ne trouve rien : **ce CVar n'existe pas en 5.8**.
|
|||
|
|
- `tick.MinimizedSyncDrawToGPU` (`E58\Engine\Private\GameEngine.cpp:105-109`) ne fait que synchroniser le GPU quand la
|
|||
|
|
fenêtre est cachée (l. 2057). Il ne règle pas la taille du back buffer.
|
|||
|
|
|
|||
|
|
## 3. Référence de non-régression : ce qui est comparable (établi)
|
|||
|
|
- **Les pixels ne sont déjà pas identiques entre 5.5 et 5.8**, fenêtre visible. Sur les 20 images de freeanims
|
|||
|
|
(`U4-REF55\freeanims\sandbox` et `U4-T6\freeanims\sandbox`), **0 sur 20** ont le même SHA-256. Écart de taille :
|
|||
|
|
planches -1,4 % à +2,5 %, vignettes +3,5 % à +19 % (`sha256sum`/`stat`, boucle sur les 30 fichiers). Le critère
|
|||
|
|
« pixels identiques » est donc hors d'atteinte quelle que soit l'option, y compris l'état actuel.
|
|||
|
|
- Trois points sont en revanche identiques :
|
|||
|
|
- les `.json` des planches (10/10, SHA-256) ;
|
|||
|
|
- les dimensions (20/20 : planches 6144×256, ou 3328×256 pour 13 images ; vignettes 512×512, lues dans l'en-tête par le
|
|||
|
|
script `imgdims.py` du bloc-notes, `python -I`) ;
|
|||
|
|
- surtout, **la géométrie de recadrage** : lignes `VIGNETTE ... (carre N px a X,Y)` et `PLANCHE ... N images D s (carre N px)`,
|
|||
|
|
une fois le chemin et le nombre d'octets retirés : **freeanims 20/20 et quaternius 86/86 identiques 5.5 / 5.8**.
|
|||
|
|
Commande : `grep -E "PLANCHE |VIGNETTE "` sur `U4-REF55\E4_<pack>.log` et `U4-T6\E4_<pack>.log`, `sed` des chemins et
|
|||
|
|
des octets, puis `diff`, qui ne signale aucune différence.
|
|||
|
|
- **Critère proposé** pour U7b : code 0 ; 0 `Assertion failed` ; géométrie 106/106 identique à `U4-REF55` ; `.json` des
|
|||
|
|
planches identiques ; dimensions identiques ; contrôle visuel d'une planche par pack. Cette géométrie dépend de la taille
|
|||
|
|
du viewport (1600×900) et de la pose : elle détecte tout changement de résolution ou de cadrage.
|
|||
|
|
|
|||
|
|
## 4. Options
|
|||
|
|
|
|||
|
|
| Option | Principe | Points d'appel à changer | Effort | Risque | Non-régression attendue |
|
|||
|
|
|---|---|---|---|---|---|
|
|||
|
|
| **A. `-RenderOffScreen`** (sans C++) | Pas de fenêtre système : Slate rend dans une texture, donc pas de `WM_SIZE` ni de back buffer 8×8. Même chaîne de capture. | `.bat:9` : transmettre `%*`, ou un .bat dédié au pilote. `AnimPipeline.ps1:334` : ajouter `-RenderOffScreen`. Libellé `:317`. Ne pas changer le défaut du .bat (`BuildMod.ps1:165`). | XS, 2 à 5 lignes, 0 C++ | Faible à moyen (voir 4.1) | Géométrie identique attendue (même viewport, même lecture) : **estimé**. Pixels : variation déjà présente aujourd'hui. |
|
|||
|
|
| **B. SceneCapture2D → render target 1600×900 → `ReadPixels`** | Capture hors du back buffer : on lit la texture du render target (`UnrealClient.cpp:61-79`), quel que soit l'état de la fenêtre. | `LPSandboxMeasureTour.cpp` : les 4 `RequestScreenshot` et les 4 branchements du délégué remplacés par un composant de capture qui copie la vue (position, rotation, FOV) du `PlayerCameraManager`. | M à L, environ 100 à 150 lignes C++ ; compilation 5.8, plus 5.5 si le fichier reste commun | Moyen à élevé | Géométrie identique seulement si la taille et le FOV sont copiés exactement. **Rendu différent** : historique TAA/TSR, exposition auto, post-process, drapeaux d'affichage (`SceneCaptureComponent.h:130-132`, `SceneCaptureComponent2D.h:174-192`). Il faut une nouvelle référence visuelle. |
|
|||
|
|
| **C1. `bAllowMinimize=False`** (sans C++) | Retire le bouton de réduction : `GetProjectSettingBool` lit `[/Script/EngineSettings.GeneralProjectSettings]` de `GGameIni` (`GameEngine.cpp:539-542, 558, 693`) → pas de `WS_MINIMIZEBOX` (`E58\ApplicationCore\Private\Windows\WindowsWindow.cpp:143-145`). Surcharge possible en ligne de commande : `-ini:Game:[/Script/EngineSettings.GeneralProjectSettings]:bAllowMinimize=False` (syntaxe générique, `E58\Core\Private\Misc\ConfigCacheIni.cpp:2322-2324`). | `.bat:9` ou argument du pilote. | XS | Moyen : couverture incomplète. Win+D, Win+M, clic sur la barre des tâches ou réduction programmée restent possibles (**incertain**, non vérifié). | Pixels et géométrie inchangés (même chemin). |
|
|||
|
|
| **C2. Garde dans la tournée** (C++) | Avant chaque `RequestScreenshot`, si `GEngine->GameViewport->GetWindow()->IsWindowMinimized()` (`SWindow.h:758`, `GameViewportClient.h:362`), alors `Restore()` (`SWindow.h:928`), journaliser, reculer l'étape (`--TourStep`, comme l. 516-519) et attendre. | `LPSandboxMeasureTour.cpp` : un point commun avant les l. 127, 541, 561 et 566. | S, environ 15 à 25 lignes | Faible à moyen : la fenêtre reprend le premier plan. Une fenêtre réduite entre le tick et le rendu dans la même image : **estimé** impossible, puisque les messages sont traités en début d'image. | Pixels et géométrie inchangés (même chemin), après au moins une image d'attente une fois la fenêtre restaurée. |
|
|||
|
|
|
|||
|
|
### 4.1 Détail de l'option A (établi dans les sources 5.8, sauf mention)
|
|||
|
|
- Sous Windows, `-RenderOffScreen` remplace l'application Windows par l'application nulle
|
|||
|
|
(`E58\ApplicationCore\Private\Windows\WindowsPlatformApplicationMisc.cpp:173-176`). Cette voie est prévue sous Windows
|
|||
|
|
(`Null\NullPlatformApplicationMisc.cpp:14-20`, commentaire Epic « supported on Linux and Windows »).
|
|||
|
|
- La fenêtre est une `FNullWindow` et `IsRenderingOffScreen()` vaut `true` (`Null\NullWindow.cpp:251-254`). Slate crée alors
|
|||
|
|
une texture `SlateOffScreenViewportTexture` à la place d'une chaîne d'échange (`SlateRHIRenderer.cpp:321-324, 340-353`),
|
|||
|
|
et `GetBackBufferResource` renvoie cette texture (`SlateRHIRenderer.cpp:471-474`). Le viewport D3D12 n'a pas de chaîne
|
|||
|
|
d'échange (`E58\D3D12RHI\Private\Windows\WindowsD3D12Viewport.cpp:34`).
|
|||
|
|
- Il n'y a ni écran de démarrage (`E58\Launch\Private\LaunchEngineLoop.cpp:3111`) ni fenêtre système, donc ni minimisation
|
|||
|
|
ni `WM_SIZE`. Slate dessine quand même la fenêtre (`SlateApplication.cpp:1249`). La taille de la fenêtre n'est pas ajustée
|
|||
|
|
à la zone de travail (`SceneViewport.cpp:1718-1720`).
|
|||
|
|
- Risques à lever par l'essai décisif :
|
|||
|
|
- (a) **taille réelle du viewport**. La zone de travail de l'application nulle part de `r.SetRes`
|
|||
|
|
(`Null\NullApplication.cpp:103-112`). La config pose `r.setres:1280x720` (`E4_freeanims.log:523`) avant que
|
|||
|
|
`-ResX=1600 -ResY=900` ne s'applique. La géométrie identique prouvera que la taille est bien 1600×900.
|
|||
|
|
- (b) **rythme des images** : sans présentation à l'écran, il n'y a ni vsync ni limite d'images. Les attentes de la tournée
|
|||
|
|
sont en secondes (`LoadWait` 0,9 s, `CameraWait` 0,5 s, l. 206-207). La géométrie ne doit donc pas bouger, mais la
|
|||
|
|
convergence TAA peut changer les pixels (**estimé**).
|
|||
|
|
- (c) aucune fenêtre visible pendant la tournée : l'utilisateur ne la voit plus. Le pilote n'attend que le processus,
|
|||
|
|
donc c'est sans effet sur lui.
|
|||
|
|
- (d) l'application nulle ne reçoit pas de clavier. C'est sans effet : la tournée est pilotée par `sandbox_tour.txt`
|
|||
|
|
(l. 290-311) et se ferme par `RequestExit` (l. 356).
|
|||
|
|
|
|||
|
|
## 5. Recommandation
|
|||
|
|
1. **A d'abord**, réservée au pilote. Le .bat transmet `%*`, ou un .bat dédié est créé, et `AnimPipeline.ps1:334` passe
|
|||
|
|
`-RenderOffScreen`. Le défaut interactif reste inchangé. Cette option supprime la cause (pas de back buffer de fenêtre),
|
|||
|
|
sans C++ et sans nouvelle compilation, sur les deux moteurs.
|
|||
|
|
2. **Si A échoue** (géométrie différente, plantage, viewport ≠ 1600×900) : **C2**, une garde de quelques lignes sur le même
|
|||
|
|
chemin de capture, donc une non-régression assurée par construction.
|
|||
|
|
3. **B en dernier recours** seulement : c'est la voie la plus coûteuse, elle change le rendu et impose une nouvelle référence
|
|||
|
|
visuelle.
|
|||
|
|
4. C1 n'est utile qu'en complément : elle ne couvre pas tous les moyens de réduire une fenêtre.
|
|||
|
|
|
|||
|
|
## 6. Essai décisif (à faire dans le lot IMPL/VAL, pas ici)
|
|||
|
|
1. **Pré-essai court**, d'une à deux minutes. Reproduire `Invoke-Sandbox` à la main pour un seul clip : `sandbox_pack.ini`
|
|||
|
|
du pack freeanims, `sandbox_mode.txt = Mesure`, `sandbox_tour.txt = vignettes planches quitter AS_RightLegKick2` (liste
|
|||
|
|
partielle autorisée, l. 294-303). Lancer avec `-RenderOffScreen`. Attendu : code 0, 0 `Assertion failed`, et les lignes
|
|||
|
|
`VIGNETTE ... (carre 695 px a 621,173)` et `PLANCHE ... 13 images 1.10 s ... (carre 724 px)`, identiques à U4-REF55.
|
|||
|
|
2. **Essai complet** : `AnimPipeline.ps1 -Pack freeanims -From 5 -To 5` (446 s en 5.8). Comparer la géométrie (20/20), les
|
|||
|
|
`.json` des planches (10/10) et les dimensions (20/20) à `U4-REF55\freeanims`, avec le même `diff` qu'au § 3. Puis
|
|||
|
|
quaternius (86/86, environ 1210 s).
|
|||
|
|
3. **Essai contraire**, si C2 est retenu : lancer fenêtre visible, la réduire pendant les planches. Attendu : ligne de
|
|||
|
|
journal « fenêtre restaurée », code 0, géométrie identique. Sans la garde, le plantage `D3D12Texture.cpp:1551` doit se
|
|||
|
|
reproduire : c'est la preuve par l'échec, à documenter avant le correctif.
|
|||
|
|
|
|||
|
|
## 7. Preuves, incertitudes
|
|||
|
|
- **Établi** : §§ 1, 2.1, 2.2 (premier point), 2.3, 3 et 4.1 (mécanique), avec les lignes et commandes citées.
|
|||
|
|
- **Estimé** :
|
|||
|
|
- comportement 5.5 en fenêtre minimisée (corruption silencieuse) ;
|
|||
|
|
- fenêtre recouverte ou écran verrouillé sans effet ;
|
|||
|
|
- géométrie conservée sous `-RenderOffScreen` ;
|
|||
|
|
- absence de course dans C2.
|
|||
|
|
- **Incertain** :
|
|||
|
|
- taille effective du viewport sous `-RenderOffScreen` avec `r.setres:1280x720` en config (essai 6.1) ;
|
|||
|
|
- couverture de C1 face à Win+D / Win+M ;
|
|||
|
|
- déterminisme des pixels d'une exécution 5.8 à l'autre (aucune paire 5.8/5.8 conservée) ;
|
|||
|
|
- recoupement par la documentation Epic, non fait faute d'outil web.
|