Compare commits

...

5 Commits

Author SHA1 Message Date
162798b026 vulkan: Avoid hardcoding features in CreateDevice (#4858)
Those shouldn't have been hardcoded.
2023-05-08 10:48:16 +00:00
1b28ecd63e Vulkan: Simplify MultiFenceHolder and managing them (#4845)
* Vulkan: Simplify waitable add/remove

Removal of unnecessary hashset and dictionary

* Thread safety for GetBufferData in PersistentFlushBuffer

* Fix WaitForFencesImpl thread safety

* Proper methods for risky reference increments

* Wrong type of CB.

* Address feedback
2023-05-08 12:45:12 +02:00
895d9b53bc Vulkan: Batch vertex buffer updates (#4843)
* Vulkan: Batch vertex buffer updates

Some games can bind a large number of vertex buffers for draws. This PR allows for vertex buffers to be updated with one call rather than one per buffer.

This mostly affects the AMD Mesa driver, the testing platform was Steam Deck with Super Mario Odyssey. It was taking about 12% before, should be greatly reduced now.

A small optimization has been added to avoid looking up the same buffer multiple times, as a common pattern is for the same buffer to be bound many times in a row with different ranges.

* Only rebind vertex buffers if they have changed

* Address feedback
2023-05-08 10:59:26 +02:00
0e06aace45 misc: Avoid copy of ApplicationControlProperty (#4849)
Avoid more giant copy when passing it around.
2023-05-08 01:50:07 +02:00
adf4ebcd60 Ava: Fix SystemTimeOffset calculation (#4848)
* Ava: Fix SystemTimeOffset calculation

During testing of #4822, Mary pointed out the way we calculate time offset is wrong in our Avalonia UI. This PR fixed that.
The axaml file is autoformatted too.

* DateTime.Now in local var
2023-05-08 00:31:08 +02:00
16 changed files with 345 additions and 175 deletions

View File

@ -238,8 +238,9 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public DateTimeOffset DateOffset { get; set; }
public TimeSpan TimeOffset { get; set; }
public DateTimeOffset CurrentDate { get; set; }
public TimeSpan CurrentTime { get; set; }
internal AvaloniaList<TimeZone> TimeZones { get; set; }
public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
@ -397,10 +398,11 @@ namespace Ryujinx.Ava.UI.ViewModels
Language = (int)config.System.Language.Value;
TimeZone = config.System.TimeZone;
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
DateTime currentDateTime = DateTime.Now;
CurrentDate = currentDateTime.Date;
CurrentTime = currentDateTime.TimeOfDay.Add(TimeSpan.FromSeconds(config.System.SystemTimeOffset));
DateOffset = dateTimeOffset.Date;
TimeOffset = dateTimeOffset.TimeOfDay;
EnableVsync = config.Graphics.EnableVsync;
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
ExpandDramSize = config.System.ExpandRam;
@ -487,9 +489,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.TimeZone.Value = TimeZone;
}
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
config.Graphics.EnableVsync.Value = EnableVsync;
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
config.System.ExpandRam.Value = ExpandDramSize;

View File

@ -3,12 +3,12 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
x:DataType="viewModels:SettingsViewModel"
mc:Ignorable="d">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
@ -27,13 +27,15 @@
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemCore}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemRegion}"
Width="250" />
<ComboBox SelectedIndex="{Binding Region}"
ToolTip.Tip="{locale:Locale RegionTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<TextBlock
Width="250"
VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemRegion}" />
<ComboBox
Width="350"
HorizontalContentAlignment="Left"
SelectedIndex="{Binding Region}"
ToolTip.Tip="{locale:Locale RegionTooltip}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" />
</ComboBoxItem>
@ -58,20 +60,21 @@
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
ToolTip.Tip="{locale:Locale LanguageTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding Language}"
ToolTip.Tip="{locale:Locale LanguageTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<TextBlock
Width="250"
VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
ToolTip.Tip="{locale:Locale LanguageTooltip}" />
<ComboBox
Width="350"
HorizontalContentAlignment="Left"
SelectedIndex="{Binding Language}"
ToolTip.Tip="{locale:Locale LanguageTooltip}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" />
@ -104,71 +107,67 @@
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
Width="250" />
<TextBlock
Width="250"
VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
<AutoCompleteBox
Name="TimeZoneBox"
Width="350"
MaxDropDownHeight="500"
FilterMode="Contains"
Items="{Binding TimeZones}"
MaxDropDownHeight="500"
SelectionChanged="TimeZoneBox_OnSelectionChanged"
Text="{Binding Path=TimeZone, Mode=OneWay}"
TextChanged="TimeZoneBox_OnTextChanged"
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTime}"
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="250"/>
<DatePicker VerticalAlignment="Center" SelectedDate="{Binding DateOffset}"
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="350" />
<TextBlock
Width="250"
VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTime}"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
<DatePicker
Width="350"
VerticalAlignment="Center"
SelectedDate="{Binding CurrentDate}"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
</StackPanel>
<StackPanel Margin="250,0,0,10" Orientation="Horizontal">
<TimePicker
Width="350"
VerticalAlignment="Center"
ClockIdentifier="24HourClock"
SelectedTime="{Binding TimeOffset}"
Width="350"
SelectedTime="{Binding CurrentTime}"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
</StackPanel>
<CheckBox IsChecked="{Binding EnableVsync}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}"
ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}" ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}"
ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}" ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
@ -180,12 +179,10 @@
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding ExpandDramSize}"
ToolTip.Tip="{locale:Locale DRamTooltip}">
<CheckBox IsChecked="{Binding ExpandDramSize}" ToolTip.Tip="{locale:Locale DRamTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" />
</CheckBox>
<CheckBox IsChecked="{Binding IgnoreMissingServices}"
ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
<CheckBox IsChecked="{Binding IgnoreMissingServices}" ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" />
</CheckBox>
</StackPanel>

