[Codec/Native] Real H.264 decode for sceVideodec2, fix TLS loader missing the main module (#824)

* [Codec/Native] Real H.264 decode for sceVideodec2, fix TLS loader missing the main module

sceVideodec2 was a capability-only stub: the game's video pipeline
worked end-to-end but never produced a picture, so intro/cinematic
videos stayed black even though playback "completed" without errors
(confirmed on Ghost of Yotei's intro cinematic).

- Videodec2Decoder: owns an FFmpeg H.264 session per decoder handle,
  running decode and presentation pacing on their own threads (never
  the guest thread) so a whole clip isn't decoded faster than it can
  be displayed. Converts to BGRA and submits straight to
  VulkanVideoPresenter, bypassing guest memory the same way the
  existing Bink2 path does.
- Videodec2Exports: wires the real decoder into
  sceVideodec2CreateDecoder/Decode/Flush/Reset/DeleteDecoder, falling
  back to the original no-picture stub whenever FFmpeg is unavailable
  or a given decoder failed to open.
- VulkanVideoPresenter: decoded frames were being dropped under a
  single "latest wins" slot the render loop didn't always poll in
  time before the next frame overwrote it. Queues pending video
  presentations the same way guest-image flips already are.
- DirectExecutionBackend: the TLS load patcher's one-shot scan missed
  the main game module when the entry point resolves to a separate
  bootstrap allocation, and never re-scanned lazily-committed pages
  patched in afterward -- both left FS:[0] TLS loads unpatched,
  causing an early mutex-spin boot stall (reproduced on Demon's
  Souls: stuck at import #256). Now scans both the entry point's own
  allocation and the standard PS5/PS4 image base, and re-scans each
  newly committed executable range as it's touched.

* [GUI] Add a toggle for SHARPEMU_FORCE_SUBMIT_ORPHAN_PREAMBLES

This flag already existed as an AGC workaround (forces queued GPU
command-buffer preambles through when their target queue never picks
them up, unblocking titles stuck on a WAIT_REG_MEM that never
signals) but was only reachable by setting the environment variable
by hand. Expose it as a checkbox next to the other env toggles, in
both the global Options panel and the per-game settings panel, so it
can be turned on for a specific title without touching a shell.

* [GUI] Add remaining language translations for the new env toggle

The previous commit only added the new key to en.json/fr.json,
relying on Localization's runtime fallback to English -- but
LocalizationTests.EmbeddedLanguages_ContainEveryEnglishOptionsKey
requires every Options.* key to exist in every embedded language
file, which broke CI on all three build jobs. Fills in the
remaining 13 languages.
This commit is contained in:
Foued Attar
2026-08-17 23:00:01 +02:00
committed by GitHub
parent 1660111189
commit 7521295ee1
23 changed files with 921 additions and 4 deletions
@@ -526,6 +526,9 @@ internal static unsafe class VulkanVideoPresenter
// render thread reaches the previous image, which otherwise starves
// presentation indefinitely.
private static readonly Queue<Presentation> _pendingGuestImagePresentations = new();
// Same fix as _pendingGuestImagePresentations above, for Submit()'s decoded video
// frames: a single "latest wins" slot dropped frames the render loop didn't poll in time.
private static readonly Queue<Presentation> _pendingVideoPresentations = new();
private static readonly Dictionary<ulong, long> _guestImageWorkSequences = new();
private static readonly Dictionary<ulong, uint> _availableGuestImages = new();
// Write-tracker generation last uploaded for a CPU-backed guest image.
@@ -805,6 +808,7 @@ internal static unsafe class VulkanVideoPresenter
_pendingSyncGuestWorkCount = 0;
_pendingGuestWorkBytes = 0;
_pendingGuestImagePresentations.Clear();
_pendingVideoPresentations.Clear();
_guestImageWorkSequences.Clear();
_availableGuestImages.Clear();
_cpuBackedUploadGenerations.Clear();
@@ -860,7 +864,7 @@ internal static unsafe class VulkanVideoPresenter
}
var sequence = (_latestPresentation?.Sequence ?? 0) + 1;
_latestPresentation = new Presentation(
var presentation = new Presentation(
bgraFrame,
width,
height,
@@ -869,6 +873,15 @@ internal static unsafe class VulkanVideoPresenter
TranslatedDraw: null,
RequiredGuestWorkSequence: 0,
IsSplash: false);
// Also dual-written to _latestPresentation as a fallback once the queue drains.
_pendingVideoPresentations.Enqueue(presentation);
while (_pendingVideoPresentations.Count > MaxPendingGuestFlipVersions)
{
_pendingVideoPresentations.Dequeue();
}
_latestPresentation = presentation;
if (_thread is not null)
{
return;
@@ -2435,6 +2448,19 @@ internal static unsafe class VulkanVideoPresenter
return false;
}
// Video's RequiredGuestWorkSequence is always 0, so this never blocks like the guest-image queue can.
while (_pendingVideoPresentations.Count > 0 &&
_pendingVideoPresentations.Peek().Sequence <= presentedSequence)
{
_pendingVideoPresentations.Dequeue();
}
if (_pendingVideoPresentations.Count > 0)
{
presentation = _pendingVideoPresentations.Dequeue();
return true;
}
if (_latestPresentation is not { } latest ||
latest.Sequence == presentedSequence ||
!IsGuestWorkCompletedLocked(latest.RequiredGuestWorkSequence))