Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
4cc00bb4b1 | |||
c98b7fc702 | |||
e65effcb05 | |||
c1ed150949 |
@ -20,7 +20,7 @@
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.1" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.2" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
|
@ -711,5 +711,36 @@ namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
return format.IsUint() || format.IsSint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is a float or sRGB color format.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not include normalized, compressed or depth formats.
|
||||
/// Float and sRGB formats do not participate in logical operations.
|
||||
/// </remarks>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the format is a float or sRGB color format, false otherwise</returns>
|
||||
public static bool IsFloatOrSrgb(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.R8G8B8A8Srgb:
|
||||
case Format.B8G8R8A8Srgb:
|
||||
case Format.R16Float:
|
||||
case Format.R16G16Float:
|
||||
case Format.R16G16B16Float:
|
||||
case Format.R16G16B16A16Float:
|
||||
case Format.R32Float:
|
||||
case Format.R32G32Float:
|
||||
case Format.R32G32B32Float:
|
||||
case Format.R32G32B32A32Float:
|
||||
case Format.R11G11B10Float:
|
||||
case Format.R9G9B9E5Float:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public VkFormat[] AttachmentFormats { get; }
|
||||
public int[] AttachmentIndices { get; }
|
||||
public uint AttachmentIntegerFormatMask { get; }
|
||||
public bool LogicOpsAllowed { get; }
|
||||
|
||||
public int AttachmentsCount { get; }
|
||||
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[^1] : -1;
|
||||
@ -32,7 +33,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public FramebufferParams(Device device, TextureView view, uint width, uint height)
|
||||
{
|
||||
bool isDepthStencil = view.Info.Format.IsDepthOrStencil();
|
||||
var format = view.Info.Format;
|
||||
|
||||
bool isDepthStencil = format.IsDepthOrStencil();
|
||||
|
||||
_device = device;
|
||||
_attachments = new[] { view.GetImageViewForAttachment() };
|
||||
@ -56,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AttachmentSamples = new[] { (uint)view.Info.Samples };
|
||||
AttachmentFormats = new[] { view.VkFormat };
|
||||
AttachmentIndices = isDepthStencil ? Array.Empty<int>() : new[] { 0 };
|
||||
AttachmentIntegerFormatMask = format.IsInteger() ? 1u : 0u;
|
||||
LogicOpsAllowed = !format.IsFloatOrSrgb();
|
||||
|
||||
AttachmentsCount = 1;
|
||||
|
||||
@ -85,6 +90,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
int index = 0;
|
||||
int bindIndex = 0;
|
||||
uint attachmentIntegerFormatMask = 0;
|
||||
bool allFormatsFloatOrSrgb = colorsCount != 0;
|
||||
|
||||
foreach (ITexture color in colors)
|
||||
{
|
||||
@ -101,11 +107,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AttachmentFormats[index] = texture.VkFormat;
|
||||
AttachmentIndices[index] = bindIndex;
|
||||
|
||||
if (texture.Info.Format.IsInteger())
|
||||
var format = texture.Info.Format;
|
||||
|
||||
if (format.IsInteger())
|
||||
{
|
||||
attachmentIntegerFormatMask |= 1u << bindIndex;
|
||||
}
|
||||
|
||||
allFormatsFloatOrSrgb &= format.IsFloatOrSrgb();
|
||||
|
||||
width = Math.Min(width, (uint)texture.Width);
|
||||
height = Math.Min(height, (uint)texture.Height);
|
||||
layers = Math.Min(layers, (uint)texture.Layers);
|
||||
@ -120,6 +130,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
|
||||
LogicOpsAllowed = !allFormatsFloatOrSrgb;
|
||||
|
||||
if (depthStencil is TextureView dsTexture && dsTexture.Valid)
|
||||
{
|
||||
|
@ -1498,6 +1498,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
|
||||
FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
|
||||
_newState.Internal.AttachmentIntegerFormatMask = FramebufferParams.AttachmentIntegerFormatMask;
|
||||
_newState.Internal.LogicOpsAllowed = FramebufferParams.LogicOpsAllowed;
|
||||
|
||||
for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++)
|
||||
{
|
||||
|
@ -302,6 +302,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
int attachmentCount = 0;
|
||||
int maxColorAttachmentIndex = -1;
|
||||
uint attachmentIntegerFormatMask = 0;
|
||||
bool allFormatsFloatOrSrgb = true;
|
||||
|
||||
for (int i = 0; i < Constants.MaxRenderTargets; i++)
|
||||
{
|
||||
@ -314,6 +315,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
attachmentIntegerFormatMask |= 1u << i;
|
||||
}
|
||||
|
||||
allFormatsFloatOrSrgb &= state.AttachmentFormats[i].IsFloatOrSrgb();
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,6 +328,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
pipeline.ColorBlendAttachmentStateCount = (uint)(maxColorAttachmentIndex + 1);
|
||||
pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
||||
pipeline.Internal.AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
|
||||
pipeline.Internal.LogicOpsAllowed = attachmentCount == 0 || !allFormatsFloatOrSrgb;
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
@ -560,10 +560,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
// Vendors other than NVIDIA have a bug where it enables logical operations even for float formats,
|
||||
// so we need to force disable them here.
|
||||
bool logicOpEnable = LogicOpEnable && (gd.Vendor == Vendor.Nvidia || Internal.LogicOpsAllowed);
|
||||
|
||||
var colorBlendState = new PipelineColorBlendStateCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
||||
LogicOpEnable = LogicOpEnable,
|
||||
LogicOpEnable = logicOpEnable,
|
||||
LogicOp = LogicOp,
|
||||
AttachmentCount = ColorBlendAttachmentStateCount,
|
||||
PAttachments = pColorBlendAttachmentState,
|
||||
|
@ -34,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
|
||||
public Array9<Format> AttachmentFormats;
|
||||
public uint AttachmentIntegerFormatMask;
|
||||
public bool LogicOpsAllowed;
|
||||
|
||||
public readonly override bool Equals(object obj)
|
||||
{
|
||||
|
@ -28,42 +28,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
private SchedulingState _state;
|
||||
|
||||
private AutoResetEvent _idleInterruptEvent;
|
||||
private readonly object _idleInterruptEventLock;
|
||||
|
||||
private KThread _previousThread;
|
||||
private KThread _currentThread;
|
||||
private readonly KThread _idleThread;
|
||||
|
||||
private int _coreIdleLock;
|
||||
private bool _idleSignalled = true;
|
||||
private bool _idleActive = true;
|
||||
private long _idleTimeRunning;
|
||||
|
||||
public KThread PreviousThread => _previousThread;
|
||||
public KThread CurrentThread => _currentThread;
|
||||
public long LastContextSwitchTime { get; private set; }
|
||||
public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning;
|
||||
public long TotalIdleTimeTicks => _idleTimeRunning;
|
||||
|
||||
public KScheduler(KernelContext context, int coreId)
|
||||
{
|
||||
_context = context;
|
||||
_coreId = coreId;
|
||||
|
||||
_idleInterruptEvent = new AutoResetEvent(false);
|
||||
_idleInterruptEventLock = new object();
|
||||
|
||||
KThread idleThread = CreateIdleThread(context, coreId);
|
||||
|
||||
_currentThread = idleThread;
|
||||
_idleThread = idleThread;
|
||||
|
||||
idleThread.StartHostThread();
|
||||
idleThread.SchedulerWaitEvent.Set();
|
||||
}
|
||||
|
||||
private KThread CreateIdleThread(KernelContext context, int cpuCore)
|
||||
{
|
||||
KThread idleThread = new(context);
|
||||
|
||||
idleThread.Initialize(0UL, 0UL, 0UL, PrioritiesCount, cpuCore, null, ThreadType.Dummy, IdleThreadLoop);
|
||||
|
||||
return idleThread;
|
||||
_currentThread = null;
|
||||
}
|
||||
|
||||
public static ulong SelectThreads(KernelContext context)
|
||||
@ -237,39 +220,64 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread;
|
||||
|
||||
// Request the thread running on that core to stop and reschedule, if we have one.
|
||||
if (threadToSignal != context.Schedulers[coreToSignal]._idleThread)
|
||||
{
|
||||
threadToSignal.Context.RequestInterrupt();
|
||||
}
|
||||
threadToSignal?.Context.RequestInterrupt();
|
||||
|
||||
// If the core is idle, ensure that the idle thread is awaken.
|
||||
context.Schedulers[coreToSignal]._idleInterruptEvent.Set();
|
||||
context.Schedulers[coreToSignal].NotifyIdleThread();
|
||||
|
||||
scheduledCoresMask &= ~(1UL << coreToSignal);
|
||||
}
|
||||
}
|
||||
|
||||
private void IdleThreadLoop()
|
||||
private void ActivateIdleThread()
|
||||
{
|
||||
while (_context.Running)
|
||||
while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0)
|
||||
{
|
||||
Thread.SpinWait(1);
|
||||
}
|
||||
|
||||
Thread.MemoryBarrier();
|
||||
|
||||
// Signals that idle thread is now active on this core.
|
||||
_idleActive = true;
|
||||
|
||||
TryLeaveIdle();
|
||||
|
||||
Interlocked.Exchange(ref _coreIdleLock, 0);
|
||||
}
|
||||
|
||||
private void NotifyIdleThread()
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0)
|
||||
{
|
||||
Thread.SpinWait(1);
|
||||
}
|
||||
|
||||
Thread.MemoryBarrier();
|
||||
|
||||
// Signals that the idle core may be able to exit idle.
|
||||
_idleSignalled = true;
|
||||
|
||||
TryLeaveIdle();
|
||||
|
||||
Interlocked.Exchange(ref _coreIdleLock, 0);
|
||||
}
|
||||
|
||||
public void TryLeaveIdle()
|
||||
{
|
||||
if (_idleSignalled && _idleActive)
|
||||
{
|
||||
_state.NeedsScheduling = false;
|
||||
Thread.MemoryBarrier();
|
||||
KThread nextThread = PickNextThread(_state.SelectedThread);
|
||||
KThread nextThread = PickNextThread(null, _state.SelectedThread);
|
||||
|
||||
if (_idleThread != nextThread)
|
||||
if (nextThread != null)
|
||||
{
|
||||
_idleThread.SchedulerWaitEvent.Reset();
|
||||
WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, _idleThread.SchedulerWaitEvent);
|
||||
_idleActive = false;
|
||||
nextThread.SchedulerWaitEvent.Set();
|
||||
}
|
||||
|
||||
_idleInterruptEvent.WaitOne();
|
||||
}
|
||||
|
||||
lock (_idleInterruptEventLock)
|
||||
{
|
||||
_idleInterruptEvent.Dispose();
|
||||
_idleInterruptEvent = null;
|
||||
_idleSignalled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,20 +300,37 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
// Wake all the threads that might be waiting until this thread context is unlocked.
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
{
|
||||
_context.Schedulers[core]._idleInterruptEvent.Set();
|
||||
_context.Schedulers[core].NotifyIdleThread();
|
||||
}
|
||||
|
||||
KThread nextThread = PickNextThread(selectedThread);
|
||||
KThread nextThread = PickNextThread(KernelStatic.GetCurrentThread(), selectedThread);
|
||||
|
||||
if (currentThread.Context.Running)
|
||||
{
|
||||
// Wait until this thread is scheduled again, and allow the next thread to run.
|
||||
WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
|
||||
|
||||
if (nextThread == null)
|
||||
{
|
||||
ActivateIdleThread();
|
||||
currentThread.SchedulerWaitEvent.WaitOne();
|
||||
}
|
||||
else
|
||||
{
|
||||
WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow the next thread to run.
|
||||
nextThread.SchedulerWaitEvent.Set();
|
||||
|
||||
if (nextThread == null)
|
||||
{
|
||||
ActivateIdleThread();
|
||||
}
|
||||
else
|
||||
{
|
||||
nextThread.SchedulerWaitEvent.Set();
|
||||
}
|
||||
|
||||
// We don't need to wait since the thread is exiting, however we need to
|
||||
// make sure this thread will never call the scheduler again, since it is
|
||||
@ -319,7 +344,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
}
|
||||
}
|
||||
|
||||
private KThread PickNextThread(KThread selectedThread)
|
||||
private KThread PickNextThread(KThread currentThread, KThread selectedThread)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
@ -335,7 +360,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
// on the core, as the scheduled thread will handle the next switch.
|
||||
if (selectedThread.ThreadContext.Lock())
|
||||
{
|
||||
SwitchTo(selectedThread);
|
||||
SwitchTo(currentThread, selectedThread);
|
||||
|
||||
if (!_state.NeedsScheduling)
|
||||
{
|
||||
@ -346,15 +371,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
}
|
||||
else
|
||||
{
|
||||
return _idleThread;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The core is idle now, make sure that the idle thread can run
|
||||
// and switch the core when a thread is available.
|
||||
SwitchTo(null);
|
||||
return _idleThread;
|
||||
SwitchTo(currentThread, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
_state.NeedsScheduling = false;
|
||||
@ -363,12 +388,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchTo(KThread nextThread)
|
||||
private void SwitchTo(KThread currentThread, KThread nextThread)
|
||||
{
|
||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
nextThread ??= _idleThread;
|
||||
KProcess currentProcess = currentThread?.Owner;
|
||||
|
||||
if (currentThread != nextThread)
|
||||
{
|
||||
@ -376,7 +398,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
long currentTicks = PerformanceCounter.ElapsedTicks;
|
||||
long ticksDelta = currentTicks - previousTicks;
|
||||
|
||||
currentThread.AddCpuTime(ticksDelta);
|
||||
if (currentThread == null)
|
||||
{
|
||||
Interlocked.Add(ref _idleTimeRunning, ticksDelta);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentThread.AddCpuTime(ticksDelta);
|
||||
}
|
||||
|
||||
currentProcess?.AddCpuTime(ticksDelta);
|
||||
|
||||
@ -386,13 +415,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
_previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null;
|
||||
}
|
||||
else if (currentThread == _idleThread)
|
||||
else if (currentThread == null)
|
||||
{
|
||||
_previousThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextThread.CurrentCore != _coreId)
|
||||
if (nextThread != null && nextThread.CurrentCore != _coreId)
|
||||
{
|
||||
nextThread.CurrentCore = _coreId;
|
||||
}
|
||||
@ -645,11 +674,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Ensure that the idle thread is not blocked and can exit.
|
||||
lock (_idleInterruptEventLock)
|
||||
{
|
||||
_idleInterruptEvent?.Set();
|
||||
}
|
||||
// No resources to dispose for now.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,9 +143,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
PreferredCore = cpuCore;
|
||||
AffinityMask |= 1UL << cpuCore;
|
||||
|
||||
SchedFlags = type == ThreadType.Dummy
|
||||
? ThreadSchedState.Running
|
||||
: ThreadSchedState.None;
|
||||
SchedFlags = ThreadSchedState.None;
|
||||
|
||||
ActiveCore = cpuCore;
|
||||
ObjSyncResult = KernelResult.ThreadNotStarted;
|
||||
@ -1055,6 +1053,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
// If the thread is not schedulable, we want to just run or pause
|
||||
// it directly as we don't care about priority or the core it is
|
||||
// running on in this case.
|
||||
|
||||
if (SchedFlags == ThreadSchedState.Running)
|
||||
{
|
||||
_schedulerWaitEvent.Set();
|
||||
|
@ -2,7 +2,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
enum ThreadType
|
||||
{
|
||||
Dummy,
|
||||
Kernel,
|
||||
Kernel2,
|
||||
User,
|
||||
|
Reference in New Issue
Block a user