mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 15:09:42 +08:00
Agent/fix gen5 thread agc compat (#130)
* Fix Gen5 thread and AGC compatibility * Trim compatibility comments * Report selected Vulkan GPU * Clean up CPU title label * Improve emulator frame pacing and performance * Regenerate package locks with pinned SDK --------- Co-authored-by: Spooks4576 <Spooks4576@users.noreply.github.com>
This commit is contained in:
@@ -48,54 +48,106 @@ public static class VideoOutExports
|
||||
private static readonly Dictionary<int, VideoOutPortState> _ports = new();
|
||||
|
||||
// Hardware raises vblank autonomously; UE blocks its frame loop on it.
|
||||
private static Timer? _vblankPumpTimer;
|
||||
private static int _vblankPumpActive;
|
||||
private const int VblankPumpIntervalMs = 16;
|
||||
private const double VblankHz = 60.0;
|
||||
private const int VblankWaitTimeoutMilliseconds = 100;
|
||||
private static Thread? _vblankPumpThread;
|
||||
private static int _vblankPumpStarted;
|
||||
|
||||
private static readonly object _vblankEdgeGate = new();
|
||||
private static ulong _vblankEdgeSequence;
|
||||
private static long _vblankMissedEdges;
|
||||
|
||||
private static void EnsureVblankPumpStarted()
|
||||
{
|
||||
if (_vblankPumpTimer is not null)
|
||||
if (Interlocked.Exchange(ref _vblankPumpStarted, 1) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_vblankPumpTimer = new Timer(
|
||||
static _ => PumpVblanks(),
|
||||
null,
|
||||
VblankPumpIntervalMs,
|
||||
VblankPumpIntervalMs);
|
||||
HostTimerResolution.Request();
|
||||
|
||||
_vblankPumpThread = new Thread(VblankPumpLoop)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "SharpEmu VideoOut vblank",
|
||||
Priority = ThreadPriority.AboveNormal,
|
||||
};
|
||||
_vblankPumpThread.Start();
|
||||
}
|
||||
|
||||
private static void VblankPumpLoop()
|
||||
{
|
||||
var intervalTicks = Math.Max(1L, (long)(Stopwatch.Frequency / VblankHz));
|
||||
var nextEdge = Stopwatch.GetTimestamp() + intervalTicks;
|
||||
|
||||
while (true)
|
||||
{
|
||||
WaitUntilTimestamp(nextEdge);
|
||||
PumpVblanks();
|
||||
|
||||
nextEdge += intervalTicks;
|
||||
|
||||
var now = Stopwatch.GetTimestamp();
|
||||
if (nextEdge < now)
|
||||
{
|
||||
var missed = (now - nextEdge) / intervalTicks + 1;
|
||||
Interlocked.Add(ref _vblankMissedEdges, missed);
|
||||
nextEdge = now + intervalTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WaitUntilTimestamp(long deadlineTicks)
|
||||
{
|
||||
var spinThresholdTicks = Stopwatch.Frequency * 2L / 1000L;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var remaining = deadlineTicks - Stopwatch.GetTimestamp();
|
||||
if (remaining <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (remaining > spinThresholdTicks)
|
||||
{
|
||||
var sleepMilliseconds =
|
||||
(int)((remaining - spinThresholdTicks) * 1000L / Stopwatch.Frequency);
|
||||
if (sleepMilliseconds > 0)
|
||||
{
|
||||
Thread.Sleep(sleepMilliseconds);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Thread.SpinWait(64);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PumpVblanks()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _vblankPumpActive, 1) != 0)
|
||||
lock (_vblankEdgeGate)
|
||||
{
|
||||
return;
|
||||
_vblankEdgeSequence++;
|
||||
Monitor.PulseAll(_vblankEdgeGate);
|
||||
}
|
||||
|
||||
try
|
||||
VideoOutPortState[] ports;
|
||||
lock (_stateGate)
|
||||
{
|
||||
VideoOutPortState[] ports;
|
||||
lock (_stateGate)
|
||||
if (_ports.Count == 0)
|
||||
{
|
||||
if (_ports.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Signalling reaches WakeBlockedThreads -> Pump(), which serialises on one global
|
||||
// flag. Waking an unwatched queue would hold it 60x/sec and starve guest threads.
|
||||
ports = _ports.Values.Where(static port => port.VblankEvents.Count != 0).ToArray();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var port in ports)
|
||||
{
|
||||
SignalVblank(port);
|
||||
}
|
||||
// Signalling reaches WakeBlockedThreads -> Pump(), which serialises on one global
|
||||
// flag. Waking an unwatched queue would hold it 60x/sec and starve guest threads.
|
||||
ports = _ports.Values.Where(static port => port.VblankEvents.Count != 0).ToArray();
|
||||
}
|
||||
finally
|
||||
|
||||
foreach (var port in ports)
|
||||
{
|
||||
Volatile.Write(ref _vblankPumpActive, 0);
|
||||
SignalVblank(port);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +155,7 @@ public static class VideoOutExports
|
||||
private static int _nextHandle = 1;
|
||||
private static int _frameDumpCount;
|
||||
private static long _nextFrameDumpIndex;
|
||||
private static string _windowTitleBase = "SharpEmu VideoOut";
|
||||
private static string _windowTitle = "SharpEmu VideoOut";
|
||||
private static readonly bool _logFrameRate = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIDEOOUT_FPS"),
|
||||
@@ -112,6 +165,10 @@ public static class VideoOutExports
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIDEOOUT_SYNC"),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
private static readonly bool _dumpVideoOut = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_VIDEOOUT"),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
private static long _frameRateWindowStart = Stopwatch.GetTimestamp();
|
||||
private static long _submittedFrameCount;
|
||||
private static long _presentedFrameCount;
|
||||
@@ -134,10 +191,24 @@ public static class VideoOutExports
|
||||
var application = parts.Count == 0 ? "VideoOut" : string.Join(' ', parts);
|
||||
var versionSuffix = string.IsNullOrWhiteSpace(version) ? string.Empty : $" v{version.Trim()}";
|
||||
var commitSuffix = string.IsNullOrWhiteSpace(emulatorCommitSha) ? string.Empty : $" \u00b7 {emulatorCommitSha.Trim()}";
|
||||
var hardwareSuffix = $" \u00b7 {HostSystemInfo.CpuName} \u00b7 {HostSystemInfo.GpuName}";
|
||||
var hardwareSuffix = $" \u00b7 {HostSystemInfo.CpuName}";
|
||||
lock (_stateGate)
|
||||
{
|
||||
_windowTitle = $"SharpEmu{commitSuffix} - {application}{versionSuffix}{hardwareSuffix}";
|
||||
_windowTitleBase = $"SharpEmu{commitSuffix} - {application}{versionSuffix}{hardwareSuffix}";
|
||||
_windowTitle = $"{_windowTitleBase} \u00b7 {HostSystemInfo.GpuName}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetSelectedGpuName(string gpuName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(gpuName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_windowTitle = $"{_windowTitleBase} \u00b7 {gpuName.Trim()}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +447,20 @@ public static class VideoOutExports
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
EnsureVblankPumpStarted();
|
||||
|
||||
lock (_vblankEdgeGate)
|
||||
{
|
||||
var entryEdge = _vblankEdgeSequence;
|
||||
while (_vblankEdgeSequence == entryEdge)
|
||||
{
|
||||
if (!Monitor.Wait(_vblankEdgeGate, VblankWaitTimeoutMilliseconds))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SignalVblank(port);
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -987,10 +1071,7 @@ public static class VideoOutExports
|
||||
displayBuffer.PitchInPixel);
|
||||
}
|
||||
|
||||
if (string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_VIDEOOUT"),
|
||||
"1",
|
||||
StringComparison.Ordinal))
|
||||
if (_dumpVideoOut)
|
||||
{
|
||||
_ = TryDumpFrame(ctx, port, bufferIndex, flipMode, flipArg);
|
||||
}
|
||||
@@ -1050,9 +1131,11 @@ public static class VideoOutExports
|
||||
var elapsedSeconds = (double)elapsedTicks / Stopwatch.Frequency;
|
||||
var submitted = Interlocked.Exchange(ref _submittedFrameCount, 0);
|
||||
var presentedCount = Interlocked.Exchange(ref _presentedFrameCount, 0);
|
||||
var missedEdges = Interlocked.Exchange(ref _vblankMissedEdges, 0);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][PERF] videoout submitted_fps={submitted / elapsedSeconds:F1} " +
|
||||
$"presented_fps={presentedCount / elapsedSeconds:F1}");
|
||||
$"presented_fps={presentedCount / elapsedSeconds:F1} " +
|
||||
$"vblank_missed={missedEdges}");
|
||||
}
|
||||
|
||||
private static int RegisterBufferRange(VideoOutPortState port, int startIndex, ReadOnlySpan<ulong> addresses, BufferAttribute attribute, int requestedGroupIndex = -1)
|
||||
|
||||
@@ -11,6 +11,7 @@ using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using Silk.NET.Windowing;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
@@ -600,7 +601,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
var traceSubmission = false;
|
||||
lock (_gate)
|
||||
{
|
||||
var known = _availableGuestImages.ContainsKey(address);
|
||||
// VideoOut registration does not imply a rendered Vulkan image.
|
||||
var known = _gpuGuestImages.ContainsKey(address);
|
||||
if (ShouldTracePresentedGuestImageContentsForDiagnostics())
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
@@ -622,7 +624,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] vk.submit_guest_image_unknown addr=0x{address:X16} " +
|
||||
$"{width}x{height} - flip target was never registered as a render output");
|
||||
$"{width}x{height} - flip target has no completed GPU render output");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -933,6 +935,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private ExtDebugUtils? _debugUtils;
|
||||
private PhysicalDevice _physicalDevice;
|
||||
private Device _device;
|
||||
private PipelineCache _pipelineCache;
|
||||
private string? _pipelineCachePath;
|
||||
private Queue _queue;
|
||||
private uint _queueFamilyIndex;
|
||||
private SwapchainKHR _swapchain;
|
||||
@@ -950,6 +954,9 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private CommandBuffer _presentationCommandBuffer;
|
||||
private VkSemaphore _imageAvailable;
|
||||
private VkSemaphore _renderFinished;
|
||||
private Fence _presentationFence;
|
||||
private bool _presentationInFlight;
|
||||
private TranslatedDrawResources? _pendingPresentationResources;
|
||||
private VkBuffer _stagingBuffer;
|
||||
private DeviceMemory _stagingMemory;
|
||||
private ulong _stagingSize;
|
||||
@@ -966,6 +973,10 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private int _directPresentationCount;
|
||||
private readonly Dictionary<ulong, GuestImageResource> _guestImages = new();
|
||||
private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureCacheHits = new();
|
||||
private static readonly bool _dumpTextures = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_TEXTURES"),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureUploads = new();
|
||||
private readonly HashSet<(ulong Address, uint Width, uint Height, uint Format)> _dumpedTextures = new();
|
||||
private readonly HashSet<(ulong Address, int Size)> _tracedGlobalBuffers = new();
|
||||
@@ -988,7 +999,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private readonly record struct GraphicsPipelineKey(
|
||||
string VertexShader,
|
||||
string FragmentShader,
|
||||
ulong RenderPass,
|
||||
Format RenderTargetFormat,
|
||||
PrimitiveTopology Topology,
|
||||
VulkanGuestBlendState Blend,
|
||||
string ResourceLayout,
|
||||
@@ -1150,6 +1161,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
CreateSurface();
|
||||
SelectPhysicalDevice();
|
||||
CreateDevice();
|
||||
CreatePipelineCache();
|
||||
CreateSwapchain();
|
||||
CreateCommandResources();
|
||||
CreateGuestDrawResources();
|
||||
@@ -1559,8 +1571,11 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
|
||||
_vk.GetPhysicalDeviceProperties(_physicalDevice, out var selected);
|
||||
var selectedName = SilkMarshal.PtrToString((nint)selected.DeviceName) ?? "unknown";
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Vulkan device: {SilkMarshal.PtrToString((nint)selected.DeviceName)} ({selected.DeviceType})");
|
||||
$"[LOADER][INFO] Vulkan device: {selectedName} ({selected.DeviceType})");
|
||||
VideoOutExports.SetSelectedGpuName(selectedName);
|
||||
_window.Title = VideoOutExports.GetWindowTitle();
|
||||
}
|
||||
|
||||
private static int ScorePhysicalDevice(
|
||||
@@ -1743,6 +1758,110 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePipelineCache()
|
||||
{
|
||||
_vk.GetPhysicalDeviceProperties(_physicalDevice, out var properties);
|
||||
var directory = Path.Combine(AppContext.BaseDirectory, "pipeline-cache");
|
||||
_pipelineCachePath = Path.Combine(
|
||||
directory,
|
||||
$"vulkan-{properties.VendorID:X4}-{properties.DeviceID:X4}-{properties.DriverVersion:X8}.bin");
|
||||
|
||||
byte[] initialData = [];
|
||||
try
|
||||
{
|
||||
if (File.Exists(_pipelineCachePath))
|
||||
{
|
||||
initialData = File.ReadAllBytes(_pipelineCachePath);
|
||||
}
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Vulkan pipeline cache could not be read: {exception.Message}");
|
||||
}
|
||||
|
||||
fixed (byte* initialDataPointer = initialData)
|
||||
{
|
||||
var createInfo = new PipelineCacheCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineCacheCreateInfo,
|
||||
InitialDataSize = (nuint)initialData.Length,
|
||||
PInitialData = initialData.Length == 0 ? null : initialDataPointer,
|
||||
};
|
||||
var result = _vk.CreatePipelineCache(
|
||||
_device,
|
||||
&createInfo,
|
||||
null,
|
||||
out _pipelineCache);
|
||||
if (result != Result.Success && initialData.Length != 0)
|
||||
{
|
||||
createInfo.InitialDataSize = 0;
|
||||
createInfo.PInitialData = null;
|
||||
Check(
|
||||
_vk.CreatePipelineCache(
|
||||
_device,
|
||||
&createInfo,
|
||||
null,
|
||||
out _pipelineCache),
|
||||
"vkCreatePipelineCache(empty)");
|
||||
initialData = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
Check(result, "vkCreatePipelineCache");
|
||||
}
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Vulkan pipeline cache: " +
|
||||
$"{(initialData.Length == 0 ? "new" : $"loaded {initialData.Length} bytes")}");
|
||||
}
|
||||
|
||||
private void SavePipelineCache()
|
||||
{
|
||||
if (_pipelineCache.Handle == 0 || string.IsNullOrWhiteSpace(_pipelineCachePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
nuint size = 0;
|
||||
Check(
|
||||
_vk.GetPipelineCacheData(_device, _pipelineCache, &size, null),
|
||||
"vkGetPipelineCacheData(size)");
|
||||
if (size == 0 || size > int.MaxValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var data = new byte[(int)size];
|
||||
fixed (byte* dataPointer = data)
|
||||
{
|
||||
Check(
|
||||
_vk.GetPipelineCacheData(
|
||||
_device,
|
||||
_pipelineCache,
|
||||
&size,
|
||||
dataPointer),
|
||||
"vkGetPipelineCacheData");
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(_pipelineCachePath);
|
||||
if (!string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
File.WriteAllBytes(_pipelineCachePath, data.AsSpan(0, (int)size).ToArray());
|
||||
}
|
||||
catch (Exception exception) when (
|
||||
exception is IOException or UnauthorizedAccessException or InvalidOperationException)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Vulkan pipeline cache could not be saved: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSwapchain()
|
||||
{
|
||||
Check(
|
||||
@@ -1835,9 +1954,44 @@ internal static unsafe class VulkanVideoPresenter
|
||||
Check(_vk.CreateSemaphore(_device, &semaphoreInfo, null, out _imageAvailable), "vkCreateSemaphore");
|
||||
Check(_vk.CreateSemaphore(_device, &semaphoreInfo, null, out _renderFinished), "vkCreateSemaphore");
|
||||
|
||||
var fenceInfo = new FenceCreateInfo
|
||||
{
|
||||
SType = StructureType.FenceCreateInfo,
|
||||
};
|
||||
Check(
|
||||
_vk.CreateFence(_device, &fenceInfo, null, out _presentationFence),
|
||||
"vkCreateFence(presentation)");
|
||||
|
||||
CreateStagingBuffer((ulong)_extent.Width * _extent.Height * 4);
|
||||
}
|
||||
|
||||
private void CompletePendingPresentation(bool wait)
|
||||
{
|
||||
if (!_presentationInFlight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var fence = _presentationFence;
|
||||
var result = wait
|
||||
? _vk.WaitForFences(_device, 1, &fence, true, ulong.MaxValue)
|
||||
: _vk.GetFenceStatus(_device, fence);
|
||||
if (!wait && result == Result.NotReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Check(result, "vkWaitForFences(presentation)");
|
||||
_presentationInFlight = false;
|
||||
if (_pendingPresentationResources is not null)
|
||||
{
|
||||
MarkSampledImagesInitialized(_pendingPresentationResources);
|
||||
MarkStorageImagesInitialized(_pendingPresentationResources);
|
||||
DestroyTranslatedDrawResources(_pendingPresentationResources);
|
||||
_pendingPresentationResources = null;
|
||||
}
|
||||
}
|
||||
|
||||
private CommandBuffer AllocateGuestCommandBuffer()
|
||||
{
|
||||
var allocateInfo = new CommandBufferAllocateInfo
|
||||
@@ -2184,7 +2338,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
Check(
|
||||
_vk.CreateGraphicsPipelines(
|
||||
_device,
|
||||
default,
|
||||
_pipelineCache,
|
||||
1,
|
||||
&pipelineInfo,
|
||||
null,
|
||||
@@ -2219,6 +2373,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private TranslatedDrawResources CreateTranslatedDrawResources(
|
||||
VulkanTranslatedGuestDraw draw,
|
||||
RenderPass renderPass,
|
||||
Format renderTargetFormat,
|
||||
Extent2D extent)
|
||||
{
|
||||
var vertexSpirv = draw.VertexSpirv;
|
||||
@@ -2285,7 +2440,13 @@ internal static unsafe class VulkanVideoPresenter
|
||||
CreateTranslatedDescriptorResources(
|
||||
resources,
|
||||
ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit);
|
||||
CreateTranslatedPipeline(resources, vertexSpirv, draw.PixelSpirv, renderPass, extent);
|
||||
CreateTranslatedPipeline(
|
||||
resources,
|
||||
vertexSpirv,
|
||||
draw.PixelSpirv,
|
||||
renderPass,
|
||||
renderTargetFormat,
|
||||
extent);
|
||||
return resources;
|
||||
}
|
||||
catch
|
||||
@@ -2581,12 +2742,13 @@ internal static unsafe class VulkanVideoPresenter
|
||||
byte[] vertexSpirv,
|
||||
byte[] fragmentSpirv,
|
||||
RenderPass renderPass,
|
||||
Format renderTargetFormat,
|
||||
Extent2D extent)
|
||||
{
|
||||
var pipelineKey = new GraphicsPipelineKey(
|
||||
GetShaderDigest(vertexSpirv),
|
||||
GetShaderDigest(fragmentSpirv),
|
||||
renderPass.Handle,
|
||||
renderTargetFormat,
|
||||
resources.Topology,
|
||||
resources.Blend,
|
||||
GetResourceLayoutKey(resources),
|
||||
@@ -2742,7 +2904,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
Check(
|
||||
_vk.CreateGraphicsPipelines(
|
||||
_device,
|
||||
default,
|
||||
_pipelineCache,
|
||||
1,
|
||||
&pipelineInfo,
|
||||
null,
|
||||
@@ -2922,7 +3084,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
Check(
|
||||
_vk.CreateComputePipelines(
|
||||
_device,
|
||||
default,
|
||||
_pipelineCache,
|
||||
1,
|
||||
&pipelineInfo,
|
||||
null,
|
||||
@@ -3448,11 +3610,36 @@ internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
const ulong offsetBasis = 14695981039346656037;
|
||||
const ulong prime = 1099511628211;
|
||||
var hash = offsetBasis;
|
||||
foreach (var value in pixels)
|
||||
|
||||
var h0 = offsetBasis ^ (ulong)pixels.Length;
|
||||
var h1 = offsetBasis;
|
||||
var h2 = offsetBasis;
|
||||
var h3 = offsetBasis;
|
||||
|
||||
var words = MemoryMarshal.Cast<byte, ulong>(pixels);
|
||||
var index = 0;
|
||||
var blockEnd = words.Length - (words.Length & 3);
|
||||
for (; index < blockEnd; index += 4)
|
||||
{
|
||||
hash ^= value;
|
||||
hash *= prime;
|
||||
h0 = (h0 ^ words[index]) * prime;
|
||||
h1 = (h1 ^ words[index + 1]) * prime;
|
||||
h2 = (h2 ^ words[index + 2]) * prime;
|
||||
h3 = (h3 ^ words[index + 3]) * prime;
|
||||
}
|
||||
|
||||
for (; index < words.Length; index++)
|
||||
{
|
||||
h0 = (h0 ^ words[index]) * prime;
|
||||
}
|
||||
|
||||
var hash = h0;
|
||||
hash = (hash ^ h1) * prime;
|
||||
hash = (hash ^ h2) * prime;
|
||||
hash = (hash ^ h3) * prime;
|
||||
|
||||
foreach (var value in pixels[(words.Length * sizeof(ulong))..])
|
||||
{
|
||||
hash = (hash ^ value) * prime;
|
||||
}
|
||||
|
||||
return hash;
|
||||
@@ -3465,10 +3652,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
uint width,
|
||||
uint height)
|
||||
{
|
||||
if (!string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_TEXTURES"),
|
||||
"1",
|
||||
StringComparison.Ordinal) ||
|
||||
if (!_dumpTextures ||
|
||||
texture.IsFallback ||
|
||||
texture.IsStorage ||
|
||||
GetTextureBytesPerPixel(texture.Format) != 4 ||
|
||||
@@ -4489,6 +4673,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
resources = CreateTranslatedDrawResources(
|
||||
work.Draw,
|
||||
target.RenderPass,
|
||||
target.Format,
|
||||
extent);
|
||||
resources.DebugName =
|
||||
$"SharpEmu offscreen rt=0x{work.Target.Address:X16} " +
|
||||
@@ -5125,6 +5310,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
CompletePendingPresentation(wait: true);
|
||||
|
||||
byte[]? pixels = null;
|
||||
if (presentation.Pixels is { } sourcePixels)
|
||||
{
|
||||
@@ -5172,6 +5359,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
translatedResources = CreateTranslatedDrawResources(
|
||||
translatedDraw,
|
||||
_renderPass,
|
||||
_swapchainFormat,
|
||||
_extent);
|
||||
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
|
||||
!_firstGuestDrawPresented)
|
||||
@@ -5303,7 +5491,16 @@ internal static unsafe class VulkanVideoPresenter
|
||||
SignalSemaphoreCount = 1,
|
||||
PSignalSemaphores = &renderFinished,
|
||||
};
|
||||
Check(_vk.QueueSubmit(_queue, 1, &submitInfo, default), "vkQueueSubmit");
|
||||
var presentationFence = _presentationFence;
|
||||
Check(
|
||||
_vk.ResetFences(_device, 1, &presentationFence),
|
||||
"vkResetFences(presentation)");
|
||||
Check(
|
||||
_vk.QueueSubmit(_queue, 1, &submitInfo, presentationFence),
|
||||
"vkQueueSubmit");
|
||||
_presentationInFlight = true;
|
||||
_pendingPresentationResources = translatedResources;
|
||||
translatedResources = null;
|
||||
|
||||
var swapchain = _swapchain;
|
||||
var presentInfo = new PresentInfoKHR
|
||||
@@ -5319,6 +5516,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
if (presentResult == Result.ErrorOutOfDateKhr)
|
||||
{
|
||||
Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle");
|
||||
CompletePendingPresentation(wait: false);
|
||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||
if (translatedResources is not null)
|
||||
{
|
||||
@@ -5331,19 +5529,13 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
CheckSwapchainResult(presentResult, "vkQueuePresentKHR");
|
||||
recreateAfterPresent |= presentResult == Result.SuboptimalKhr;
|
||||
Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle");
|
||||
VideoOutExports.ReportPresentedFrame();
|
||||
if (_swapchainReadbackPending)
|
||||
{
|
||||
CompletePendingPresentation(wait: true);
|
||||
TraceSwapchainReadback();
|
||||
}
|
||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||
if (translatedResources is not null)
|
||||
{
|
||||
MarkSampledImagesInitialized(translatedResources);
|
||||
MarkStorageImagesInitialized(translatedResources);
|
||||
DestroyTranslatedDrawResources(translatedResources);
|
||||
}
|
||||
|
||||
_imageInitialized[imageIndex] = true;
|
||||
_presentedSequence = presentation.Sequence;
|
||||
@@ -6597,7 +6789,9 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
_vulkanReady = false;
|
||||
_vk.DeviceWaitIdle(_device);
|
||||
CompletePendingPresentation(wait: false);
|
||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||
SavePipelineCache();
|
||||
foreach (var pipeline in _computePipelines.Values)
|
||||
{
|
||||
_vk.DestroyPipeline(_device, pipeline, null);
|
||||
@@ -6608,6 +6802,11 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_vk.DestroyPipeline(_device, pipeline, null);
|
||||
}
|
||||
_graphicsPipelines.Clear();
|
||||
if (_pipelineCache.Handle != 0)
|
||||
{
|
||||
_vk.DestroyPipelineCache(_device, _pipelineCache, null);
|
||||
_pipelineCache = default;
|
||||
}
|
||||
foreach (var layout in _descriptorLayouts.Values)
|
||||
{
|
||||
_vk.DestroyPipelineLayout(_device, layout.PipelineLayout, null);
|
||||
@@ -6699,6 +6898,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Vulkan VideoOut recreating swapchain after {operation}: {result}");
|
||||
_vk.DeviceWaitIdle(_device);
|
||||
CompletePendingPresentation(wait: false);
|
||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||
DestroySwapchainResources();
|
||||
CreateSwapchain();
|
||||
@@ -6732,6 +6932,11 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_vk.DestroySemaphore(_device, _renderFinished, null);
|
||||
_renderFinished = default;
|
||||
}
|
||||
if (_presentationFence.Handle != 0)
|
||||
{
|
||||
_vk.DestroyFence(_device, _presentationFence, null);
|
||||
_presentationFence = default;
|
||||
}
|
||||
if (_barycentricPipeline.Handle != 0)
|
||||
{
|
||||
_vk.DestroyPipeline(_device, _barycentricPipeline, null);
|
||||
|
||||
Reference in New Issue
Block a user