View File

@ -105,6 +105,23 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public bool TryIncrementReferenceCount()
{
int lastValue;
do
{
lastValue = _referenceCount;
if (lastValue == 0)
{
return false;
}
}
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
return true;
}
public void IncrementReferenceCount()
{
if (Interlocked.Increment(ref _referenceCount) == 1)

View File

@ -599,9 +599,10 @@ namespace Ryujinx.Graphics.Vulkan
Auto<DisposableBuffer> dst,
int srcOffset,
int dstOffset,
int size)
int size,
bool registerSrcUsage = true)
{
var srcBuffer = src.Get(cbs, srcOffset, size).Value;
var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
var dstBuffer = dst.Get(cbs, dstOffset, size).Value;
InsertBufferBarrier(

View File

@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
public SemaphoreHolder Semaphore;
public List<IAuto> Dependants;
public HashSet<MultiFenceHolder> Waitables;
public List<MultiFenceHolder> Waitables;
public HashSet<SemaphoreHolder> Dependencies;
public void Initialize(Vk api, Device device, CommandPool pool)
@ -47,7 +47,7 @@ namespace Ryujinx.Graphics.Vulkan
api.AllocateCommandBuffers(device, allocateInfo, out CommandBuffer);
Dependants = new List<IAuto>();
Waitables = new HashSet<MultiFenceHolder>();
Waitables = new List<MultiFenceHolder>();
Dependencies = new HashSet<SemaphoreHolder>();
}
}
@ -143,8 +143,10 @@ namespace Ryujinx.Graphics.Vulkan
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
{
ref var entry = ref _commandBuffers[cbIndex];
waitable.AddFence(cbIndex, entry.Fence);
entry.Waitables.Add(waitable);
if (waitable.AddFence(cbIndex, entry.Fence))
{
entry.Waitables.Add(waitable);
}
}
public bool HasWaitableOnRentedCommandBuffer(MultiFenceHolder waitable, int offset, int size)
@ -156,7 +158,7 @@ namespace Ryujinx.Graphics.Vulkan
ref var entry = ref _commandBuffers[i];
if (entry.InUse &&
entry.Waitables.Contains(waitable) &&
waitable.HasFence(i) &&
waitable.IsBufferRangeInUse(i, offset, size))
{
return true;
@ -331,7 +333,7 @@ namespace Ryujinx.Graphics.Vulkan
foreach (var waitable in entry.Waitables)
{
waitable.RemoveFence(cbIndex, entry.Fence);
waitable.RemoveFence(cbIndex);
waitable.RemoveBufferUses(cbIndex);
}

View File

@ -32,6 +32,25 @@ namespace Ryujinx.Graphics.Vulkan
return _fence;
}
public bool TryGet(out Fence fence)
{
int lastValue;
do
{
lastValue = _referenceCount;
if (lastValue == 0)
{
fence = default;
return false;
}
}
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
fence = _fence;
return true;
}
public Fence Get()
{
Interlocked.Increment(ref _referenceCount);

View File

@ -1,6 +1,5 @@
using Silk.NET.Vulkan;
using System.Collections.Generic;
using System.Linq;
using System;
namespace Ryujinx.Graphics.Vulkan
{
@ -11,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
{
private static int BufferUsageTrackingGranularity = 4096;
private readonly Dictionary<FenceHolder, int> _fences;
private readonly FenceHolder[] _fences;
private BufferUsageBitmap _bufferUsageBitmap;
/// <summary>
@ -19,7 +18,7 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary>
public MultiFenceHolder()
{
_fences = new Dictionary<FenceHolder, int>();
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
}
/// <summary>
@ -28,7 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
/// <param name="size">Size of the buffer</param>
public MultiFenceHolder(int size)
{
_fences = new Dictionary<FenceHolder, int>();
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
}
@ -80,25 +79,37 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary>
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
/// <param name="fence">Fence to be added</param>
public void AddFence(int cbIndex, FenceHolder fence)
/// <returns>True if the command buffer's previous fence value was null</returns>
public bool AddFence(int cbIndex, FenceHolder fence)
{
lock (_fences)
ref FenceHolder fenceRef = ref _fences[cbIndex];
if (fenceRef == null)
{
_fences.TryAdd(fence, cbIndex);
fenceRef = fence;
return true;
}
return false;
}
/// <summary>
/// Removes a fence from the holder.
/// </summary>
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
/// <param name="fence">Fence to be removed</param>
public void RemoveFence(int cbIndex, FenceHolder fence)
public void RemoveFence(int cbIndex)
{
lock (_fences)
{
_fences.Remove(fence);
}
_fences[cbIndex] = null;
}
/// <summary>
/// Determines if a fence referenced on the given command buffer.
/// </summary>
/// <param name="cbIndex">Index of the command buffer to check if it's used</param>
/// <returns>True if referenced, false otherwise</returns>
public bool HasFence(int cbIndex)
{
return _fences[cbIndex] != null;
}
/// <summary>
@ -147,21 +158,29 @@ namespace Ryujinx.Graphics.Vulkan
/// <returns>True if all fences were signaled before the timeout expired, false otherwise</returns>
private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
{
FenceHolder[] fenceHolders;
Fence[] fences;
Span<FenceHolder> fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
lock (_fences)
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
Span<Fence> fences = stackalloc Fence[count];
int fenceCount = 0;
for (int i = 0; i < count; i++)
{
fenceHolders = size != 0 ? GetOverlappingFences(offset, size) : _fences.Keys.ToArray();
fences = new Fence[fenceHolders.Length];
for (int i = 0; i < fenceHolders.Length; i++)
if (fenceHolders[i].TryGet(out Fence fence))
{
fences[i] = fenceHolders[i].Get();
fences[fenceCount] = fence;
if (fenceCount < i)
{
fenceHolders[fenceCount] = fenceHolders[i];
}
fenceCount++;
}
}
if (fences.Length == 0)
if (fenceCount == 0)
{
return true;
}
@ -170,14 +189,14 @@ namespace Ryujinx.Graphics.Vulkan
if (hasTimeout)
{
signaled = FenceHelper.AllSignaled(api, device, fences, timeout);
signaled = FenceHelper.AllSignaled(api, device, fences.Slice(0, fenceCount), timeout);
}
else
{
FenceHelper.WaitAllIndefinitely(api, device, fences);
FenceHelper.WaitAllIndefinitely(api, device, fences.Slice(0, fenceCount));
}
for (int i = 0; i < fenceHolders.Length; i++)
for (int i = 0; i < fenceCount; i++)
{
fenceHolders[i].Put();
}
@ -186,27 +205,49 @@ namespace Ryujinx.Graphics.Vulkan
}
/// <summary>
/// Gets fences to wait for use of a given buffer region.
/// Gets fences to wait for.
/// </summary>
/// <param name="offset">Offset of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>Fences for the specified region</returns>
private FenceHolder[] GetOverlappingFences(int offset, int size)
/// <param name="storage">Span to store fences in</param>
/// <returns>Number of fences placed in storage</returns>
private int GetFences(Span<FenceHolder> storage)
{
List<FenceHolder> overlapping = new List<FenceHolder>();
int count = 0;
foreach (var kv in _fences)
for (int i = 0; i < _fences.Length; i++)
{
var fence = kv.Key;
var ownerCbIndex = kv.Value;
var fence = _fences[i];
if (_bufferUsageBitmap.OverlapsWith(ownerCbIndex, offset, size))
if (fence != null)
{
overlapping.Add(fence);
storage[count++] = fence;
}
}
return overlapping.ToArray();
return count;
}
/// <summary>
/// Gets fences to wait for use of a given buffer region.
/// </summary>
/// <param name="storage">Span to store overlapping fences in</param>
/// <param name="offset">Offset of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>Number of fences for the specified region placed in storage</returns>
private int GetOverlappingFences(Span<FenceHolder> storage, int offset, int size)
{
int count = 0;
for (int i = 0; i < _fences.Length; i++)
{
var fence = _fences[i];
if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size))
{
storage[count++] = fence;
}
}
return count;
}
}
}

