A vplayer fix (#821)

* Astrobot-video-fix

* Astrobot-video-fix with updated files

* Astrobot-video-fix with tests files
This commit is contained in:
Astell
2026-08-16 23:57:42 +02:00
committed by GitHub
parent 7caf430aa9
commit 1660111189
8 changed files with 1280 additions and 75 deletions
@@ -6,6 +6,7 @@ using Silk.NET.Core.Native;
using System.Collections.Concurrent;
using SharpEmu.HLE;
using SharpEmu.Libs.Agc;
using SharpEmu.Libs.AvPlayer;
using SharpEmu.Libs.Media;
using SharpEmu.Libs.Gpu;
using SharpEmu.ShaderCompiler;
@@ -2426,6 +2427,7 @@ internal static unsafe class VulkanVideoPresenter
if (IsGuestWorkCompletedLocked(pending.RequiredGuestWorkSequence))
{
presentation = _pendingGuestImagePresentations.Dequeue();
TryReplaceWithHostMovieFrame(ref presentation);
return true;
}
@@ -2458,10 +2460,97 @@ internal static unsafe class VulkanVideoPresenter
}
presentation = latest;
TryReplaceWithHostMovieFrame(ref presentation);
return true;
}
}
/// <summary>
/// AvPlayer titles whose guest texture allocators reject the decoded movie
/// surface have no sampled image to draw, so the movie would never become
/// visible. In that case the AvPlayer HLE keeps a host-decoded BGRA frame
/// available; substitute it for the guest image the title is flipping.
/// </summary>
private static void TryReplaceWithHostMovieFrame(ref Presentation presentation)
{
if (!TryTakeHostMovieFrame(out var pixels, out var width, out var height))
{
return;
}
presentation = new Presentation(
pixels,
width,
height,
presentation.Sequence,
GuestDrawKind.None,
TranslatedDraw: null,
presentation.RequiredGuestWorkSequence,
IsSplash: false);
}
/// <summary>
/// The movie is decoded on the host clock, so it must not be limited to the
/// title's flip rate: emulated flips are far slower than 59.94 Hz, which
/// would turn the intro into a slideshow. The render loop uses this on the
/// ticks where the guest produced no new flip, keeping the same presented
/// sequence so guest presentation bookkeeping is untouched.
/// </summary>
private static bool TryTakeHostMovieOnlyPresentation(
long presentedSequence,
out Presentation presentation)
{
if (!TryTakeHostMovieFrame(out var pixels, out var width, out var height))
{
presentation = default;
return false;
}
presentation = new Presentation(
pixels,
width,
height,
presentedSequence,
GuestDrawKind.None,
TranslatedDraw: null,
RequiredGuestWorkSequence: 0,
IsSplash: false);
return true;
}
private static bool TryTakeHostMovieFrame(
out byte[] pixels,
out uint width,
out uint height)
{
if (!AvPlayerExports.TryGetFallbackPresentationFrame(
out pixels,
out width,
out height,
out var serial))
{
return false;
}
if (Interlocked.Exchange(
ref _tracedAvPlayerFallbackPresentationSerial,
serial) != serial)
{
var frameCount = Interlocked.Increment(
ref _avPlayerFallbackPresentationCount);
if (frameCount <= 4 || frameCount % 30 == 0)
{
Console.Error.WriteLine(
"[VIDEOOUT][INFO] AvPlayer host fallback frame presented: " +
$"frame={frameCount} serial={serial} size={width}x{height}.");
}
}
return true;
}
private static long _tracedAvPlayerFallbackPresentationSerial;
private static long _avPlayerFallbackPresentationCount;
private static readonly HashSet<long> _tracedGuestImagePresentRejections = new();
private static bool HasPendingGuestPresentation(long presentedSequence)
@@ -15567,6 +15656,12 @@ internal static unsafe class VulkanVideoPresenter
tookPresentation = TryTakePresentation(_presentedSequence, out presentation);
}
if (!tookPresentation &&
TryTakeHostMovieOnlyPresentation(_presentedSequence, out presentation))
{
tookPresentation = true;
}
if (!tookPresentation)
{
// A render-loop tick with no newer flip is normal. Warn only when