mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 22:49:53 +08:00
[VideoOut] Add Bink2 support via FFMPEG bridge (#527)
* [VideoOut] Add Bink2 support via FFMPEG bridge * [CMake] update commit * [CMake] update commit
This commit is contained in:
@@ -2230,7 +2230,6 @@ internal static unsafe class VulkanVideoPresenter
|
||||
if (IsGuestWorkCompletedLocked(pending.RequiredGuestWorkSequence))
|
||||
{
|
||||
presentation = _pendingGuestImagePresentations.Dequeue();
|
||||
TryReplaceWithBinkFrame(ref presentation);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2263,29 +2262,10 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
|
||||
presentation = latest;
|
||||
TryReplaceWithBinkFrame(ref presentation);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryReplaceWithBinkFrame(ref Presentation presentation)
|
||||
{
|
||||
if (!Bink2MovieBridge.TryDecodeNextFrame(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);
|
||||
}
|
||||
|
||||
private static readonly HashSet<long> _tracedGuestImagePresentRejections = new();
|
||||
|
||||
private static bool HasPendingGuestPresentation(long presentedSequence)
|
||||
@@ -2835,6 +2815,40 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private VkBuffer _stagingBuffer;
|
||||
private DeviceMemory _stagingMemory;
|
||||
private ulong _stagingSize;
|
||||
private VkBuffer[] _frameUploadBuffers = [];
|
||||
private DeviceMemory[] _frameUploadMemory = [];
|
||||
private nint[] _frameUploadMapped = [];
|
||||
private Image _hostMovieImage;
|
||||
private DeviceMemory _hostMovieImageMemory;
|
||||
private ImageView _hostMovieImageView;
|
||||
private uint _hostMovieImageWidth;
|
||||
private uint _hostMovieImageHeight;
|
||||
private Format _hostMovieImageFormat;
|
||||
private uint _hostMovieImageDstSelect;
|
||||
private bool _hostMovieImageInitialized;
|
||||
private Image _hostMovieChromaImage;
|
||||
private DeviceMemory _hostMovieChromaImageMemory;
|
||||
private ImageView _hostMovieChromaImageView;
|
||||
private uint _hostMovieChromaImageWidth;
|
||||
private uint _hostMovieChromaImageHeight;
|
||||
private uint _hostMovieChromaImageDstSelect;
|
||||
private bool _hostMovieChromaImageInitialized;
|
||||
private byte[]? _hostMovieFramePixels;
|
||||
private byte[]? _hostMovieLumaPixels;
|
||||
private byte[]? _hostMovieChromaPixels;
|
||||
private uint _hostMovieFrameWidth;
|
||||
private uint _hostMovieFrameHeight;
|
||||
private long _hostMovieFrameSerial;
|
||||
private long _hostMovieConvertedFrameSerial = -1;
|
||||
private long _hostMovieLumaUploadedFrameSerial = -1;
|
||||
private long _hostMovieChromaUploadedFrameSerial = -1;
|
||||
private string? _hostMovieFramePath;
|
||||
private ulong _hostMovieLumaTextureAddress;
|
||||
private ulong _hostMovieChromaTextureAddress;
|
||||
private uint _hostMovieLumaDstSelect;
|
||||
private uint _hostMovieChromaDstSelect;
|
||||
private readonly HashSet<string> _tracedHostMovieTextureBindings =
|
||||
new(StringComparer.Ordinal);
|
||||
// Perf overlay: CPU-rasterized panel copied through per-slot staging
|
||||
// buffers into one image, then blitted onto the swapchain.
|
||||
private Image _overlayImage;
|
||||
@@ -3027,6 +3041,9 @@ internal static unsafe class VulkanVideoPresenter
|
||||
public bool OwnsStorage;
|
||||
public bool IsStorage;
|
||||
public bool Cached;
|
||||
public bool IsHostMovie;
|
||||
public int HostMoviePlane = -1;
|
||||
public long HostMovieFrameSerial;
|
||||
public ulong CpuContentFingerprint;
|
||||
public bool UpdatesCpuContent;
|
||||
public GuestSampler SamplerState;
|
||||
@@ -4328,6 +4345,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
var surfaceFormat = ChooseSurfaceFormat(formats);
|
||||
_swapchainFormat = surfaceFormat.Format;
|
||||
_extent = ChooseExtent(capabilities);
|
||||
Bink2MovieBridge.SetPresentationSize(_extent.Width, _extent.Height);
|
||||
var presentMode = ChoosePresentMode();
|
||||
var imageCount = capabilities.MinImageCount + 1;
|
||||
if (capabilities.MaxImageCount != 0)
|
||||
@@ -4474,6 +4492,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_presentationCommandBuffer = _commandBuffer;
|
||||
|
||||
CreateStagingBuffer((ulong)_extent.Width * _extent.Height * 4);
|
||||
CreateFrameUploadBuffers(_stagingSize);
|
||||
CreateOverlayResources();
|
||||
}
|
||||
|
||||
@@ -6076,10 +6095,15 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
}
|
||||
|
||||
var hostMovieTextures = FindHostMovieTextureBindings(draw.Textures);
|
||||
for (var index = 0; index < draw.Textures.Count; index++)
|
||||
{
|
||||
var texture = draw.Textures[index];
|
||||
var resolved = ResolveTextureResource(texture);
|
||||
var resolved = index == hostMovieTextures.Luma
|
||||
? CreateHostMovieTextureResource(texture, plane: 0)
|
||||
: index == hostMovieTextures.Chroma
|
||||
? CreateHostMovieTextureResource(texture, plane: 1)
|
||||
: ResolveTextureResource(texture);
|
||||
var feedbackTarget = !texture.IsStorage
|
||||
? feedbackTargets?.FirstOrDefault(target =>
|
||||
ReferenceEquals(resolved.GuestImage, target))
|
||||
@@ -6934,6 +6958,343 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
}
|
||||
|
||||
private void PumpHostMovieFrame()
|
||||
{
|
||||
if (!Bink2MovieBridge.TryDecodeNextFrame(
|
||||
advanceClock: _hostMovieLumaTextureAddress != 0 &&
|
||||
_hostMovieChromaTextureAddress != 0,
|
||||
out var pixels,
|
||||
out var width,
|
||||
out var height,
|
||||
out var advanced,
|
||||
out var frameSerial,
|
||||
out var hostPath))
|
||||
{
|
||||
// Keep the last decoded image until a replacement arrives.
|
||||
// Movie sessions are queued asynchronously; clearing here
|
||||
// exposes the guest decoder's neutral surfaces between the
|
||||
// final frame of one movie and the first frame of the next.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(
|
||||
_hostMovieFramePath,
|
||||
hostPath,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_hostMovieFramePath = hostPath;
|
||||
_hostMovieLumaTextureAddress = 0;
|
||||
_hostMovieChromaTextureAddress = 0;
|
||||
_hostMovieLumaDstSelect = 0;
|
||||
_hostMovieChromaDstSelect = 0;
|
||||
_hostMovieConvertedFrameSerial = -1;
|
||||
_hostMovieLumaUploadedFrameSerial = -1;
|
||||
_hostMovieChromaUploadedFrameSerial = -1;
|
||||
}
|
||||
|
||||
if (!advanced && _hostMovieFramePixels is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_hostMovieFramePixels = pixels;
|
||||
_hostMovieFrameWidth = width;
|
||||
_hostMovieFrameHeight = height;
|
||||
_hostMovieFrameSerial = frameSerial;
|
||||
}
|
||||
|
||||
private readonly record struct HostMovieTextureBindings(int Luma, int Chroma)
|
||||
{
|
||||
public static HostMovieTextureBindings None { get; } = new(-1, -1);
|
||||
}
|
||||
|
||||
private HostMovieTextureBindings FindHostMovieTextureBindings(
|
||||
IReadOnlyList<GuestDrawTexture> textures)
|
||||
{
|
||||
if (_hostMovieFramePixels is null ||
|
||||
_hostMovieFrameWidth == 0 ||
|
||||
_hostMovieFrameHeight == 0)
|
||||
{
|
||||
return HostMovieTextureBindings.None;
|
||||
}
|
||||
|
||||
if (_hostMovieLumaTextureAddress != 0 &&
|
||||
_hostMovieChromaTextureAddress != 0)
|
||||
{
|
||||
var lumaIndex = -1;
|
||||
var chromaIndex = -1;
|
||||
for (var index = 0; index < textures.Count; index++)
|
||||
{
|
||||
if (textures[index].Address == _hostMovieLumaTextureAddress)
|
||||
{
|
||||
lumaIndex = index;
|
||||
}
|
||||
else if (textures[index].Address == _hostMovieChromaTextureAddress)
|
||||
{
|
||||
chromaIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
if (lumaIndex >= 0 && chromaIndex >= 0)
|
||||
{
|
||||
return RememberHostMovieTextureMappings(
|
||||
textures,
|
||||
lumaIndex,
|
||||
chromaIndex);
|
||||
}
|
||||
|
||||
// Bluepoint alternates decoder output between multiple Y/UV
|
||||
// surface pairs. Fall through and discover the active pair
|
||||
// instead of sampling the stale guest surface on every other
|
||||
// movie draw.
|
||||
}
|
||||
|
||||
var bestLumaIndex = -1;
|
||||
var bestChromaIndex = -1;
|
||||
ulong bestArea = 0;
|
||||
for (var lumaIndex = 0; lumaIndex < textures.Count; lumaIndex++)
|
||||
{
|
||||
var luma = textures[lumaIndex];
|
||||
if (!IsHostMovieLumaCandidate(luma))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var chromaIndex = 0; chromaIndex < textures.Count; chromaIndex++)
|
||||
{
|
||||
var chroma = textures[chromaIndex];
|
||||
if (!IsHostMovieChromaCandidate(luma, chroma))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var area = (ulong)luma.Width * luma.Height;
|
||||
if (bestLumaIndex < 0 || area > bestArea)
|
||||
{
|
||||
bestLumaIndex = lumaIndex;
|
||||
bestChromaIndex = chromaIndex;
|
||||
bestArea = area;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestLumaIndex < 0 || bestChromaIndex < 0)
|
||||
{
|
||||
return HostMovieTextureBindings.None;
|
||||
}
|
||||
|
||||
var lumaTexture = textures[bestLumaIndex];
|
||||
var chromaTexture = textures[bestChromaIndex];
|
||||
_hostMovieLumaTextureAddress = lumaTexture.Address;
|
||||
_hostMovieChromaTextureAddress = chromaTexture.Address;
|
||||
_hostMovieLumaDstSelect = lumaTexture.DstSelect;
|
||||
_hostMovieChromaDstSelect = chromaTexture.DstSelect;
|
||||
var traceKey =
|
||||
$"{_hostMovieFramePath}|{lumaTexture.Address:X16}|{chromaTexture.Address:X16}";
|
||||
if (_tracedHostMovieTextureBindings.Add(traceKey))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Bink2 YUV textures bound: " +
|
||||
$"{Path.GetFileName(_hostMovieFramePath)} " +
|
||||
$"y={bestLumaIndex}:0x{lumaTexture.Address:X16}:" +
|
||||
$"{lumaTexture.Width}x{lumaTexture.Height}:dst=0x{lumaTexture.DstSelect:X} " +
|
||||
$"uv={bestChromaIndex}:0x{chromaTexture.Address:X16}:" +
|
||||
$"{chromaTexture.Width}x{chromaTexture.Height}:dst=0x{chromaTexture.DstSelect:X} " +
|
||||
$"host={_hostMovieFrameWidth}x{_hostMovieFrameHeight}.");
|
||||
}
|
||||
|
||||
return new HostMovieTextureBindings(bestLumaIndex, bestChromaIndex);
|
||||
}
|
||||
|
||||
private HostMovieTextureBindings RememberHostMovieTextureMappings(
|
||||
IReadOnlyList<GuestDrawTexture> textures,
|
||||
int lumaIndex,
|
||||
int chromaIndex)
|
||||
{
|
||||
_hostMovieLumaDstSelect = textures[lumaIndex].DstSelect;
|
||||
_hostMovieChromaDstSelect = textures[chromaIndex].DstSelect;
|
||||
return new HostMovieTextureBindings(lumaIndex, chromaIndex);
|
||||
}
|
||||
|
||||
private bool IsHostMovieLumaCandidate(GuestDrawTexture texture)
|
||||
{
|
||||
if (texture.Address == 0 ||
|
||||
texture.IsStorage ||
|
||||
texture.IsFallback ||
|
||||
texture.ArrayedView ||
|
||||
texture.ArrayLayers > 1 ||
|
||||
texture.Format != 1 ||
|
||||
texture.NumberType != 0 ||
|
||||
texture.Width < 1280 ||
|
||||
texture.Height < 720)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var guestAspect = (ulong)texture.Width * _hostMovieFrameHeight;
|
||||
var hostAspect = (ulong)texture.Height * _hostMovieFrameWidth;
|
||||
var difference = guestAspect > hostAspect
|
||||
? guestAspect - hostAspect
|
||||
: hostAspect - guestAspect;
|
||||
return difference * 100 <= Math.Max(guestAspect, hostAspect) * 2;
|
||||
}
|
||||
|
||||
private static bool IsHostMovieChromaCandidate(
|
||||
GuestDrawTexture luma,
|
||||
GuestDrawTexture chroma)
|
||||
{
|
||||
return chroma.Address != 0 &&
|
||||
chroma.Address != luma.Address &&
|
||||
!chroma.IsStorage &&
|
||||
!chroma.IsFallback &&
|
||||
!chroma.ArrayedView &&
|
||||
chroma.ArrayLayers <= 1 &&
|
||||
chroma.Format == 3 &&
|
||||
chroma.NumberType == 0 &&
|
||||
luma.Width == chroma.Width * 2 &&
|
||||
luma.Height == chroma.Height * 2;
|
||||
}
|
||||
|
||||
private TextureResource CreateHostMovieTextureResource(
|
||||
GuestDrawTexture texture,
|
||||
int plane)
|
||||
{
|
||||
EnsureHostMovieYuvFrame();
|
||||
var isLuma = plane == 0;
|
||||
var pixels = isLuma ? _hostMovieLumaPixels! : _hostMovieChromaPixels!;
|
||||
var width = isLuma
|
||||
? _hostMovieFrameWidth
|
||||
: (_hostMovieFrameWidth + 1) / 2;
|
||||
var height = isLuma
|
||||
? _hostMovieFrameHeight
|
||||
: (_hostMovieFrameHeight + 1) / 2;
|
||||
EnsureHostMovieImages(
|
||||
_hostMovieFrameWidth,
|
||||
_hostMovieFrameHeight,
|
||||
_hostMovieLumaDstSelect,
|
||||
(_hostMovieFrameWidth + 1) / 2,
|
||||
(_hostMovieFrameHeight + 1) / 2,
|
||||
_hostMovieChromaDstSelect);
|
||||
|
||||
var uploadedFrameSerial = isLuma
|
||||
? _hostMovieLumaUploadedFrameSerial
|
||||
: _hostMovieChromaUploadedFrameSerial;
|
||||
var needsUpload = uploadedFrameSerial != _hostMovieFrameSerial;
|
||||
VkBuffer stagingBuffer = default;
|
||||
DeviceMemory stagingMemory = default;
|
||||
if (needsUpload)
|
||||
{
|
||||
(stagingBuffer, stagingMemory) = CreateTextureStagingBuffer(
|
||||
pixels,
|
||||
$"Bink2 frame {_hostMovieFrameSerial} plane {plane} staging");
|
||||
}
|
||||
|
||||
return new TextureResource
|
||||
{
|
||||
Address = texture.Address,
|
||||
StagingBuffer = stagingBuffer,
|
||||
StagingMemory = stagingMemory,
|
||||
Image = isLuma ? _hostMovieImage : _hostMovieChromaImage,
|
||||
View = isLuma ? _hostMovieImageView : _hostMovieChromaImageView,
|
||||
Width = width,
|
||||
Height = height,
|
||||
RowLength = width,
|
||||
DstSelect = texture.DstSelect,
|
||||
NeedsUpload = needsUpload,
|
||||
IsHostMovie = true,
|
||||
HostMoviePlane = plane,
|
||||
HostMovieFrameSerial = _hostMovieFrameSerial,
|
||||
SamplerState = texture.Sampler,
|
||||
};
|
||||
}
|
||||
|
||||
private void EnsureHostMovieYuvFrame()
|
||||
{
|
||||
if (_hostMovieConvertedFrameSerial == _hostMovieFrameSerial)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bgra = _hostMovieFramePixels ??
|
||||
throw new InvalidOperationException("Host movie frame is unavailable.");
|
||||
var width = checked((int)_hostMovieFrameWidth);
|
||||
var height = checked((int)_hostMovieFrameHeight);
|
||||
var chromaWidth = (width + 1) / 2;
|
||||
var chromaHeight = (height + 1) / 2;
|
||||
if (_hostMovieLumaPixels?.Length != width * height)
|
||||
{
|
||||
_hostMovieLumaPixels = GC.AllocateUninitializedArray<byte>(width * height);
|
||||
}
|
||||
if (_hostMovieChromaPixels?.Length != chromaWidth * chromaHeight * 2)
|
||||
{
|
||||
_hostMovieChromaPixels =
|
||||
GC.AllocateUninitializedArray<byte>(chromaWidth * chromaHeight * 2);
|
||||
}
|
||||
|
||||
ConvertBgraToYuv420(
|
||||
bgra,
|
||||
width,
|
||||
height,
|
||||
_hostMovieLumaPixels,
|
||||
_hostMovieChromaPixels);
|
||||
_hostMovieConvertedFrameSerial = _hostMovieFrameSerial;
|
||||
}
|
||||
|
||||
internal static void ConvertBgraToYuv420(
|
||||
ReadOnlySpan<byte> bgra,
|
||||
int width,
|
||||
int height,
|
||||
Span<byte> luma,
|
||||
Span<byte> chroma)
|
||||
{
|
||||
var chromaWidth = (width + 1) / 2;
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
for (var x = 0; x < width; x++)
|
||||
{
|
||||
var source = (y * width + x) * 4;
|
||||
var b = bgra[source];
|
||||
var g = bgra[source + 1];
|
||||
var r = bgra[source + 2];
|
||||
luma[y * width + x] = ClampByte(
|
||||
(54 * r + 183 * g + 19 * b + 128) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
for (var y = 0; y < height; y += 2)
|
||||
{
|
||||
for (var x = 0; x < width; x += 2)
|
||||
{
|
||||
var red = 0;
|
||||
var green = 0;
|
||||
var blue = 0;
|
||||
var samples = 0;
|
||||
for (var sampleY = y; sampleY < Math.Min(y + 2, height); sampleY++)
|
||||
{
|
||||
for (var sampleX = x; sampleX < Math.Min(x + 2, width); sampleX++)
|
||||
{
|
||||
var source = (sampleY * width + sampleX) * 4;
|
||||
blue += bgra[source];
|
||||
green += bgra[source + 1];
|
||||
red += bgra[source + 2];
|
||||
samples++;
|
||||
}
|
||||
}
|
||||
|
||||
red /= samples;
|
||||
green /= samples;
|
||||
blue /= samples;
|
||||
var destination = ((y / 2) * chromaWidth + x / 2) * 2;
|
||||
chroma[destination] = ClampByte(
|
||||
((128 * red - 116 * green - 12 * blue + 128) >> 8) + 128);
|
||||
chroma[destination + 1] = ClampByte(
|
||||
((-29 * red - 99 * green + 128 * blue + 128) >> 8) + 128);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte ClampByte(int value) => (byte)Math.Clamp(value, 0, 255);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private TextureResource ResolveTextureResource(GuestDrawTexture texture)
|
||||
{
|
||||
@@ -9539,6 +9900,26 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_stagingSize = size;
|
||||
}
|
||||
|
||||
private void CreateFrameUploadBuffers(ulong size)
|
||||
{
|
||||
_frameUploadBuffers = new VkBuffer[MaxFramesInFlight];
|
||||
_frameUploadMemory = new DeviceMemory[MaxFramesInFlight];
|
||||
_frameUploadMapped = new nint[MaxFramesInFlight];
|
||||
for (var slot = 0; slot < MaxFramesInFlight; slot++)
|
||||
{
|
||||
_frameUploadBuffers[slot] = CreateBuffer(
|
||||
size,
|
||||
BufferUsageFlags.TransferSrcBit,
|
||||
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit,
|
||||
out _frameUploadMemory[slot]);
|
||||
void* mapped;
|
||||
Check(
|
||||
_vk.MapMemory(_device, _frameUploadMemory[slot], 0, size, 0, &mapped),
|
||||
"vkMapMemory(frame upload)");
|
||||
_frameUploadMapped[slot] = (nint)mapped;
|
||||
}
|
||||
}
|
||||
|
||||
private uint FindMemoryType(uint typeBits, MemoryPropertyFlags requiredFlags)
|
||||
{
|
||||
_vk.GetPhysicalDeviceMemoryProperties(_physicalDevice, out var properties);
|
||||
@@ -9590,6 +9971,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
PumpHostMovieFrame();
|
||||
|
||||
if (_skipAllCompute ||
|
||||
AddressListContains("SHARPEMU_SKIP_COMPUTE_CS", work.ShaderAddress) ||
|
||||
(_skipTallComputeZ > 0 && work.GroupCountZ >= _skipTallComputeZ))
|
||||
@@ -12476,10 +12859,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
EvictDirtyCachedTextures();
|
||||
var completedWork = 0;
|
||||
HashSet<string>? deferredOrderedQueues = null;
|
||||
var renderWorkDeadline = _renderWorkBudgetTicks > 0
|
||||
? System.Diagnostics.Stopwatch.GetTimestamp() + _renderWorkBudgetTicks
|
||||
var workBudgetTicks = _renderWorkBudgetTicks;
|
||||
var renderWorkDeadline = workBudgetTicks > 0
|
||||
? System.Diagnostics.Stopwatch.GetTimestamp() + workBudgetTicks
|
||||
: long.MaxValue;
|
||||
while (completedWork < _maxGuestWorkPerRender)
|
||||
var workLimit = _maxGuestWorkPerRender;
|
||||
while (completedWork < workLimit)
|
||||
{
|
||||
// Never block the macOS main thread waiting for in-flight GPU
|
||||
// work to drain. If submission is at capacity (a slow-compute
|
||||
@@ -12536,6 +12921,14 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
try
|
||||
{
|
||||
// A host-decoded movie only overrides which image gets
|
||||
// presented (see the presentation selection below); the
|
||||
// guest's own command stream keeps draining normally.
|
||||
// Silently discarding these instead of executing them
|
||||
// leaves guest-visible completion state (labels, buffers,
|
||||
// job results) permanently unwritten, which desyncs the
|
||||
// engine's own job system and previously crashed it
|
||||
// shortly after the movie finished.
|
||||
switch (work)
|
||||
{
|
||||
case VulkanOffscreenGuestDraw offscreenDraw:
|
||||
@@ -12613,7 +13006,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
FlushBatchedGuestCommands();
|
||||
CollectAbandonedGuestImageVersions();
|
||||
|
||||
if (!TryTakePresentation(_presentedSequence, out var presentation))
|
||||
Presentation presentation;
|
||||
if (!TryTakePresentation(_presentedSequence, out presentation))
|
||||
{
|
||||
if (_pendingHostSplashReplay is { } splash)
|
||||
{
|
||||
@@ -12637,7 +13031,6 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_hostSurface is not null)
|
||||
{
|
||||
if (presentation.IsSplash && presentation.Pixels is not null)
|
||||
@@ -12690,6 +13083,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TranslatedDrawResources? translatedResources = null;
|
||||
@@ -12817,19 +13211,11 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
if (pixels is not null)
|
||||
{
|
||||
// The staging buffer is shared across frame slots; a CPU
|
||||
// pixel upload (splash / host frames) degrades to serial
|
||||
// presentation rather than corrupting an in-flight copy.
|
||||
WaitAllFrameSlots();
|
||||
void* mapped;
|
||||
Check(
|
||||
_vk.MapMemory(_device, _stagingMemory, 0, (ulong)pixels.Length, 0, &mapped),
|
||||
"vkMapMemory");
|
||||
var mapped = (void*)_frameUploadMapped[frameSlot];
|
||||
fixed (byte* source = pixels)
|
||||
{
|
||||
System.Buffer.MemoryCopy(source, mapped, pixels.Length, pixels.Length);
|
||||
}
|
||||
_vk.UnmapMemory(_device, _stagingMemory);
|
||||
}
|
||||
|
||||
Check(_vk.ResetCommandBuffer(_commandBuffer, 0), "vkResetCommandBuffer");
|
||||
@@ -12843,7 +13229,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
PipelineStageFlags waitStage;
|
||||
if (pixels is not null)
|
||||
{
|
||||
RecordUpload(imageIndex);
|
||||
RecordUpload(imageIndex, frameSlot);
|
||||
waitStage = PipelineStageFlags.TransferBit;
|
||||
}
|
||||
else if (presentation.DrawKind == GuestDrawKind.FullscreenBarycentric)
|
||||
@@ -13365,11 +13751,22 @@ internal static unsafe class VulkanVideoPresenter
|
||||
continue;
|
||||
}
|
||||
|
||||
var hostMovieImageInitialized = texture.HostMoviePlane switch
|
||||
{
|
||||
0 => _hostMovieImageInitialized,
|
||||
1 => _hostMovieChromaImageInitialized,
|
||||
_ => false,
|
||||
};
|
||||
var toTransfer = new ImageMemoryBarrier
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = texture.IsHostMovie && hostMovieImageInitialized
|
||||
? AccessFlags.ShaderReadBit
|
||||
: 0,
|
||||
DstAccessMask = AccessFlags.TransferWriteBit,
|
||||
OldLayout = ImageLayout.Undefined,
|
||||
OldLayout = texture.IsHostMovie && hostMovieImageInitialized
|
||||
? ImageLayout.ShaderReadOnlyOptimal
|
||||
: ImageLayout.Undefined,
|
||||
NewLayout = ImageLayout.TransferDstOptimal,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
@@ -13378,7 +13775,9 @@ internal static unsafe class VulkanVideoPresenter
|
||||
};
|
||||
_vk.CmdPipelineBarrier(
|
||||
_commandBuffer,
|
||||
PipelineStageFlags.TopOfPipeBit,
|
||||
texture.IsHostMovie && hostMovieImageInitialized
|
||||
? PipelineStageFlags.AllCommandsBit
|
||||
: PipelineStageFlags.TopOfPipeBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
0,
|
||||
0,
|
||||
@@ -13438,6 +13837,19 @@ internal static unsafe class VulkanVideoPresenter
|
||||
// reuse the image without restaging it.
|
||||
texture.NeedsUpload = false;
|
||||
}
|
||||
if (texture.IsHostMovie)
|
||||
{
|
||||
if (texture.HostMoviePlane == 0)
|
||||
{
|
||||
_hostMovieImageInitialized = true;
|
||||
_hostMovieLumaUploadedFrameSerial = texture.HostMovieFrameSerial;
|
||||
}
|
||||
else if (texture.HostMoviePlane == 1)
|
||||
{
|
||||
_hostMovieChromaImageInitialized = true;
|
||||
_hostMovieChromaUploadedFrameSerial = texture.HostMovieFrameSerial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14751,7 +15163,176 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
}
|
||||
|
||||
private void RecordUpload(uint imageIndex)
|
||||
private void EnsureHostMovieImages(
|
||||
uint lumaWidth,
|
||||
uint lumaHeight,
|
||||
uint lumaDstSelect,
|
||||
uint chromaWidth,
|
||||
uint chromaHeight,
|
||||
uint chromaDstSelect)
|
||||
{
|
||||
if (_hostMovieImage.Handle != 0 &&
|
||||
_hostMovieImageWidth == lumaWidth &&
|
||||
_hostMovieImageHeight == lumaHeight &&
|
||||
_hostMovieImageFormat == Format.R8Unorm &&
|
||||
_hostMovieImageDstSelect == lumaDstSelect &&
|
||||
_hostMovieChromaImage.Handle != 0 &&
|
||||
_hostMovieChromaImageWidth == chromaWidth &&
|
||||
_hostMovieChromaImageHeight == chromaHeight &&
|
||||
_hostMovieChromaImageDstSelect == chromaDstSelect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hostMovieImage.Handle != 0 || _hostMovieChromaImage.Handle != 0)
|
||||
{
|
||||
FlushBatchedGuestCommands();
|
||||
WaitForAllGuestSubmissions();
|
||||
DrainFrameSlots();
|
||||
DestroyHostMovieImage();
|
||||
}
|
||||
|
||||
CreateHostMoviePlaneImage(
|
||||
lumaWidth,
|
||||
lumaHeight,
|
||||
Format.R8Unorm,
|
||||
lumaDstSelect,
|
||||
"luma",
|
||||
out _hostMovieImage,
|
||||
out _hostMovieImageMemory,
|
||||
out _hostMovieImageView);
|
||||
CreateHostMoviePlaneImage(
|
||||
chromaWidth,
|
||||
chromaHeight,
|
||||
Format.R8G8Unorm,
|
||||
chromaDstSelect,
|
||||
"chroma",
|
||||
out _hostMovieChromaImage,
|
||||
out _hostMovieChromaImageMemory,
|
||||
out _hostMovieChromaImageView);
|
||||
_hostMovieImageWidth = lumaWidth;
|
||||
_hostMovieImageHeight = lumaHeight;
|
||||
_hostMovieImageFormat = Format.R8Unorm;
|
||||
_hostMovieImageDstSelect = lumaDstSelect;
|
||||
_hostMovieChromaImageWidth = chromaWidth;
|
||||
_hostMovieChromaImageHeight = chromaHeight;
|
||||
_hostMovieChromaImageDstSelect = chromaDstSelect;
|
||||
_hostMovieImageInitialized = false;
|
||||
_hostMovieChromaImageInitialized = false;
|
||||
_hostMovieLumaUploadedFrameSerial = -1;
|
||||
_hostMovieChromaUploadedFrameSerial = -1;
|
||||
}
|
||||
|
||||
private void CreateHostMoviePlaneImage(
|
||||
uint width,
|
||||
uint height,
|
||||
Format format,
|
||||
uint dstSelect,
|
||||
string planeName,
|
||||
out Image image,
|
||||
out DeviceMemory memory,
|
||||
out ImageView view)
|
||||
{
|
||||
var imageInfo = new ImageCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageCreateInfo,
|
||||
ImageType = ImageType.Type2D,
|
||||
Format = format,
|
||||
Extent = new Extent3D(width, height, 1),
|
||||
MipLevels = 1,
|
||||
ArrayLayers = 1,
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
Tiling = ImageTiling.Optimal,
|
||||
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
InitialLayout = ImageLayout.Undefined,
|
||||
};
|
||||
Check(
|
||||
_vk.CreateImage(_device, &imageInfo, null, out image),
|
||||
$"vkCreateImage(host movie {planeName})");
|
||||
_vk.GetImageMemoryRequirements(_device, image, out var requirements);
|
||||
var allocationInfo = new MemoryAllocateInfo
|
||||
{
|
||||
SType = StructureType.MemoryAllocateInfo,
|
||||
AllocationSize = requirements.Size,
|
||||
MemoryTypeIndex = FindMemoryType(
|
||||
requirements.MemoryTypeBits,
|
||||
MemoryPropertyFlags.DeviceLocalBit),
|
||||
};
|
||||
Check(
|
||||
_vk.AllocateMemory(
|
||||
_device,
|
||||
&allocationInfo,
|
||||
null,
|
||||
out memory),
|
||||
$"vkAllocateMemory(host movie {planeName})");
|
||||
Check(
|
||||
_vk.BindImageMemory(_device, image, memory, 0),
|
||||
$"vkBindImageMemory(host movie {planeName})");
|
||||
var viewInfo = new ImageViewCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageViewCreateInfo,
|
||||
Image = image,
|
||||
ViewType = ImageViewType.Type2D,
|
||||
Format = format,
|
||||
Components = ToVkComponentMapping(dstSelect),
|
||||
SubresourceRange = ColorSubresourceRange(),
|
||||
};
|
||||
Check(
|
||||
_vk.CreateImageView(
|
||||
_device,
|
||||
&viewInfo,
|
||||
null,
|
||||
out view),
|
||||
$"vkCreateImageView(host movie {planeName})");
|
||||
SetDebugName(ObjectType.Image, image.Handle, $"SharpEmu Bink2 {planeName} image");
|
||||
SetDebugName(ObjectType.ImageView, view.Handle, $"SharpEmu Bink2 {planeName} view");
|
||||
}
|
||||
|
||||
private void DestroyHostMovieImage()
|
||||
{
|
||||
if (_hostMovieImageView.Handle != 0)
|
||||
{
|
||||
_vk.DestroyImageView(_device, _hostMovieImageView, null);
|
||||
_hostMovieImageView = default;
|
||||
}
|
||||
if (_hostMovieImage.Handle != 0)
|
||||
{
|
||||
_vk.DestroyImage(_device, _hostMovieImage, null);
|
||||
_hostMovieImage = default;
|
||||
}
|
||||
if (_hostMovieImageMemory.Handle != 0)
|
||||
{
|
||||
_vk.FreeMemory(_device, _hostMovieImageMemory, null);
|
||||
_hostMovieImageMemory = default;
|
||||
}
|
||||
if (_hostMovieChromaImageView.Handle != 0)
|
||||
{
|
||||
_vk.DestroyImageView(_device, _hostMovieChromaImageView, null);
|
||||
_hostMovieChromaImageView = default;
|
||||
}
|
||||
if (_hostMovieChromaImage.Handle != 0)
|
||||
{
|
||||
_vk.DestroyImage(_device, _hostMovieChromaImage, null);
|
||||
_hostMovieChromaImage = default;
|
||||
}
|
||||
if (_hostMovieChromaImageMemory.Handle != 0)
|
||||
{
|
||||
_vk.FreeMemory(_device, _hostMovieChromaImageMemory, null);
|
||||
_hostMovieChromaImageMemory = default;
|
||||
}
|
||||
_hostMovieImageWidth = 0;
|
||||
_hostMovieImageHeight = 0;
|
||||
_hostMovieImageFormat = Format.Undefined;
|
||||
_hostMovieChromaImageWidth = 0;
|
||||
_hostMovieChromaImageHeight = 0;
|
||||
_hostMovieImageInitialized = false;
|
||||
_hostMovieChromaImageInitialized = false;
|
||||
_hostMovieLumaUploadedFrameSerial = -1;
|
||||
_hostMovieChromaUploadedFrameSerial = -1;
|
||||
}
|
||||
|
||||
private void RecordUpload(uint imageIndex, int frameSlot)
|
||||
{
|
||||
var oldLayout = _imageInitialized[imageIndex]
|
||||
? ImageLayout.PresentSrcKhr
|
||||
@@ -14793,7 +15374,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
};
|
||||
_vk.CmdCopyBufferToImage(
|
||||
_commandBuffer,
|
||||
_stagingBuffer,
|
||||
_frameUploadBuffers[frameSlot],
|
||||
_swapchainImages[imageIndex],
|
||||
ImageLayout.TransferDstOptimal,
|
||||
1,
|
||||
@@ -15661,7 +16242,26 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
private void DestroySwapchainResources()
|
||||
{
|
||||
DestroyHostMovieImage();
|
||||
DestroyPresentEncodeImage();
|
||||
for (var slot = 0; slot < _frameUploadBuffers.Length; slot++)
|
||||
{
|
||||
if (_frameUploadMapped.Length > slot && _frameUploadMapped[slot] != 0)
|
||||
{
|
||||
_vk.UnmapMemory(_device, _frameUploadMemory[slot]);
|
||||
}
|
||||
if (_frameUploadBuffers[slot].Handle != 0)
|
||||
{
|
||||
_vk.DestroyBuffer(_device, _frameUploadBuffers[slot], null);
|
||||
}
|
||||
if (_frameUploadMemory[slot].Handle != 0)
|
||||
{
|
||||
_vk.FreeMemory(_device, _frameUploadMemory[slot], null);
|
||||
}
|
||||
}
|
||||
_frameUploadBuffers = [];
|
||||
_frameUploadMemory = [];
|
||||
_frameUploadMapped = [];
|
||||
if (_stagingBuffer.Handle != 0)
|
||||
{
|
||||
_vk.DestroyBuffer(_device, _stagingBuffer, null);
|
||||
|
||||
Reference in New Issue
Block a user