View File

@ -34,16 +34,26 @@ namespace Ryujinx.Graphics.Vulkan
public Span<byte> GetBufferData(CommandBufferPool cbp, BufferHolder buffer, int offset, int size)
{
var flushStorage = ResizeIfNeeded(size);
Auto<DisposableBuffer> srcBuffer;
using (var cbs = cbp.Rent())
{
var srcBuffer = buffer.GetBuffer(cbs.CommandBuffer);
srcBuffer = buffer.GetBuffer(cbs.CommandBuffer);
var dstBuffer = flushStorage.GetBuffer(cbs.CommandBuffer);
BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, 0, size);
if (srcBuffer.TryIncrementReferenceCount())
{
BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false);
}
else
{
// Source buffer is no longer alive, don't copy anything to flush storage.
srcBuffer = null;
}
}
flushStorage.WaitForFences();
srcBuffer?.DecrementReferenceCount();
return flushStorage.GetDataStorage(0, size);
}

View File

@ -66,6 +66,8 @@ namespace Ryujinx.Graphics.Vulkan
private ulong _vertexBuffersDirty;
protected Rectangle<int> ClearScissor;
private readonly VertexBufferUpdater _vertexBufferUpdater;
public SupportBufferUpdater SupportBufferUpdater;
public IndexBufferPattern QuadsToTrisPattern;
public IndexBufferPattern TriFanToTrisPattern;
@ -96,6 +98,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
_descriptorSetUpdater = new DescriptorSetUpdater(gd, this);
_vertexBufferUpdater = new VertexBufferUpdater(gd);
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
_vertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers + 1];
@ -1185,6 +1188,9 @@ namespace Ryujinx.Graphics.Vulkan
int validCount = 1;
BufferHandle lastHandle = default;
Auto<DisposableBuffer> lastBuffer = default;
for (int i = 0; i < count; i++)
{
var vertexBuffer = vertexBuffers[i];
@ -1194,7 +1200,12 @@ namespace Ryujinx.Graphics.Vulkan
if (vertexBuffer.Buffer.Handle != BufferHandle.Null)
{
var vb = Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
Auto<DisposableBuffer> vb = (vertexBuffer.Buffer.Handle == lastHandle) ? lastBuffer :
Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
lastHandle = vertexBuffer.Buffer.Handle;
lastBuffer = vb;
if (vb != null)
{
int binding = i + 1;
@ -1222,24 +1233,29 @@ namespace Ryujinx.Graphics.Vulkan
ref var buffer = ref _vertexBuffers[binding];
int oldScalarAlign = buffer.AttributeScalarAlignment;
buffer.Dispose();
if (Gd.Capabilities.VertexBufferAlignment < 2 &&
(vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
{
buffer = new VertexBufferState(
vb,
descriptorIndex,
vertexBuffer.Buffer.Offset,
vbSize,
vertexBuffer.Stride);
if (!buffer.Matches(vb, descriptorIndex, vertexBuffer.Buffer.Offset, vbSize, vertexBuffer.Stride))
{
buffer.Dispose();
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState);
buffer = new VertexBufferState(
vb,
descriptorIndex,
vertexBuffer.Buffer.Offset,
vbSize,
vertexBuffer.Stride);
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater);
}
}
else
{
// May need to be rewritten. Bind this buffer before draw.
buffer.Dispose();
buffer = new VertexBufferState(
vertexBuffer.Buffer.Handle,
descriptorIndex,
@ -1255,6 +1271,8 @@ namespace Ryujinx.Graphics.Vulkan
}
}
_vertexBufferUpdater.Commit(Cbs);
_newState.VertexBindingDescriptionsCount = (uint)validCount;
SignalStateChange();
}
@ -1596,10 +1614,12 @@ namespace Ryujinx.Graphics.Vulkan
{
int i = BitOperations.TrailingZeroCount(_vertexBuffersDirty);
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState);
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater);
_vertexBuffersDirty &= ~(1UL << i);
}
_vertexBufferUpdater.Commit(Cbs);
}
if (_stateDirty || Pbp != pbp)
@ -1712,6 +1732,7 @@ namespace Ryujinx.Graphics.Vulkan
_framebuffer?.Dispose();
_newState.Dispose();
_descriptorSetUpdater.Dispose();
_vertexBufferUpdater.Dispose();
for (int i = 0; i < _vertexBuffers.Length; i++)
{

View File

@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Vulkan
AttributeScalarAlignment = 1;
}
public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding, ref PipelineState state)
public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding, ref PipelineState state, VertexBufferUpdater updater)
{
var autoBuffer = _buffer;
@ -65,21 +65,7 @@ namespace Ryujinx.Graphics.Vulkan
var buffer = autoBuffer.Get(cbs, 0, newSize).Value;
if (gd.Capabilities.SupportsExtendedDynamicState)
{
gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
cbs.CommandBuffer,
binding,
1,
buffer,
0,
(ulong)newSize,
(ulong)stride);
}
else
{
gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, 0);
}
updater.BindVertexBuffer(cbs, binding, buffer, 0, (ulong)newSize, (ulong)stride);
_buffer = autoBuffer;
@ -106,21 +92,7 @@ namespace Ryujinx.Graphics.Vulkan
{
var buffer = autoBuffer.Get(cbs, _offset, _size).Value;
if (gd.Capabilities.SupportsExtendedDynamicState)
{
gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
cbs.CommandBuffer,
binding,
1,
buffer,
(ulong)_offset,
(ulong)_size,
(ulong)_stride);
}
else
{
gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset);
}
updater.BindVertexBuffer(cbs, binding, buffer, (ulong)_offset, (ulong)_size, (ulong)_stride);
}
}
@ -129,6 +101,11 @@ namespace Ryujinx.Graphics.Vulkan
return _buffer == buffer;
}
public bool Matches(Auto<DisposableBuffer> buffer, int descriptorIndex, int offset, int size, int stride = 0)
{
return _buffer == buffer && DescriptorIndex == descriptorIndex && _offset == offset && _size == size && _stride == stride;
}
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
{
if (_buffer == from)

View File

@ -0,0 +1,84 @@
using Silk.NET.Vulkan;
using System;
using VkBuffer = Silk.NET.Vulkan.Buffer;
namespace Ryujinx.Graphics.Vulkan
{
internal class VertexBufferUpdater : IDisposable
{
private VulkanRenderer _gd;
private uint _baseBinding;
private uint _count;
private NativeArray<VkBuffer> _buffers;
private NativeArray<ulong> _offsets;
private NativeArray<ulong> _sizes;
private NativeArray<ulong> _strides;
public VertexBufferUpdater(VulkanRenderer gd)
{
_gd = gd;
_buffers = new NativeArray<VkBuffer>(Constants.MaxVertexBuffers);
_offsets = new NativeArray<ulong>(Constants.MaxVertexBuffers);
_sizes = new NativeArray<ulong>(Constants.MaxVertexBuffers);
_strides = new NativeArray<ulong>(Constants.MaxVertexBuffers);
}
public void BindVertexBuffer(CommandBufferScoped cbs, uint binding, VkBuffer buffer, ulong offset, ulong size, ulong stride)
{
if (_count == 0)
{
_baseBinding = binding;
}
else if (_baseBinding + _count != binding)
{
Commit(cbs);
_baseBinding = binding;
}
int index = (int)_count;
_buffers[index] = buffer;
_offsets[index] = offset;
_sizes[index] = size;
_strides[index] = stride;
_count++;
}
public unsafe void Commit(CommandBufferScoped cbs)
{
if (_count != 0)
{
if (_gd.Capabilities.SupportsExtendedDynamicState)
{
_gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
cbs.CommandBuffer,
_baseBinding,
_count,
_buffers.Pointer,
_offsets.Pointer,
_sizes.Pointer,
_strides.Pointer);
}
else
{
_gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, _baseBinding, _count, _buffers.Pointer, _offsets.Pointer);
}
_count = 0;
}
}
public void Dispose()
{
_buffers.Dispose();
_offsets.Dispose();
_sizes.Dispose();
_strides.Dispose();
}
}
}

View File

@ -351,26 +351,26 @@ namespace Ryujinx.Graphics.Vulkan
var features = new PhysicalDeviceFeatures()
{
DepthBiasClamp = true,
DepthBiasClamp = supportedFeatures.DepthBiasClamp,
DepthClamp = supportedFeatures.DepthClamp,
DualSrcBlend = supportedFeatures.DualSrcBlend,
FragmentStoresAndAtomics = true,
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
GeometryShader = supportedFeatures.GeometryShader,
ImageCubeArray = true,
IndependentBlend = true,
ImageCubeArray = supportedFeatures.ImageCubeArray,
IndependentBlend = supportedFeatures.IndependentBlend,
LogicOp = supportedFeatures.LogicOp,
OcclusionQueryPrecise = supportedFeatures.OcclusionQueryPrecise,
MultiViewport = supportedFeatures.MultiViewport,
PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery,
SamplerAnisotropy = true,
ShaderClipDistance = true,
SamplerAnisotropy = supportedFeatures.SamplerAnisotropy,
ShaderClipDistance = supportedFeatures.ShaderClipDistance,
ShaderFloat64 = supportedFeatures.ShaderFloat64,
ShaderImageGatherExtended = supportedFeatures.ShaderImageGatherExtended,
ShaderStorageImageMultisample = supportedFeatures.ShaderStorageImageMultisample,
// ShaderStorageImageReadWithoutFormat = true,
// ShaderStorageImageWriteWithoutFormat = true,
TessellationShader = supportedFeatures.TessellationShader,
VertexPipelineStoresAndAtomics = true,
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
RobustBufferAccess = useRobustBufferAccess
};

View File

@ -116,7 +116,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
device,
device.System.KernelContext,
metaLoader,
nacpData.Value,
nacpData,
enablePtc,
allowCodeMemoryForJit,
programName,

View File

@ -209,7 +209,7 @@ namespace Ryujinx.HLE.Loaders.Processes
ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,
_device.System.KernelContext,
dummyExeFs.GetNpdm(),
nacpData.Value,
nacpData,
diskCacheEnabled: false,
allowCodeMemoryForJit: true,
programName,

View File

@ -219,7 +219,7 @@ namespace Ryujinx.HLE.Loaders.Processes
Switch device,
KernelContext context,
MetaLoader metaLoader,
ApplicationControlProperty applicationControlProperties,
BlitStruct<ApplicationControlProperty> applicationControlProperties,
bool diskCacheEnabled,
bool allowCodeMemoryForJit,
string name,
@ -355,7 +355,7 @@ namespace Ryujinx.HLE.Loaders.Processes
context.Device.System.TickSource,
context.Device.Gpu,
$"{programId:x16}",
applicationControlProperties.DisplayVersionString.ToString(),
applicationControlProperties.Value.DisplayVersionString.ToString(),
diskCacheEnabled,
codeStart,
codeSize);

View File

@ -1,4 +1,5 @@
using LibHac.Loader;
using LibHac.Common;
using LibHac.Loader;
using LibHac.Ns;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
@ -11,7 +12,7 @@ namespace Ryujinx.HLE.Loaders.Processes
{
public class ProcessResult
{
public static ProcessResult Failed => new(null, new ApplicationControlProperty(), false, false, null, 0, 0, 0, TitleLanguage.AmericanEnglish);
public static ProcessResult Failed => new(null, new BlitStruct<ApplicationControlProperty>(1), false, false, null, 0, 0, 0, TitleLanguage.AmericanEnglish);
private readonly byte _mainThreadPriority;
private readonly uint _mainThreadStackSize;
@ -31,15 +32,15 @@ namespace Ryujinx.HLE.Loaders.Processes
public readonly bool AllowCodeMemoryForJit;
public ProcessResult(
MetaLoader metaLoader,
ApplicationControlProperty applicationControlProperties,
bool diskCacheEnabled,
bool allowCodeMemoryForJit,
IDiskCacheLoadState diskCacheLoadState,
ulong pid,
byte mainThreadPriority,
uint mainThreadStackSize,
TitleLanguage titleLanguage)
MetaLoader metaLoader,
BlitStruct<ApplicationControlProperty> applicationControlProperties,
bool diskCacheEnabled,
bool allowCodeMemoryForJit,
IDiskCacheLoadState diskCacheLoadState,
ulong pid,
byte mainThreadPriority,
uint mainThreadStackSize,
TitleLanguage titleLanguage)
{
_mainThreadPriority = mainThreadPriority;
_mainThreadStackSize = mainThreadStackSize;
@ -48,7 +49,7 @@ namespace Ryujinx.HLE.Loaders.Processes
ProcessId = pid;
MetaLoader = metaLoader;
ApplicationControlProperties = applicationControlProperties;
ApplicationControlProperties = applicationControlProperties.Value;
if (metaLoader is not null)
{