Compare commits

..

2 Commits

Author SHA1 Message Date
TSR Berry
b51d57b642 Fix fetch failure when iterating over PRs 2024-05-01 18:13:09 +02:00
dependabot[bot]
54c16f4052 ci: bump actions/github-script from 6 to 7
Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-01 18:11:17 +02:00
128 changed files with 1509 additions and 6550 deletions

View File

@@ -1,7 +1,6 @@
name: Feature Request name: Feature Request
description: Suggest a new feature for Ryujinx. description: Suggest a new feature for Ryujinx.
title: "[Feature Request]" title: "[Feature Request]"
labels: enhancement
body: body:
- type: textarea - type: textarea
id: overview id: overview

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps: steps:
- uses: actions/github-script@v6 - uses: actions/github-script@v7
with: with:
script: | script: |
const {owner, repo} = context.repo; const {owner, repo} = context.repo;
@@ -19,7 +19,7 @@ jobs:
const pull_head_sha = '${{github.event.workflow_run.head_sha}}'; const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
const issue_number = await (async () => { const issue_number = await (async () => {
const pulls = await github.rest.pulls.list({owner, repo}); const pulls = await github.rest.pulls.list.endpoint.merge({owner, repo});
for await (const {data} of github.paginate.iterator(pulls)) { for await (const {data} of github.paginate.iterator(pulls)) {
for (const pull of data) { for (const pull of data) {
if (pull.head.sha === pull_head_sha) { if (pull.head.sha === pull_head_sha) {
@@ -68,4 +68,4 @@ jobs:
} else { } else {
core.info(`Creating a comment`); core.info(`Creating a comment`);
await github.rest.issues.createComment({repo, owner, issue_number, body}); await github.rest.issues.createComment({repo, owner, issue_number, body});
} }

View File

@@ -34,7 +34,7 @@ jobs:
shell: bash shell: bash
- name: Create tag - name: Create tag
uses: actions/github-script@v6 uses: actions/github-script@v7
with: with:
script: | script: |
github.rest.git.createRef({ github.rest.git.createRef({

View File

@@ -8,10 +8,10 @@
<PackageVersion Include="Avalonia.Desktop" Version="11.0.10" /> <PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" /> <PackageVersion Include="Avalonia.Svg" Version="11.0.0.16" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" /> <PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.16" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="2.2.0" /> <PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="8.4.1" /> <PackageVersion Include="DynamicData" Version="8.4.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" /> <PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
@@ -49,4 +49,4 @@
<PackageVersion Include="System.Management" Version="8.0.0" /> <PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -36,8 +36,8 @@
## Compatibility ## Compatibility
As of May 2024, Ryujinx has been tested on approximately 4,300 titles; As of October 2023, Ryujinx has been tested on approximately 4,200 titles;
over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable. over 4,150 boot past menus and into gameplay, with roughly 3,500 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).

View File

@@ -4,8 +4,6 @@
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="NAMESPACE" /&gt;&lt;Kind Name="CLASS" /&gt;&lt;Kind Name="STRUCT" /&gt;&lt;Kind Name="ENUM" /&gt;&lt;Kind Name="DELEGATE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>

View File

@@ -857,14 +857,8 @@ namespace ARMeilleure.Translation.PTC
Stopwatch sw = Stopwatch.StartNew(); Stopwatch sw = Stopwatch.StartNew();
foreach (var thread in threads) threads.ForEach((thread) => thread.Start());
{ threads.ForEach((thread) => thread.Join());
thread.Start();
}
foreach (var thread in threads)
{
thread.Join();
}
threads.Clear(); threads.Clear();

View File

@@ -15,6 +15,7 @@ namespace Ryujinx.Audio.Renderer.Common
{ {
public const int Align = 0x10; public const int Align = 0x10;
public const int BiquadStateOffset = 0x0; public const int BiquadStateOffset = 0x0;
public const int BiquadStateSize = 0x10;
/// <summary> /// <summary>
/// The state of the biquad filters of this voice. /// The state of the biquad filters of this voice.

View File

@@ -16,15 +16,10 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// <param name="parameter">The biquad filter parameter</param> /// <param name="parameter">The biquad filter parameter</param>
/// <param name="state">The biquad filter state</param> /// <param name="state">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param> /// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param> /// <param name="inputBuffer">The input buffer to write the result</param>
/// <param name="sampleCount">The count of samples to process</param> /// <param name="sampleCount">The count of samples to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessBiquadFilter( public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
ref BiquadFilterParameter parameter,
ref BiquadFilterState state,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount)
{ {
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
@@ -45,96 +40,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
} }
} }
/// <summary>
/// Apply a single biquad filter and mix the result into the output buffer.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameter">The biquad filter parameter</param>
/// <param name="state">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
/// <param name="volume">Mix volume</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessBiquadFilterAndMix(
ref BiquadFilterParameter parameter,
ref BiquadFilterState state,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount,
float volume)
{
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
state.State1 = state.State0;
state.State0 = input;
state.State3 = state.State2;
state.State2 = output;
outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
}
}
/// <summary>
/// Apply a single biquad filter and mix the result into the output buffer with volume ramp.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameter">The biquad filter parameter</param>
/// <param name="state">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
/// <param name="volume">Initial mix volume</param>
/// <param name="ramp">Volume increment step</param>
/// <returns>Last filtered sample value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ProcessBiquadFilterAndMixRamp(
ref BiquadFilterParameter parameter,
ref BiquadFilterState state,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount,
float volume,
float ramp)
{
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
float mixState = 0f;
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
state.State1 = state.State0;
state.State0 = input;
state.State3 = state.State2;
state.State2 = output;
mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
outputBuffer[i] += mixState;
volume += ramp;
}
return mixState;
}
/// <summary> /// <summary>
/// Apply multiple biquad filter. /// Apply multiple biquad filter.
/// </summary> /// </summary>
@@ -142,15 +47,10 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// <param name="parameters">The biquad filter parameter</param> /// <param name="parameters">The biquad filter parameter</param>
/// <param name="states">The biquad filter state</param> /// <param name="states">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param> /// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param> /// <param name="inputBuffer">The input buffer to write the result</param>
/// <param name="sampleCount">The count of samples to process</param> /// <param name="sampleCount">The count of samples to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessBiquadFilter( public static void ProcessBiquadFilter(ReadOnlySpan<BiquadFilterParameter> parameters, Span<BiquadFilterState> states, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
ReadOnlySpan<BiquadFilterParameter> parameters,
Span<BiquadFilterState> states,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount)
{ {
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++) for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
{ {
@@ -167,7 +67,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {
float input = stageIndex != 0 ? outputBuffer[i] : inputBuffer[i]; float input = inputBuffer[i];
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
state.State1 = state.State0; state.State1 = state.State0;
@@ -179,129 +79,5 @@ namespace Ryujinx.Audio.Renderer.Dsp
} }
} }
} }
/// <summary>
/// Apply double biquad filter and mix the result into the output buffer.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameters">The biquad filter parameter</param>
/// <param name="states">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
/// <param name="volume">Mix volume</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessDoubleBiquadFilterAndMix(
ref BiquadFilterParameter parameter0,
ref BiquadFilterParameter parameter1,
ref BiquadFilterState state0,
ref BiquadFilterState state1,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount,
float volume)
{
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
state0.State1 = state0.State0;
state0.State0 = input;
state0.State3 = state0.State2;
state0.State2 = output;
input = output;
output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
state1.State1 = state1.State0;
state1.State0 = input;
state1.State3 = state1.State2;
state1.State2 = output;
outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
}
}
/// <summary>
/// Apply double biquad filter and mix the result into the output buffer with volume ramp.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameters">The biquad filter parameter</param>
/// <param name="states">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
/// <param name="volume">Initial mix volume</param>
/// <param name="ramp">Volume increment step</param>
/// <returns>Last filtered sample value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ProcessDoubleBiquadFilterAndMixRamp(
ref BiquadFilterParameter parameter0,
ref BiquadFilterParameter parameter1,
ref BiquadFilterState state0,
ref BiquadFilterState state1,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount,
float volume,
float ramp)
{
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
float mixState = 0f;
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
state0.State1 = state0.State0;
state0.State0 = input;
state0.State3 = state0.State2;
state0.State2 = output;
input = output;
output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
state1.State1 = state1.State0;
state1.State0 = input;
state1.State3 = state1.State2;
state1.State2 = output;
mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
outputBuffer[i] += mixState;
volume += ramp;
}
return mixState;
}
} }
} }

View File

@@ -1,123 +0,0 @@
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using System;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
public class BiquadFilterAndMixCommand : ICommand
{
public bool Enabled { get; set; }
public int NodeId { get; }
public CommandType CommandType => CommandType.BiquadFilterAndMix;
public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; }
private BiquadFilterParameter _parameter;
public Memory<BiquadFilterState> BiquadFilterState { get; }
public Memory<BiquadFilterState> PreviousBiquadFilterState { get; }
public Memory<VoiceUpdateState> State { get; }
public int LastSampleIndex { get; }
public float Volume0 { get; }
public float Volume1 { get; }
public bool NeedInitialization { get; }
public bool HasVolumeRamp { get; }
public bool IsFirstMixBuffer { get; }
public BiquadFilterAndMixCommand(
float volume0,
float volume1,
uint inputBufferIndex,
uint outputBufferIndex,
int lastSampleIndex,
Memory<VoiceUpdateState> state,
ref BiquadFilterParameter filter,
Memory<BiquadFilterState> biquadFilterState,
Memory<BiquadFilterState> previousBiquadFilterState,
bool needInitialization,
bool hasVolumeRamp,
bool isFirstMixBuffer,
int nodeId)
{
Enabled = true;
NodeId = nodeId;
InputBufferIndex = (ushort)inputBufferIndex;
OutputBufferIndex = (ushort)outputBufferIndex;
_parameter = filter;
BiquadFilterState = biquadFilterState;
PreviousBiquadFilterState = previousBiquadFilterState;
State = state;
LastSampleIndex = lastSampleIndex;
Volume0 = volume0;
Volume1 = volume1;
NeedInitialization = needInitialization;
HasVolumeRamp = hasVolumeRamp;
IsFirstMixBuffer = isFirstMixBuffer;
}
public void Process(CommandList context)
{
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
if (NeedInitialization)
{
// If there is no previous state, initialize to zero.
BiquadFilterState.Span[0] = new BiquadFilterState();
}
else if (IsFirstMixBuffer)
{
// This is the first buffer, set previous state to current state.
PreviousBiquadFilterState.Span[0] = BiquadFilterState.Span[0];
}
else
{
// Rewind the current state by copying back the previous state.
BiquadFilterState.Span[0] = PreviousBiquadFilterState.Span[0];
}
if (HasVolumeRamp)
{
float volume = Volume0;
float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessBiquadFilterAndMixRamp(
ref _parameter,
ref BiquadFilterState.Span[0],
outputBuffer,
inputBuffer,
context.SampleCount,
volume,
ramp);
}
else
{
BiquadFilterHelper.ProcessBiquadFilterAndMix(
ref _parameter,
ref BiquadFilterState.Span[0],
outputBuffer,
inputBuffer,
context.SampleCount,
Volume1);
}
}
}
}

View File

@@ -30,10 +30,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
CopyMixBuffer, CopyMixBuffer,
LimiterVersion1, LimiterVersion1,
LimiterVersion2, LimiterVersion2,
MultiTapBiquadFilter, GroupedBiquadFilter,
CaptureBuffer, CaptureBuffer,
Compressor, Compressor,
BiquadFilterAndMix,
MultiTapBiquadFilterAndMix,
} }
} }

View File

@@ -4,13 +4,13 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.Command namespace Ryujinx.Audio.Renderer.Dsp.Command
{ {
public class MultiTapBiquadFilterCommand : ICommand public class GroupedBiquadFilterCommand : ICommand
{ {
public bool Enabled { get; set; } public bool Enabled { get; set; }
public int NodeId { get; } public int NodeId { get; }
public CommandType CommandType => CommandType.MultiTapBiquadFilter; public CommandType CommandType => CommandType.GroupedBiquadFilter;
public uint EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
@@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private readonly int _outputBufferIndex; private readonly int _outputBufferIndex;
private readonly bool[] _isInitialized; private readonly bool[] _isInitialized;
public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId) public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
{ {
_parameters = filters.ToArray(); _parameters = filters.ToArray();
_biquadFilterStates = biquadFilterStateMemory; _biquadFilterStates = biquadFilterStateMemory;

View File

@@ -24,14 +24,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public Memory<VoiceUpdateState> State { get; } public Memory<VoiceUpdateState> State { get; }
public MixRampGroupedCommand( public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> volume0, Span<float> volume1, Memory<VoiceUpdateState> state, int nodeId)
uint mixBufferCount,
uint inputBufferIndex,
uint outputBufferIndex,
ReadOnlySpan<float> volume0,
ReadOnlySpan<float> volume1,
Memory<VoiceUpdateState> state,
int nodeId)
{ {
Enabled = true; Enabled = true;
MixBufferCount = mixBufferCount; MixBufferCount = mixBufferCount;
@@ -55,12 +48,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float ProcessMixRampGrouped( private static float ProcessMixRampGrouped(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float volume0, float volume1, int sampleCount)
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
float volume0,
float volume1,
int sampleCount)
{ {
float ramp = (volume1 - volume0) / sampleCount; float ramp = (volume1 - volume0) / sampleCount;
float volume = volume0; float volume = volume0;

View File

@@ -1,145 +0,0 @@
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using System;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
public class MultiTapBiquadFilterAndMixCommand : ICommand
{
public bool Enabled { get; set; }
public int NodeId { get; }
public CommandType CommandType => CommandType.MultiTapBiquadFilterAndMix;
public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; }
private BiquadFilterParameter _parameter0;
private BiquadFilterParameter _parameter1;
public Memory<BiquadFilterState> BiquadFilterState0 { get; }
public Memory<BiquadFilterState> BiquadFilterState1 { get; }
public Memory<BiquadFilterState> PreviousBiquadFilterState0 { get; }
public Memory<BiquadFilterState> PreviousBiquadFilterState1 { get; }
public Memory<VoiceUpdateState> State { get; }
public int LastSampleIndex { get; }
public float Volume0 { get; }
public float Volume1 { get; }
public bool NeedInitialization0 { get; }
public bool NeedInitialization1 { get; }
public bool HasVolumeRamp { get; }
public bool IsFirstMixBuffer { get; }
public MultiTapBiquadFilterAndMixCommand(
float volume0,
float volume1,
uint inputBufferIndex,
uint outputBufferIndex,
int lastSampleIndex,
Memory<VoiceUpdateState> state,
ref BiquadFilterParameter filter0,
ref BiquadFilterParameter filter1,
Memory<BiquadFilterState> biquadFilterState0,
Memory<BiquadFilterState> biquadFilterState1,
Memory<BiquadFilterState> previousBiquadFilterState0,
Memory<BiquadFilterState> previousBiquadFilterState1,
bool needInitialization0,
bool needInitialization1,
bool hasVolumeRamp,
bool isFirstMixBuffer,
int nodeId)
{
Enabled = true;
NodeId = nodeId;
InputBufferIndex = (ushort)inputBufferIndex;
OutputBufferIndex = (ushort)outputBufferIndex;
_parameter0 = filter0;
_parameter1 = filter1;
BiquadFilterState0 = biquadFilterState0;
BiquadFilterState1 = biquadFilterState1;
PreviousBiquadFilterState0 = previousBiquadFilterState0;
PreviousBiquadFilterState1 = previousBiquadFilterState1;
State = state;
LastSampleIndex = lastSampleIndex;
Volume0 = volume0;
Volume1 = volume1;
NeedInitialization0 = needInitialization0;
NeedInitialization1 = needInitialization1;
HasVolumeRamp = hasVolumeRamp;
IsFirstMixBuffer = isFirstMixBuffer;
}
private void UpdateState(Memory<BiquadFilterState> state, Memory<BiquadFilterState> previousState, bool needInitialization)
{
if (needInitialization)
{
// If there is no previous state, initialize to zero.
state.Span[0] = new BiquadFilterState();
}
else if (IsFirstMixBuffer)
{
// This is the first buffer, set previous state to current state.
previousState.Span[0] = state.Span[0];
}
else
{
// Rewind the current state by copying back the previous state.
state.Span[0] = previousState.Span[0];
}
}
public void Process(CommandList context)
{
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
UpdateState(BiquadFilterState0, PreviousBiquadFilterState0, NeedInitialization0);
UpdateState(BiquadFilterState1, PreviousBiquadFilterState1, NeedInitialization1);
if (HasVolumeRamp)
{
float volume = Volume0;
float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessDoubleBiquadFilterAndMixRamp(
ref _parameter0,
ref _parameter1,
ref BiquadFilterState0.Span[0],
ref BiquadFilterState1.Span[0],
outputBuffer,
inputBuffer,
context.SampleCount,
volume,
ramp);
}
else
{
BiquadFilterHelper.ProcessDoubleBiquadFilterAndMix(
ref _parameter0,
ref _parameter1,
ref BiquadFilterState0.Span[0],
ref BiquadFilterState1.Span[0],
outputBuffer,
inputBuffer,
context.SampleCount,
Volume1);
}
}
}
}

View File

@@ -2,16 +2,12 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.State namespace Ryujinx.Audio.Renderer.Dsp.State
{ {
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)] [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
public struct BiquadFilterState public struct BiquadFilterState
{ {
public float State0; public float State0;
public float State1; public float State1;
public float State2; public float State2;
public float State3; public float State3;
public float State4;
public float State5;
public float State6;
public float State7;
} }
} }

View File

@@ -1,43 +0,0 @@
using Ryujinx.Common.Memory;
using System;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Generic interface for the splitter destination parameters.
/// </summary>
public interface ISplitterDestinationInParameter
{
/// <summary>
/// Target splitter destination data id.
/// </summary>
int Id { get; }
/// <summary>
/// The mix to output the result of the splitter.
/// </summary>
int DestinationId { get; }
/// <summary>
/// Biquad filter parameters.
/// </summary>
Array2<BiquadFilterParameter> BiquadFilters { get; }
/// <summary>
/// Set to true if in use.
/// </summary>
bool IsUsed { get; }
/// <summary>
/// Mix buffer volumes.
/// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
Span<float> MixBufferVolume { get; }
/// <summary>
/// Check if the magic is valid.
/// </summary>
/// <returns>Returns true if the magic is valid.</returns>
bool IsMagicValid();
}
}

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -6,10 +5,10 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter namespace Ryujinx.Audio.Renderer.Parameter
{ {
/// <summary> /// <summary>
/// Input header for a splitter destination version 1 update. /// Input header for a splitter destination update.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter public struct SplitterDestinationInParameter
{ {
/// <summary> /// <summary>
/// Magic of the input header. /// Magic of the input header.
@@ -42,7 +41,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// </summary> /// </summary>
private unsafe fixed byte _reserved[3]; private unsafe fixed byte _reserved[3];
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { } private struct MixArray { }
/// <summary> /// <summary>
@@ -51,14 +50,6 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// <remarks>Used when a splitter id is specified in the mix.</remarks> /// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume); public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
readonly int ISplitterDestinationInParameter.Id => Id;
readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
/// <summary> /// <summary>
/// The expected constant of any input header. /// The expected constant of any input header.
/// </summary> /// </summary>

View File

@@ -1,81 +0,0 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input header for a splitter destination version 2 update.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter
{
/// <summary>
/// Magic of the input header.
/// </summary>
public uint Magic;
/// <summary>
/// Target splitter destination data id.
/// </summary>
public int Id;
/// <summary>
/// Mix buffer volumes storage.
/// </summary>
private MixArray _mixBufferVolume;
/// <summary>
/// The mix to output the result of the splitter.
/// </summary>
public int DestinationId;
/// <summary>
/// Biquad filter parameters.
/// </summary>
public Array2<BiquadFilterParameter> BiquadFilters;
/// <summary>
/// Set to true if in use.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
/// <summary>
/// Reserved/padding.
/// </summary>
private unsafe fixed byte _reserved[11];
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
/// <summary>
/// Mix buffer volumes.
/// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
readonly int ISplitterDestinationInParameter.Id => Id;
readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
/// <summary>
/// The expected constant of any input header.
/// </summary>
private const uint ValidMagic = 0x44444E53;
/// <summary>
/// Check if the magic is valid.
/// </summary>
/// <returns>Returns true if the magic is valid.</returns>
public readonly bool IsMagicValid()
{
return Magic == ValidMagic;
}
}
}

View File

@@ -1,7 +1,6 @@
using Ryujinx.Audio.Integration; using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.Command; using Ryujinx.Audio.Renderer.Dsp.Command;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server.Effect; using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Audio.Renderer.Server.MemoryPool; using Ryujinx.Audio.Renderer.Server.MemoryPool;
@@ -174,22 +173,6 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.WorkBufferTooSmall; return ResultCode.WorkBufferTooSmall;
} }
Memory<BiquadFilterState> splitterBqfStates = Memory<BiquadFilterState>.Empty;
if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
parameter.SplitterCount > 0 &&
parameter.SplitterDestinationCount > 0)
{
splitterBqfStates = workBufferAllocator.Allocate<BiquadFilterState>(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
if (splitterBqfStates.IsEmpty)
{
return ResultCode.WorkBufferTooSmall;
}
splitterBqfStates.Span.Clear();
}
// Invalidate DSP cache on what was currently allocated with workBuffer. // Invalidate DSP cache on what was currently allocated with workBuffer.
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset); AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
@@ -309,7 +292,7 @@ namespace Ryujinx.Audio.Renderer.Server
state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
} }
if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator, splitterBqfStates)) if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator))
{ {
return ResultCode.WorkBufferTooSmall; return ResultCode.WorkBufferTooSmall;
} }
@@ -792,13 +775,6 @@ namespace Ryujinx.Audio.Renderer.Server
// Splitter // Splitter
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter); size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
parameter.SplitterCount > 0 &&
parameter.SplitterDestinationCount > 0)
{
size = WorkBufferAllocator.GetTargetSize<BiquadFilterState>(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
}
// DSP Voice // DSP Voice
size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align); size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);

View File

@@ -45,6 +45,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP. /// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
/// A new version of the command estimator was added to address timing changes caused by the voice changes. /// A new version of the command estimator was added to address timing changes caused by the voice changes.
/// Additionally, the rendering limit percent was incremented to 80%. /// Additionally, the rendering limit percent was incremented to 80%.
///
/// </summary> /// </summary>
/// <remarks>This was added in system update 6.0.0</remarks> /// <remarks>This was added in system update 6.0.0</remarks>
public const int Revision5 = 5 << 24; public const int Revision5 = 5 << 24;
@@ -100,18 +101,10 @@ namespace Ryujinx.Audio.Renderer.Server
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks> /// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
public const int Revision11 = 11 << 24; public const int Revision11 = 11 << 24;
/// <summary>
/// REV12:
/// Two new commands were added to for biquad filtering and mixing (with optinal volume ramp) on the same command.
/// Splitter destinations can now specify up to two biquad filtering parameters, used for filtering the buffer before mixing.
/// </summary>
/// <remarks>This was added in system update 17.0.0</remarks>
public const int Revision12 = 12 << 24;
/// <summary> /// <summary>
/// Last revision supported by the implementation. /// Last revision supported by the implementation.
/// </summary> /// </summary>
public const int LastRevision = Revision12; public const int LastRevision = Revision11;
/// <summary> /// <summary>
/// Target revision magic supported by the implementation. /// Target revision magic supported by the implementation.
@@ -361,7 +354,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice. /// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice.
/// </summary> /// </summary>
/// <returns>True if the audio renderer should use the optimization.</returns> /// <returns>True if the audio renderer should use the optimization.</returns>
public bool UseMultiTapBiquadFilterProcessing() public bool IsBiquadFilterGroupedOptimizationSupported()
{ {
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10); return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
} }
@@ -375,15 +368,6 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11); return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
} }
/// <summary>
/// Check if the audio renderer should support biquad filter on splitter.
/// </summary>
/// <returns>True if the audio renderer support biquad filter on splitter</returns>
public bool IsBiquadFilterParameterForSplitterEnabled()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
}
/// <summary> /// <summary>
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>. /// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
/// </summary> /// </summary>

View File

@@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server
} }
/// <summary> /// <summary>
/// Create a new <see cref="MultiTapBiquadFilterCommand"/>. /// Create a new <see cref="GroupedBiquadFilterCommand"/>.
/// </summary> /// </summary>
/// <param name="baseIndex">The base index of the input and output buffer.</param> /// <param name="baseIndex">The base index of the input and output buffer.</param>
/// <param name="filters">The biquad filter parameters.</param> /// <param name="filters">The biquad filter parameters.</param>
@@ -213,9 +213,9 @@ namespace Ryujinx.Audio.Renderer.Server
/// <param name="outputBufferOffset">The output buffer offset.</param> /// <param name="outputBufferOffset">The output buffer offset.</param>
/// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param> /// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
/// <param name="nodeId">The node id associated to this command.</param> /// <param name="nodeId">The node id associated to this command.</param>
public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId) public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
{ {
MultiTapBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId); GroupedBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
@@ -232,7 +232,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <param name="volume">The new volume.</param> /// <param name="volume">The new volume.</param>
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param> /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
/// <param name="nodeId">The node id associated to this command.</param> /// <param name="nodeId">The node id associated to this command.</param>
public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan<float> previousVolume, ReadOnlySpan<float> volume, Memory<VoiceUpdateState> state, int nodeId) public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> previousVolume, Span<float> volume, Memory<VoiceUpdateState> state, int nodeId)
{ {
MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId); MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
@@ -260,120 +260,6 @@ namespace Ryujinx.Audio.Renderer.Server
AddCommand(command); AddCommand(command);
} }
/// <summary>
/// Generate a new <see cref="BiquadFilterAndMixCommand"/>.
/// </summary>
/// <param name="previousVolume">The previous volume.</param>
/// <param name="volume">The new volume.</param>
/// <param name="inputBufferIndex">The input buffer index.</param>
/// <param name="outputBufferIndex">The output buffer index.</param>
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
/// <param name="filter">The biquad filter parameter.</param>
/// <param name="biquadFilterState">The biquad state.</param>
/// <param name="previousBiquadFilterState">The previous biquad state.</param>
/// <param name="needInitialization">Set to true if the biquad filter state needs to be initialized.</param>
/// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
/// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateBiquadFilterAndMix(
float previousVolume,
float volume,
uint inputBufferIndex,
uint outputBufferIndex,
int lastSampleIndex,
Memory<VoiceUpdateState> state,
ref BiquadFilterParameter filter,
Memory<BiquadFilterState> biquadFilterState,
Memory<BiquadFilterState> previousBiquadFilterState,
bool needInitialization,
bool hasVolumeRamp,
bool isFirstMixBuffer,
int nodeId)
{
BiquadFilterAndMixCommand command = new(
previousVolume,
volume,
inputBufferIndex,
outputBufferIndex,
lastSampleIndex,
state,
ref filter,
biquadFilterState,
previousBiquadFilterState,
needInitialization,
hasVolumeRamp,
isFirstMixBuffer,
nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
/// <summary>
/// Generate a new <see cref="MultiTapBiquadFilterAndMixCommand"/>.
/// </summary>
/// <param name="previousVolume">The previous volume.</param>
/// <param name="volume">The new volume.</param>
/// <param name="inputBufferIndex">The input buffer index.</param>
/// <param name="outputBufferIndex">The output buffer index.</param>
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
/// <param name="filter0">First biquad filter parameter.</param>
/// <param name="filter1">Second biquad filter parameter.</param>
/// <param name="biquadFilterState0">First biquad state.</param>
/// <param name="biquadFilterState1">Second biquad state.</param>
/// <param name="previousBiquadFilterState0">First previous biquad state.</param>
/// <param name="previousBiquadFilterState1">Second previous biquad state.</param>
/// <param name="needInitialization0">Set to true if the first biquad filter state needs to be initialized.</param>
/// <param name="needInitialization1">Set to true if the second biquad filter state needs to be initialized.</param>
/// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
/// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateMultiTapBiquadFilterAndMix(
float previousVolume,
float volume,
uint inputBufferIndex,
uint outputBufferIndex,
int lastSampleIndex,
Memory<VoiceUpdateState> state,
ref BiquadFilterParameter filter0,
ref BiquadFilterParameter filter1,
Memory<BiquadFilterState> biquadFilterState0,
Memory<BiquadFilterState> biquadFilterState1,
Memory<BiquadFilterState> previousBiquadFilterState0,
Memory<BiquadFilterState> previousBiquadFilterState1,
bool needInitialization0,
bool needInitialization1,
bool hasVolumeRamp,
bool isFirstMixBuffer,
int nodeId)
{
MultiTapBiquadFilterAndMixCommand command = new(
previousVolume,
volume,
inputBufferIndex,
outputBufferIndex,
lastSampleIndex,
state,
ref filter0,
ref filter1,
biquadFilterState0,
biquadFilterState1,
previousBiquadFilterState0,
previousBiquadFilterState1,
needInitialization0,
needInitialization1,
hasVolumeRamp,
isFirstMixBuffer,
nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
/// <summary> /// <summary>
/// Generate a new <see cref="DepopForMixBuffersCommand"/>. /// Generate a new <see cref="DepopForMixBuffersCommand"/>.
/// </summary> /// </summary>
@@ -382,7 +268,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <param name="bufferCount">The buffer count.</param> /// <param name="bufferCount">The buffer count.</param>
/// <param name="nodeId">The node id associated to this command.</param> /// <param name="nodeId">The node id associated to this command.</param>
/// <param name="sampleRate">The target sample rate in use.</param> /// <param name="sampleRate">The target sample rate in use.</param>
public void GenerateDepopForMixBuffers(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate) public void GenerateDepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
{ {
DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate); DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);

View File

@@ -12,7 +12,6 @@ using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Audio.Renderer.Utils;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server namespace Ryujinx.Audio.Renderer.Server
{ {
@@ -47,13 +46,12 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
ref MixState mix = ref _mixContext.GetState(voiceState.MixId); ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
_commandBuffer.GenerateDepopPrepare( _commandBuffer.GenerateDepopPrepare(dspState,
dspState, _rendererContext.DepopBuffer,
_rendererContext.DepopBuffer, mix.BufferCount,
mix.BufferCount, mix.BufferOffset,
mix.BufferOffset, voiceState.NodeId,
voiceState.NodeId, voiceState.WasPlaying);
voiceState.WasPlaying);
} }
else if (voiceState.SplitterId != Constants.UnusedSplitterId) else if (voiceState.SplitterId != Constants.UnusedSplitterId)
{ {
@@ -61,13 +59,15 @@ namespace Ryujinx.Audio.Renderer.Server
while (true) while (true)
{ {
SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++); Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
if (destination.IsNull) if (destinationSpan.IsEmpty)
{ {
break; break;
} }
ref SplitterDestination destination = ref destinationSpan[0];
if (destination.IsConfigured()) if (destination.IsConfigured())
{ {
int mixId = destination.DestinationId; int mixId = destination.DestinationId;
@@ -76,13 +76,12 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
ref MixState mix = ref _mixContext.GetState(mixId); ref MixState mix = ref _mixContext.GetState(mixId);
_commandBuffer.GenerateDepopPrepare( _commandBuffer.GenerateDepopPrepare(dspState,
dspState, _rendererContext.DepopBuffer,
_rendererContext.DepopBuffer, mix.BufferCount,
mix.BufferCount, mix.BufferOffset,
mix.BufferOffset, voiceState.NodeId,
voiceState.NodeId, voiceState.WasPlaying);
voiceState.WasPlaying);
destination.MarkAsNeedToUpdateInternalState(); destination.MarkAsNeedToUpdateInternalState();
} }
@@ -96,39 +95,35 @@ namespace Ryujinx.Audio.Renderer.Server
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported()) if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
{ {
_commandBuffer.GenerateDataSourceVersion2( _commandBuffer.GenerateDataSourceVersion2(ref voiceState,
ref voiceState, dspState,
dspState, (ushort)_rendererContext.MixBufferCount,
(ushort)_rendererContext.MixBufferCount, (ushort)channelIndex,
(ushort)channelIndex, voiceState.NodeId);
voiceState.NodeId);
} }
else else
{ {
switch (voiceState.SampleFormat) switch (voiceState.SampleFormat)
{ {
case SampleFormat.PcmInt16: case SampleFormat.PcmInt16:
_commandBuffer.GeneratePcmInt16DataSourceVersion1( _commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState,
ref voiceState, dspState,
dspState, (ushort)_rendererContext.MixBufferCount,
(ushort)_rendererContext.MixBufferCount, (ushort)channelIndex,
(ushort)channelIndex, voiceState.NodeId);
voiceState.NodeId);
break; break;
case SampleFormat.PcmFloat: case SampleFormat.PcmFloat:
_commandBuffer.GeneratePcmFloatDataSourceVersion1( _commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState,
ref voiceState, dspState,
dspState, (ushort)_rendererContext.MixBufferCount,
(ushort)_rendererContext.MixBufferCount, (ushort)channelIndex,
(ushort)channelIndex, voiceState.NodeId);
voiceState.NodeId);
break; break;
case SampleFormat.Adpcm: case SampleFormat.Adpcm:
_commandBuffer.GenerateAdpcmDataSourceVersion1( _commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState,
ref voiceState, dspState,
dspState, (ushort)_rendererContext.MixBufferCount,
(ushort)_rendererContext.MixBufferCount, voiceState.NodeId);
voiceState.NodeId);
break; break;
default: default:
throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}"); throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
@@ -139,14 +134,14 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId) private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
{ {
bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing(); bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported();
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable) if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
{ {
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)]; Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory); Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
_commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId); _commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
} }
else else
{ {
@@ -156,134 +151,33 @@ namespace Ryujinx.Audio.Renderer.Server
if (filter.Enable) if (filter.Enable)
{ {
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)]; Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory); Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
_commandBuffer.GenerateBiquadFilter( _commandBuffer.GenerateBiquadFilter(baseIndex,
baseIndex, ref filter,
ref filter, stateMemory.Slice(i, 1),
stateMemory.Slice(i, 1), bufferOffset,
bufferOffset, bufferOffset,
bufferOffset, !voiceState.BiquadFilterNeedInitialization[i],
!voiceState.BiquadFilterNeedInitialization[i], nodeId);
nodeId);
} }
} }
} }
} }
private void GenerateVoiceMixWithSplitter( private void GenerateVoiceMix(Span<float> mixVolumes, Span<float> previousMixVolumes, Memory<VoiceUpdateState> state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
SplitterDestination destination,
Memory<VoiceUpdateState> state,
uint bufferOffset,
uint bufferCount,
uint bufferIndex,
int nodeId)
{
ReadOnlySpan<float> mixVolumes = destination.MixBufferVolume;
ReadOnlySpan<float> previousMixVolumes = destination.PreviousMixBufferVolume;
ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
Memory<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
bool isFirstMixBuffer = true;
for (int i = 0; i < bufferCount; i++)
{
float previousMixVolume = previousMixVolumes[i];
float mixVolume = mixVolumes[i];
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
{
if (bqf0.Enable && bqf1.Enable)
{
_commandBuffer.GenerateMultiTapBiquadFilterAndMix(
previousMixVolume,
mixVolume,
bufferIndex,
bufferOffset + (uint)i,
i,
state,
ref bqf0,
ref bqf1,
bqfState[..1],
bqfState.Slice(1, 1),
bqfState.Slice(2, 1),
bqfState.Slice(3, 1),
!destination.IsBiquadFilterEnabledPrev(),
!destination.IsBiquadFilterEnabledPrev(),
true,
isFirstMixBuffer,
nodeId);
destination.UpdateBiquadFilterEnabledPrev(0);
destination.UpdateBiquadFilterEnabledPrev(1);
}
else if (bqf0.Enable)
{
_commandBuffer.GenerateBiquadFilterAndMix(
previousMixVolume,
mixVolume,
bufferIndex,
bufferOffset + (uint)i,
i,
state,
ref bqf0,
bqfState[..1],
bqfState.Slice(1, 1),
!destination.IsBiquadFilterEnabledPrev(),
true,
isFirstMixBuffer,
nodeId);
destination.UpdateBiquadFilterEnabledPrev(0);
}
else if (bqf1.Enable)
{
_commandBuffer.GenerateBiquadFilterAndMix(
previousMixVolume,
mixVolume,
bufferIndex,
bufferOffset + (uint)i,
i,
state,
ref bqf1,
bqfState[..1],
bqfState.Slice(1, 1),
!destination.IsBiquadFilterEnabledPrev(),
true,
isFirstMixBuffer,
nodeId);
destination.UpdateBiquadFilterEnabledPrev(1);
}
isFirstMixBuffer = false;
}
}
}
private void GenerateVoiceMix(
ReadOnlySpan<float> mixVolumes,
ReadOnlySpan<float> previousMixVolumes,
Memory<VoiceUpdateState> state,
uint bufferOffset,
uint bufferCount,
uint bufferIndex,
int nodeId)
{ {
if (bufferCount > Constants.VoiceChannelCountMax) if (bufferCount > Constants.VoiceChannelCountMax)
{ {
_commandBuffer.GenerateMixRampGrouped( _commandBuffer.GenerateMixRampGrouped(bufferCount,
bufferCount, bufferIndex,
bufferIndex, bufferOffset,
bufferOffset, previousMixVolumes,
previousMixVolumes, mixVolumes,
mixVolumes, state,
state, nodeId);
nodeId);
} }
else else
{ {
@@ -294,14 +188,13 @@ namespace Ryujinx.Audio.Renderer.Server
if (mixVolume != 0.0f || previousMixVolume != 0.0f) if (mixVolume != 0.0f || previousMixVolume != 0.0f)
{ {
_commandBuffer.GenerateMixRamp( _commandBuffer.GenerateMixRamp(previousMixVolume,
previousMixVolume, mixVolume,
mixVolume, bufferIndex,
bufferIndex, bufferOffset + (uint)i,
bufferOffset + (uint)i, i,
i, state,
state, nodeId);
nodeId);
} }
} }
} }
@@ -378,11 +271,10 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
} }
_commandBuffer.GenerateVolumeRamp( _commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume,
voiceState.PreviousVolume, voiceState.Volume,
voiceState.Volume, _rendererContext.MixBufferCount + (uint)channelIndex,
_rendererContext.MixBufferCount + (uint)channelIndex, nodeId);
nodeId);
if (performanceInitialized) if (performanceInitialized)
{ {
@@ -399,13 +291,15 @@ namespace Ryujinx.Audio.Renderer.Server
while (true) while (true)
{ {
SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId); Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
if (destination.IsNull) if (destinationSpan.IsEmpty)
{ {
break; break;
} }
ref SplitterDestination destination = ref destinationSpan[0];
destinationId += (int)channelsCount; destinationId += (int)channelsCount;
if (destination.IsConfigured()) if (destination.IsConfigured())
@@ -416,27 +310,13 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
ref MixState mix = ref _mixContext.GetState(mixId); ref MixState mix = ref _mixContext.GetState(mixId);
if (destination.IsBiquadFilterEnabled()) GenerateVoiceMix(destination.MixBufferVolume,
{ destination.PreviousMixBufferVolume,
GenerateVoiceMixWithSplitter( dspStateMemory,
destination, mix.BufferOffset,
dspStateMemory, mix.BufferCount,
mix.BufferOffset, _rendererContext.MixBufferCount + (uint)channelIndex,
mix.BufferCount, nodeId);
_rendererContext.MixBufferCount + (uint)channelIndex,
nodeId);
}
else
{
GenerateVoiceMix(
destination.MixBufferVolume,
destination.PreviousMixBufferVolume,
dspStateMemory,
mix.BufferOffset,
mix.BufferCount,
_rendererContext.MixBufferCount + (uint)channelIndex,
nodeId);
}
destination.MarkAsNeedToUpdateInternalState(); destination.MarkAsNeedToUpdateInternalState();
} }
@@ -457,14 +337,13 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
} }
GenerateVoiceMix( GenerateVoiceMix(channelResource.Mix.AsSpan(),
channelResource.Mix.AsSpan(), channelResource.PreviousMix.AsSpan(),
channelResource.PreviousMix.AsSpan(), dspStateMemory,
dspStateMemory, mix.BufferOffset,
mix.BufferOffset, mix.BufferCount,
mix.BufferCount, _rendererContext.MixBufferCount + (uint)channelIndex,
_rendererContext.MixBufferCount + (uint)channelIndex, nodeId);
nodeId);
if (performanceInitialized) if (performanceInitialized)
{ {
@@ -530,11 +409,10 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
if (effect.Parameter.Volumes[i] != 0.0f) if (effect.Parameter.Volumes[i] != 0.0f)
{ {
_commandBuffer.GenerateMix( _commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i],
(uint)bufferOffset + effect.Parameter.Input[i], (uint)bufferOffset + effect.Parameter.Output[i],
(uint)bufferOffset + effect.Parameter.Output[i], nodeId,
nodeId, effect.Parameter.Volumes[i]);
effect.Parameter.Volumes[i]);
} }
} }
} }
@@ -569,18 +447,17 @@ namespace Ryujinx.Audio.Renderer.Server
updateCount = newUpdateCount; updateCount = newUpdateCount;
} }
_commandBuffer.GenerateAuxEffect( _commandBuffer.GenerateAuxEffect(bufferOffset,
bufferOffset, effect.Parameter.Input[i],
effect.Parameter.Input[i], effect.Parameter.Output[i],
effect.Parameter.Output[i], ref effect.State,
ref effect.State, effect.IsEnabled,
effect.IsEnabled, effect.Parameter.BufferStorageSize,
effect.Parameter.BufferStorageSize, effect.State.SendBufferInfoBase,
effect.State.SendBufferInfoBase, effect.State.ReturnBufferInfoBase,
effect.State.ReturnBufferInfoBase, updateCount,
updateCount, writeOffset,
writeOffset, nodeId);
nodeId);
writeOffset = newUpdateCount; writeOffset = newUpdateCount;
@@ -623,7 +500,7 @@ namespace Ryujinx.Audio.Renderer.Server
if (effect.IsEnabled) if (effect.IsEnabled)
{ {
bool needInitialization = effect.Parameter.Status == UsageState.Invalid || bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
(effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed()); (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
BiquadFilterParameter parameter = new() BiquadFilterParameter parameter = new()
{ {
@@ -635,14 +512,11 @@ namespace Ryujinx.Audio.Renderer.Server
for (int i = 0; i < effect.Parameter.ChannelCount; i++) for (int i = 0; i < effect.Parameter.ChannelCount; i++)
{ {
_commandBuffer.GenerateBiquadFilter( _commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1),
(int)bufferOffset, effect.Parameter.Input[i],
ref parameter, effect.Parameter.Output[i],
effect.State.Slice(i, 1), needInitialization,
effect.Parameter.Input[i], nodeId);
effect.Parameter.Output[i],
needInitialization,
nodeId);
} }
} }
else else
@@ -717,16 +591,15 @@ namespace Ryujinx.Audio.Renderer.Server
updateCount = newUpdateCount; updateCount = newUpdateCount;
} }
_commandBuffer.GenerateCaptureEffect( _commandBuffer.GenerateCaptureEffect(bufferOffset,
bufferOffset, effect.Parameter.Input[i],
effect.Parameter.Input[i], effect.State.SendBufferInfo,
effect.State.SendBufferInfo, effect.IsEnabled,
effect.IsEnabled, effect.Parameter.BufferStorageSize,
effect.Parameter.BufferStorageSize, effect.State.SendBufferInfoBase,
effect.State.SendBufferInfoBase, updateCount,
updateCount, writeOffset,
writeOffset, nodeId);
nodeId);
writeOffset = newUpdateCount; writeOffset = newUpdateCount;
@@ -739,12 +612,11 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
Debug.Assert(effect.Type == EffectType.Compressor); Debug.Assert(effect.Type == EffectType.Compressor);
_commandBuffer.GenerateCompressorEffect( _commandBuffer.GenerateCompressorEffect(bufferOffset,
bufferOffset, effect.Parameter,
effect.Parameter, effect.State,
effect.State, effect.IsEnabled,
effect.IsEnabled, nodeId);
nodeId);
} }
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect) private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
@@ -757,11 +629,8 @@ namespace Ryujinx.Audio.Renderer.Server
bool performanceInitialized = false; bool performanceInitialized = false;
if (_performanceManager != null && _performanceManager.GetNextEntry( if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(),
out performanceEntry, isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId))
effect.GetPerformanceDetailType(),
isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix,
nodeId))
{ {
performanceInitialized = true; performanceInitialized = true;
@@ -837,85 +706,6 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
private void GenerateMixWithSplitter(
uint inputBufferIndex,
uint outputBufferIndex,
float volume,
SplitterDestination destination,
ref bool isFirstMixBuffer,
int nodeId)
{
ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
Memory<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
if (bqf0.Enable && bqf1.Enable)
{
_commandBuffer.GenerateMultiTapBiquadFilterAndMix(
0f,
volume,
inputBufferIndex,
outputBufferIndex,
0,
Memory<VoiceUpdateState>.Empty,
ref bqf0,
ref bqf1,
bqfState[..1],
bqfState.Slice(1, 1),
bqfState.Slice(2, 1),
bqfState.Slice(3, 1),
!destination.IsBiquadFilterEnabledPrev(),
!destination.IsBiquadFilterEnabledPrev(),
false,
isFirstMixBuffer,
nodeId);
destination.UpdateBiquadFilterEnabledPrev(0);
destination.UpdateBiquadFilterEnabledPrev(1);
}
else if (bqf0.Enable)
{
_commandBuffer.GenerateBiquadFilterAndMix(
0f,
volume,
inputBufferIndex,
outputBufferIndex,
0,
Memory<VoiceUpdateState>.Empty,
ref bqf0,
bqfState[..1],
bqfState.Slice(1, 1),
!destination.IsBiquadFilterEnabledPrev(),
false,
isFirstMixBuffer,
nodeId);
destination.UpdateBiquadFilterEnabledPrev(0);
}
else if (bqf1.Enable)
{
_commandBuffer.GenerateBiquadFilterAndMix(
0f,
volume,
inputBufferIndex,
outputBufferIndex,
0,
Memory<VoiceUpdateState>.Empty,
ref bqf1,
bqfState[..1],
bqfState.Slice(1, 1),
!destination.IsBiquadFilterEnabledPrev(),
false,
isFirstMixBuffer,
nodeId);
destination.UpdateBiquadFilterEnabledPrev(1);
}
isFirstMixBuffer = false;
}
private void GenerateMix(ref MixState mix) private void GenerateMix(ref MixState mix)
{ {
if (mix.HasAnyDestination()) if (mix.HasAnyDestination())
@@ -932,13 +722,15 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
int destinationIndex = destinationId++; int destinationIndex = destinationId++;
SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex); Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
if (destination.IsNull) if (destinationSpan.IsEmpty)
{ {
break; break;
} }
ref SplitterDestination destination = ref destinationSpan[0];
if (destination.IsConfigured()) if (destination.IsConfigured())
{ {
int mixId = destination.DestinationId; int mixId = destination.DestinationId;
@@ -949,32 +741,16 @@ namespace Ryujinx.Audio.Renderer.Server
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount); uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
bool isFirstMixBuffer = true;
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
{ {
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex); float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
if (volume != 0.0f) if (volume != 0.0f)
{ {
if (destination.IsBiquadFilterEnabled()) _commandBuffer.GenerateMix(inputBufferIndex,
{ destinationMix.BufferOffset + bufferDestinationIndex,
GenerateMixWithSplitter( mix.NodeId,
inputBufferIndex, volume);
destinationMix.BufferOffset + bufferDestinationIndex,
volume,
destination,
ref isFirstMixBuffer,
mix.NodeId);
}
else
{
_commandBuffer.GenerateMix(
inputBufferIndex,
destinationMix.BufferOffset + bufferDestinationIndex,
mix.NodeId,
volume);
}
} }
} }
} }
@@ -994,11 +770,10 @@ namespace Ryujinx.Audio.Renderer.Server
if (volume != 0.0f) if (volume != 0.0f)
{ {
_commandBuffer.GenerateMix( _commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex,
mix.BufferOffset + bufferIndex, destinationMix.BufferOffset + bufferDestinationIndex,
destinationMix.BufferOffset + bufferDestinationIndex, mix.NodeId,
mix.NodeId, volume);
volume);
} }
} }
} }
@@ -1008,12 +783,11 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateSubMix(ref MixState subMix) private void GenerateSubMix(ref MixState subMix)
{ {
_commandBuffer.GenerateDepopForMixBuffers( _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
_rendererContext.DepopBuffer, subMix.BufferOffset,
subMix.BufferOffset, subMix.BufferCount,
subMix.BufferCount, subMix.NodeId,
subMix.NodeId, subMix.SampleRate);
subMix.SampleRate);
GenerateEffects(ref subMix); GenerateEffects(ref subMix);
@@ -1073,12 +847,11 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
ref MixState finalMix = ref _mixContext.GetFinalState(); ref MixState finalMix = ref _mixContext.GetFinalState();
_commandBuffer.GenerateDepopForMixBuffers( _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
_rendererContext.DepopBuffer, finalMix.BufferOffset,
finalMix.BufferOffset, finalMix.BufferCount,
finalMix.BufferCount, finalMix.NodeId,
finalMix.NodeId, finalMix.SampleRate);
finalMix.SampleRate);
GenerateEffects(ref finalMix); GenerateEffects(ref finalMix);
@@ -1109,10 +882,9 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
} }
_commandBuffer.GenerateVolume( _commandBuffer.GenerateVolume(finalMix.Volume,
finalMix.Volume, finalMix.BufferOffset + bufferIndex,
finalMix.BufferOffset + bufferIndex, nodeId);
nodeId);
if (performanceSubInitialized) if (performanceSubInitialized)
{ {
@@ -1166,45 +938,41 @@ namespace Ryujinx.Audio.Renderer.Server
if (useCustomDownMixingCommand) if (useCustomDownMixingCommand)
{ {
_commandBuffer.GenerateDownMixSurroundToStereo( _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
finalMix.BufferOffset, sink.Parameter.Input.AsSpan(),
sink.Parameter.Input.AsSpan(), sink.Parameter.Input.AsSpan(),
sink.Parameter.Input.AsSpan(), sink.DownMixCoefficients,
sink.DownMixCoefficients, Constants.InvalidNodeId);
Constants.InvalidNodeId);
} }
// NOTE: We do the downmixing at the DSP level as it's easier that way. // NOTE: We do the downmixing at the DSP level as it's easier that way.
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6) else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
{ {
_commandBuffer.GenerateDownMixSurroundToStereo( _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
finalMix.BufferOffset, sink.Parameter.Input.AsSpan(),
sink.Parameter.Input.AsSpan(), sink.Parameter.Input.AsSpan(),
sink.Parameter.Input.AsSpan(), Constants.DefaultSurroundToStereoCoefficients,
Constants.DefaultSurroundToStereoCoefficients, Constants.InvalidNodeId);
Constants.InvalidNodeId);
} }
CommandList commandList = _commandBuffer.CommandList; CommandList commandList = _commandBuffer.CommandList;
if (sink.UpsamplerState != null) if (sink.UpsamplerState != null)
{ {
_commandBuffer.GenerateUpsample( _commandBuffer.GenerateUpsample(finalMix.BufferOffset,
finalMix.BufferOffset, sink.UpsamplerState,
sink.UpsamplerState, sink.Parameter.InputCount,
sink.Parameter.InputCount, sink.Parameter.Input.AsSpan(),
sink.Parameter.Input.AsSpan(), commandList.BufferCount,
commandList.BufferCount, commandList.SampleCount,
commandList.SampleCount, commandList.SampleRate,
commandList.SampleRate, Constants.InvalidNodeId);
Constants.InvalidNodeId);
} }
_commandBuffer.GenerateDeviceSink( _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset,
finalMix.BufferOffset, sink,
sink, _rendererContext.SessionId,
_rendererContext.SessionId, commandList.Buffers,
commandList.Buffers, Constants.InvalidNodeId);
Constants.InvalidNodeId);
} }
private void GenerateSink(BaseSink sink, ref MixState finalMix) private void GenerateSink(BaseSink sink, ref MixState finalMix)

View File

@@ -170,7 +170,7 @@ namespace Ryujinx.Audio.Renderer.Server
return 0; return 0;
} }
public uint Estimate(MultiTapBiquadFilterCommand command) public uint Estimate(GroupedBiquadFilterCommand command)
{ {
return 0; return 0;
} }
@@ -184,15 +184,5 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
return 0; return 0;
} }
public uint Estimate(BiquadFilterAndMixCommand command)
{
return 0;
}
public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
{
return 0;
}
} }
} }

View File

@@ -462,7 +462,7 @@ namespace Ryujinx.Audio.Renderer.Server
return 0; return 0;
} }
public uint Estimate(MultiTapBiquadFilterCommand command) public uint Estimate(GroupedBiquadFilterCommand command)
{ {
return 0; return 0;
} }
@@ -476,15 +476,5 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
return 0; return 0;
} }
public uint Estimate(BiquadFilterAndMixCommand command)
{
return 0;
}
public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
{
return 0;
}
} }
} }

View File

@@ -632,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
}; };
} }
public virtual uint Estimate(MultiTapBiquadFilterCommand command) public virtual uint Estimate(GroupedBiquadFilterCommand command)
{ {
return 0; return 0;
} }
@@ -646,15 +646,5 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
return 0; return 0;
} }
public virtual uint Estimate(BiquadFilterAndMixCommand command)
{
return 0;
}
public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command)
{
return 0;
}
} }
} }

View File

@@ -10,7 +10,7 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { } public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
public override uint Estimate(MultiTapBiquadFilterCommand command) public override uint Estimate(GroupedBiquadFilterCommand command)
{ {
Debug.Assert(SampleCount == 160 || SampleCount == 240); Debug.Assert(SampleCount == 160 || SampleCount == 240);

View File

@@ -210,53 +210,5 @@ namespace Ryujinx.Audio.Renderer.Server
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
}; };
} }
public override uint Estimate(BiquadFilterAndMixCommand command)
{
Debug.Assert(SampleCount == 160 || SampleCount == 240);
if (command.HasVolumeRamp)
{
if (SampleCount == 160)
{
return 5204;
}
return 6683;
}
else
{
if (SampleCount == 160)
{
return 3427;
}
return 4752;
}
}
public override uint Estimate(MultiTapBiquadFilterAndMixCommand command)
{
Debug.Assert(SampleCount == 160 || SampleCount == 240);
if (command.HasVolumeRamp)
{
if (SampleCount == 160)
{
return 7939;
}
return 10669;
}
else
{
if (SampleCount == 160)
{
return 6256;
}
return 8683;
}
}
} }
} }

View File

@@ -33,10 +33,8 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(UpsampleCommand command); uint Estimate(UpsampleCommand command);
uint Estimate(LimiterCommandVersion1 command); uint Estimate(LimiterCommandVersion1 command);
uint Estimate(LimiterCommandVersion2 command); uint Estimate(LimiterCommandVersion2 command);
uint Estimate(MultiTapBiquadFilterCommand command); uint Estimate(GroupedBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command); uint Estimate(CaptureBufferCommand command);
uint Estimate(CompressorCommand command); uint Estimate(CompressorCommand command);
uint Estimate(BiquadFilterAndMixCommand command);
uint Estimate(MultiTapBiquadFilterAndMixCommand command);
} }
} }

View File

@@ -225,11 +225,11 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
for (int i = 0; i < splitter.DestinationCount; i++) for (int i = 0; i < splitter.DestinationCount; i++)
{ {
SplitterDestination destination = splitter.GetData(i); Span<SplitterDestination> destination = splitter.GetData(i);
if (!destination.IsNull) if (!destination.IsEmpty)
{ {
int destinationMixId = destination.DestinationId; int destinationMixId = destination[0].DestinationId;
if (destinationMixId != UnusedMixId) if (destinationMixId != UnusedMixId)
{ {

View File

@@ -1,5 +1,4 @@
using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common; using Ryujinx.Common;
@@ -16,35 +15,15 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
public class SplitterContext public class SplitterContext
{ {
/// <summary>
/// Amount of biquad filter states per splitter destination.
/// </summary>
public const int BqfStatesPerDestination = 4;
/// <summary> /// <summary>
/// Storage for <see cref="SplitterState"/>. /// Storage for <see cref="SplitterState"/>.
/// </summary> /// </summary>
private Memory<SplitterState> _splitters; private Memory<SplitterState> _splitters;
/// <summary> /// <summary>
/// Storage for <see cref="SplitterDestinationVersion1"/>. /// Storage for <see cref="SplitterDestination"/>.
/// </summary> /// </summary>
private Memory<SplitterDestinationVersion1> _splitterDestinationsV1; private Memory<SplitterDestination> _splitterDestinations;
/// <summary>
/// Storage for <see cref="SplitterDestinationVersion2"/>.
/// </summary>
private Memory<SplitterDestinationVersion2> _splitterDestinationsV2;
/// <summary>
/// Splitter biquad filtering states.
/// </summary>
private Memory<BiquadFilterState> _splitterBqfStates;
/// <summary>
/// Version of the splitter context that is being used, currently can be 1 or 2.
/// </summary>
public int Version { get; private set; }
/// <summary> /// <summary>
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>. /// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.
@@ -57,17 +36,12 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// <param name="behaviourContext">The behaviour context.</param> /// <param name="behaviourContext">The behaviour context.</param>
/// <param name="parameter">The audio renderer configuration.</param> /// <param name="parameter">The audio renderer configuration.</param>
/// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param> /// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param>
/// <param name="splitterBqfStates">Memory to store the biquad filtering state for splitters during processing.</param>
/// <returns>Return true if the initialization was successful.</returns> /// <returns>Return true if the initialization was successful.</returns>
public bool Initialize( public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
ref BehaviourContext behaviourContext,
ref AudioRendererConfiguration parameter,
WorkBufferAllocator workBufferAllocator,
Memory<BiquadFilterState> splitterBqfStates)
{ {
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0) if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
{ {
Setup(Memory<SplitterState>.Empty, Memory<SplitterDestinationVersion1>.Empty, Memory<SplitterDestinationVersion2>.Empty, false); Setup(Memory<SplitterState>.Empty, Memory<SplitterDestination>.Empty, false);
return true; return true;
} }
@@ -86,62 +60,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
splitter = new SplitterState(splitterId++); splitter = new SplitterState(splitterId++);
} }
Memory<SplitterDestinationVersion1> splitterDestinationsV1 = Memory<SplitterDestinationVersion1>.Empty; Memory<SplitterDestination> splitterDestinations = workBufferAllocator.Allocate<SplitterDestination>(parameter.SplitterDestinationCount,
Memory<SplitterDestinationVersion2> splitterDestinationsV2 = Memory<SplitterDestinationVersion2>.Empty; SplitterDestination.Alignment);
if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled()) if (splitterDestinations.IsEmpty)
{ {
Version = 1; return false;
splitterDestinationsV1 = workBufferAllocator.Allocate<SplitterDestinationVersion1>(parameter.SplitterDestinationCount,
SplitterDestinationVersion1.Alignment);
if (splitterDestinationsV1.IsEmpty)
{
return false;
}
int splitterDestinationId = 0;
foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span)
{
data = new SplitterDestinationVersion1(splitterDestinationId++);
}
} }
else
int splitterDestinationId = 0;
foreach (ref SplitterDestination data in splitterDestinations.Span)
{ {
Version = 2; data = new SplitterDestination(splitterDestinationId++);
splitterDestinationsV2 = workBufferAllocator.Allocate<SplitterDestinationVersion2>(parameter.SplitterDestinationCount,
SplitterDestinationVersion2.Alignment);
if (splitterDestinationsV2.IsEmpty)
{
return false;
}
int splitterDestinationId = 0;
foreach (ref SplitterDestinationVersion2 data in splitterDestinationsV2.Span)
{
data = new SplitterDestinationVersion2(splitterDestinationId++);
}
if (parameter.SplitterDestinationCount > 0)
{
// Official code stores it in the SplitterDestinationVersion2 struct,
// but we don't to avoid using unsafe code.
splitterBqfStates.Span.Clear();
_splitterBqfStates = splitterBqfStates;
}
else
{
_splitterBqfStates = Memory<BiquadFilterState>.Empty;
}
} }
SplitterState.InitializeSplitters(splitters.Span); SplitterState.InitializeSplitters(splitters.Span);
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed()); Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed());
return true; return true;
} }
@@ -158,15 +93,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
if (behaviourContext.IsSplitterSupported()) if (behaviourContext.IsSplitterSupported())
{ {
size = WorkBufferAllocator.GetTargetSize<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment); size = WorkBufferAllocator.GetTargetSize<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment);
size = WorkBufferAllocator.GetTargetSize<SplitterDestination>(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
{
size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion2>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment);
}
else
{
size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion1>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment);
}
if (behaviourContext.IsSplitterBugFixed()) if (behaviourContext.IsSplitterBugFixed())
{ {
@@ -183,18 +110,12 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Setup the <see cref="SplitterContext"/> instance. /// Setup the <see cref="SplitterContext"/> instance.
/// </summary> /// </summary>
/// <param name="splitters">The <see cref="SplitterState"/> storage.</param> /// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
/// <param name="splitterDestinationsV1">The <see cref="SplitterDestinationVersion1"/> storage.</param> /// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param>
/// <param name="splitterDestinationsV2">The <see cref="SplitterDestinationVersion2"/> storage.</param>
/// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param> /// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param>
private void Setup( private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
Memory<SplitterState> splitters,
Memory<SplitterDestinationVersion1> splitterDestinationsV1,
Memory<SplitterDestinationVersion2> splitterDestinationsV2,
bool isBugFixed)
{ {
_splitters = splitters; _splitters = splitters;
_splitterDestinationsV1 = splitterDestinationsV1; _splitterDestinations = splitterDestinations;
_splitterDestinationsV2 = splitterDestinationsV2;
IsBugFixed = isBugFixed; IsBugFixed = isBugFixed;
} }
@@ -220,9 +141,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
return 0; return 0;
} }
int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length; return _splitterDestinations.Length / _splitters.Length;
return length / _splitters.Length;
} }
/// <summary> /// <summary>
@@ -259,39 +178,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
} }
/// <summary> /// <summary>
/// Update one splitter destination data from user parameters. /// Update one or multiple <see cref="SplitterDestination"/> from user parameters.
/// </summary>
/// <param name="input">The raw data after the splitter header.</param>
/// <returns>True if the update was successful, false otherwise</returns>
private bool UpdateData<T>(ref SequenceReader<byte> input) where T : unmanaged, ISplitterDestinationInParameter
{
ref readonly T parameter = ref input.GetRefOrRefToCopy<T>(out _);
Debug.Assert(parameter.IsMagicValid());
if (parameter.IsMagicValid())
{
int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
if (parameter.Id >= 0 && parameter.Id < length)
{
SplitterDestination destination = GetDestination(parameter.Id);
destination.Update(parameter);
}
return true;
}
else
{
input.Rewind(Unsafe.SizeOf<T>());
return false;
}
}
/// <summary>
/// Update one or multiple splitter destination data from user parameters.
/// </summary> /// </summary>
/// <param name="inputHeader">The splitter header.</param> /// <param name="inputHeader">The splitter header.</param>
/// <param name="input">The raw data after the splitter header.</param> /// <param name="input">The raw data after the splitter header.</param>
@@ -299,23 +186,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
{ {
if (Version == 1) ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _);
Debug.Assert(parameter.IsMagicValid());
if (parameter.IsMagicValid())
{ {
if (!UpdateData<SplitterDestinationInParameterVersion1>(ref input)) if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
{ {
break; ref SplitterDestination destination = ref GetDestination(parameter.Id);
}
} destination.Update(parameter);
else if (Version == 2)
{
if (!UpdateData<SplitterDestinationInParameterVersion2>(ref input))
{
break;
} }
} }
else else
{ {
Debug.Fail($"Invalid splitter context version {Version}."); input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>());
break;
} }
} }
} }
@@ -327,7 +214,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// <returns>Return true if the update was successful.</returns> /// <returns>Return true if the update was successful.</returns>
public bool Update(ref SequenceReader<byte> input) public bool Update(ref SequenceReader<byte> input)
{ {
if (!UsingSplitter()) if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
{ {
return true; return true;
} }
@@ -364,52 +251,45 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
} }
/// <summary> /// <summary>
/// Get a reference to the splitter destination data at the given <paramref name="id"/>. /// Get a reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.
/// </summary> /// </summary>
/// <param name="id">The index to use.</param> /// <param name="id">The index to use.</param>
/// <returns>A reference to the splitter destination data at the given <paramref name="id"/>.</returns> /// <returns>A reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.</returns>
public SplitterDestination GetDestination(int id) public ref SplitterDestination GetDestination(int id)
{ {
if (_splitterDestinationsV2.IsEmpty) return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
{
return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV1, id, (uint)_splitterDestinationsV1.Length));
}
else
{
return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV2, id, (uint)_splitterDestinationsV2.Length));
}
} }
/// <summary> /// <summary>
/// Get a <see cref="SplitterDestination"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>. /// Get a <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.
/// </summary>
/// <param name="id">The index to use.</param>
/// <returns>A <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.</returns>
public Memory<SplitterDestination> GetDestinationMemory(int id)
{
return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
}
/// <summary>
/// Get a <see cref="Span{SplitterDestination}"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
/// </summary> /// </summary>
/// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param> /// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param>
/// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param> /// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param>
/// <returns>A <see cref="SplitterDestination"/>.</returns> /// <returns>A <see cref="Span{SplitterDestination}"/>.</returns>
public SplitterDestination GetDestination(int id, int destinationId) public Span<SplitterDestination> GetDestination(int id, int destinationId)
{ {
ref SplitterState splitter = ref GetState(id); ref SplitterState splitter = ref GetState(id);
return splitter.GetData(destinationId); return splitter.GetData(destinationId);
} }
/// <summary>
/// Gets the biquad filter state for a given splitter destination.
/// </summary>
/// <param name="destination">The splitter destination.</param>
/// <returns>Biquad filter state for the specified destination.</returns>
public Memory<BiquadFilterState> GetBiquadFilterState(SplitterDestination destination)
{
return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination);
}
/// <summary> /// <summary>
/// Return true if the audio renderer has any splitters. /// Return true if the audio renderer has any splitters.
/// </summary> /// </summary>
/// <returns>True if the audio renderer has any splitters.</returns> /// <returns>True if the audio renderer has any splitters.</returns>
public bool UsingSplitter() public bool UsingSplitter()
{ {
return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty); return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
} }
/// <summary> /// <summary>

View File

@@ -1,198 +1,115 @@
using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Common.Utilities;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
/// <summary> /// <summary>
/// Server state for a splitter destination. /// Server state for a splitter destination.
/// </summary> /// </summary>
public ref struct SplitterDestination [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
public struct SplitterDestination
{ {
private ref SplitterDestinationVersion1 _v1; public const int Alignment = 0x10;
private ref SplitterDestinationVersion2 _v2;
/// <summary> /// <summary>
/// Checks if the splitter destination data reference is null. /// The unique id of this <see cref="SplitterDestination"/>.
/// </summary> /// </summary>
public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2); public int Id;
/// <summary>
/// The splitter unique id.
/// </summary>
public int Id
{
get
{
if (Unsafe.IsNullRef(ref _v2))
{
if (Unsafe.IsNullRef(ref _v1))
{
return 0;
}
else
{
return _v1.Id;
}
}
else
{
return _v2.Id;
}
}
}
/// <summary> /// <summary>
/// The mix to output the result of the splitter. /// The mix to output the result of the splitter.
/// </summary> /// </summary>
public int DestinationId public int DestinationId;
{
get /// <summary>
{ /// Mix buffer volumes storage.
if (Unsafe.IsNullRef(ref _v2)) /// </summary>
{ private MixArray _mix;
if (Unsafe.IsNullRef(ref _v1)) private MixArray _previousMix;
{
return 0; /// <summary>
} /// Pointer to the next linked element.
else /// </summary>
{ private unsafe SplitterDestination* _next;
return _v1.DestinationId;
} /// <summary>
} /// Set to true if in use.
else /// </summary>
{ [MarshalAs(UnmanagedType.I1)]
return _v2.DestinationId; public bool IsUsed;
}
} /// <summary>
} /// Set to true if the internal state need to be updated.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool NeedToUpdateInternalState;
[StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
/// <summary> /// <summary>
/// Mix buffer volumes. /// Mix buffer volumes.
/// </summary> /// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks> /// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> MixBufferVolume public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
{
get
{
if (Unsafe.IsNullRef(ref _v2))
{
if (Unsafe.IsNullRef(ref _v1))
{
return Span<float>.Empty;
}
else
{
return _v1.MixBufferVolume;
}
}
else
{
return _v2.MixBufferVolume;
}
}
}
/// <summary> /// <summary>
/// Previous mix buffer volumes. /// Previous mix buffer volumes.
/// </summary> /// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks> /// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> PreviousMixBufferVolume public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
{
get
{
if (Unsafe.IsNullRef(ref _v2))
{
if (Unsafe.IsNullRef(ref _v1))
{
return Span<float>.Empty;
}
else
{
return _v1.PreviousMixBufferVolume;
}
}
else
{
return _v2.PreviousMixBufferVolume;
}
}
}
/// <summary> /// <summary>
/// Get the <see cref="SplitterDestination"/> of the next element or null if not present. /// Get the <see cref="Span{SplitterDestination}"/> of the next element or <see cref="Span{SplitterDestination}.Empty"/> if not present.
/// </summary> /// </summary>
public readonly SplitterDestination Next public readonly Span<SplitterDestination> Next
{ {
get get
{ {
unsafe unsafe
{ {
if (Unsafe.IsNullRef(ref _v2)) return _next != null ? new Span<SplitterDestination>(_next, 1) : Span<SplitterDestination>.Empty;
{
if (Unsafe.IsNullRef(ref _v1))
{
return new SplitterDestination();
}
else
{
return new SplitterDestination(ref _v1.Next);
}
}
else
{
return new SplitterDestination(ref _v2.Next);
}
} }
} }
} }
/// <summary> /// <summary>
/// Creates a new splitter destination wrapper for the version 1 splitter destination data. /// Create a new <see cref="SplitterDestination"/>.
/// </summary> /// </summary>
/// <param name="v1">Version 1 splitter destination data</param> /// <param name="id">The unique id of this <see cref="SplitterDestination"/>.</param>
public SplitterDestination(ref SplitterDestinationVersion1 v1) public SplitterDestination(int id) : this()
{ {
_v1 = ref v1; Id = id;
_v2 = ref Unsafe.NullRef<SplitterDestinationVersion2>(); DestinationId = Constants.UnusedMixId;
ClearVolumes();
} }
/// <summary> /// <summary>
/// Creates a new splitter destination wrapper for the version 2 splitter destination data. /// Update the <see cref="SplitterDestination"/> from user parameter.
/// </summary>
/// <param name="v2">Version 2 splitter destination data</param>
public SplitterDestination(ref SplitterDestinationVersion2 v2)
{
_v1 = ref Unsafe.NullRef<SplitterDestinationVersion1>();
_v2 = ref v2;
}
/// <summary>
/// Creates a new splitter destination wrapper for the splitter destination data.
/// </summary>
/// <param name="v1">Version 1 splitter destination data</param>
/// <param name="v2">Version 2 splitter destination data</param>
public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2)
{
_v1 = ref Unsafe.AsRef<SplitterDestinationVersion1>(v1);
_v2 = ref Unsafe.AsRef<SplitterDestinationVersion2>(v2);
}
/// <summary>
/// Update the splitter destination data from user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter public void Update(SplitterDestinationInParameter parameter)
{ {
if (Unsafe.IsNullRef(ref _v2)) Debug.Assert(Id == parameter.Id);
if (parameter.IsMagicValid() && Id == parameter.Id)
{ {
_v1.Update(parameter); DestinationId = parameter.DestinationId;
}
else parameter.MixBufferVolume.CopyTo(MixBufferVolume);
{
_v2.Update(parameter); if (!IsUsed && parameter.IsUsed)
{
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
NeedToUpdateInternalState = false;
}
IsUsed = parameter.IsUsed;
} }
} }
@@ -201,14 +118,12 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
public void UpdateInternalState() public void UpdateInternalState()
{ {
if (Unsafe.IsNullRef(ref _v2)) if (IsUsed && NeedToUpdateInternalState)
{ {
_v1.UpdateInternalState(); MixBufferVolume.CopyTo(PreviousMixBufferVolume);
}
else
{
_v2.UpdateInternalState();
} }
NeedToUpdateInternalState = false;
} }
/// <summary> /// <summary>
@@ -216,23 +131,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
public void MarkAsNeedToUpdateInternalState() public void MarkAsNeedToUpdateInternalState()
{ {
if (Unsafe.IsNullRef(ref _v2)) NeedToUpdateInternalState = true;
{
_v1.MarkAsNeedToUpdateInternalState();
}
else
{
_v2.MarkAsNeedToUpdateInternalState();
}
} }
/// <summary> /// <summary>
/// Return true if the splitter destination is used and has a destination. /// Return true if the <see cref="SplitterDestination"/> is used and has a destination.
/// </summary> /// </summary>
/// <returns>True if the splitter destination is used and has a destination.</returns> /// <returns>True if the <see cref="SplitterDestination"/> is used and has a destination.</returns>
public readonly bool IsConfigured() public readonly bool IsConfigured()
{ {
return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured(); return IsUsed && DestinationId != Constants.UnusedMixId;
} }
/// <summary> /// <summary>
@@ -242,17 +150,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// <returns>The volume for the given destination.</returns> /// <returns>The volume for the given destination.</returns>
public float GetMixVolume(int destinationIndex) public float GetMixVolume(int destinationIndex)
{ {
return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolume(destinationIndex) : _v2.GetMixVolume(destinationIndex); Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
}
/// <summary> return MixBufferVolume[destinationIndex];
/// Get the previous volume for a given destination.
/// </summary>
/// <param name="destinationIndex">The destination index to use.</param>
/// <returns>The volume for the given destination.</returns>
public float GetMixVolumePrev(int destinationIndex)
{
return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex);
} }
/// <summary> /// <summary>
@@ -260,33 +160,22 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
public void ClearVolumes() public void ClearVolumes()
{ {
if (Unsafe.IsNullRef(ref _v2)) MixBufferVolume.Clear();
{ PreviousMixBufferVolume.Clear();
_v1.ClearVolumes();
}
else
{
_v2.ClearVolumes();
}
} }
/// <summary> /// <summary>
/// Link the next element to the given splitter destination. /// Link the next element to the given <see cref="SplitterDestination"/>.
/// </summary> /// </summary>
/// <param name="next">The given splitter destination to link.</param> /// <param name="next">The given <see cref="SplitterDestination"/> to link.</param>
public void Link(SplitterDestination next) public void Link(ref SplitterDestination next)
{ {
if (Unsafe.IsNullRef(ref _v2)) unsafe
{ {
Debug.Assert(!Unsafe.IsNullRef(ref next._v1)); fixed (SplitterDestination* nextPtr = &next)
{
_v1.Link(ref next._v1); _next = nextPtr;
} }
else
{
Debug.Assert(!Unsafe.IsNullRef(ref next._v2));
_v2.Link(ref next._v2);
} }
} }
@@ -295,74 +184,10 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
public void Unlink() public void Unlink()
{ {
if (Unsafe.IsNullRef(ref _v2)) unsafe
{ {
_v1.Unlink(); _next = null;
} }
else
{
_v2.Unlink();
}
}
/// <summary>
/// Checks if any biquad filter is enabled.
/// </summary>
/// <returns>True if any biquad filter is enabled.</returns>
public bool IsBiquadFilterEnabled()
{
return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled();
}
/// <summary>
/// Checks if any biquad filter was previously enabled.
/// </summary>
/// <returns>True if any biquad filter was previously enabled.</returns>
public bool IsBiquadFilterEnabledPrev()
{
return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev();
}
/// <summary>
/// Gets the biquad filter parameters.
/// </summary>
/// <param name="index">Biquad filter index (0 or 1).</param>
/// <returns>Biquad filter parameters.</returns>
public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
{
Debug.Assert(!Unsafe.IsNullRef(ref _v2));
return ref _v2.GetBiquadFilterParameter(index);
}
/// <summary>
/// Checks if any biquad filter was previously enabled.
/// </summary>
/// <param name="index">Biquad filter index (0 or 1).</param>
public void UpdateBiquadFilterEnabledPrev(int index)
{
if (!Unsafe.IsNullRef(ref _v2))
{
_v2.UpdateBiquadFilterEnabledPrev(index);
}
}
/// <summary>
/// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null.
/// </summary>
/// <returns>Reference for the version 1 splitter destination data.</returns>
public ref SplitterDestinationVersion1 GetV1RefOrNull()
{
return ref _v1;
}
/// <summary>
/// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null.
/// </summary>
/// <returns>Reference for the version 2 splitter destination data.</returns>
public ref SplitterDestinationVersion2 GetV2RefOrNull()
{
return ref _v2;
} }
} }
} }

View File

@@ -1,206 +0,0 @@
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Common.Utilities;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter
{
/// <summary>
/// Server state for a splitter destination (version 1).
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
public struct SplitterDestinationVersion1
{
public const int Alignment = 0x10;
/// <summary>
/// The unique id of this <see cref="SplitterDestinationVersion1"/>.
/// </summary>
public int Id;
/// <summary>
/// The mix to output the result of the splitter.
/// </summary>
public int DestinationId;
/// <summary>
/// Mix buffer volumes storage.
/// </summary>
private MixArray _mix;
private MixArray _previousMix;
/// <summary>
/// Pointer to the next linked element.
/// </summary>
private unsafe SplitterDestinationVersion1* _next;
/// <summary>
/// Set to true if in use.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
/// <summary>
/// Set to true if the internal state need to be updated.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool NeedToUpdateInternalState;
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
/// <summary>
/// Mix buffer volumes.
/// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
/// <summary>
/// Previous mix buffer volumes.
/// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
/// <summary>
/// Get the reference of the next element or null if not present.
/// </summary>
public readonly ref SplitterDestinationVersion1 Next
{
get
{
unsafe
{
return ref Unsafe.AsRef<SplitterDestinationVersion1>(_next);
}
}
}
/// <summary>
/// Create a new <see cref="SplitterDestinationVersion1"/>.
/// </summary>
/// <param name="id">The unique id of this <see cref="SplitterDestinationVersion1"/>.</param>
public SplitterDestinationVersion1(int id) : this()
{
Id = id;
DestinationId = Constants.UnusedMixId;
ClearVolumes();
}
/// <summary>
/// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
/// </summary>
/// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
{
Debug.Assert(Id == parameter.Id);
if (parameter.IsMagicValid() && Id == parameter.Id)
{
DestinationId = parameter.DestinationId;
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
if (!IsUsed && parameter.IsUsed)
{
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
NeedToUpdateInternalState = false;
}
IsUsed = parameter.IsUsed;
}
}
/// <summary>
/// Update the internal state of the instance.
/// </summary>
public void UpdateInternalState()
{
if (IsUsed && NeedToUpdateInternalState)
{
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
}
NeedToUpdateInternalState = false;
}
/// <summary>
/// Set the update internal state marker.
/// </summary>
public void MarkAsNeedToUpdateInternalState()
{
NeedToUpdateInternalState = true;
}
/// <summary>
/// Return true if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.
/// </summary>
/// <returns>True if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.</returns>
public readonly bool IsConfigured()
{
return IsUsed && DestinationId != Constants.UnusedMixId;
}
/// <summary>
/// Get the volume for a given destination.
/// </summary>
/// <param name="destinationIndex">The destination index to use.</param>
/// <returns>The volume for the given destination.</returns>
public float GetMixVolume(int destinationIndex)
{
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
return MixBufferVolume[destinationIndex];
}
/// <summary>
/// Get the previous volume for a given destination.
/// </summary>
/// <param name="destinationIndex">The destination index to use.</param>
/// <returns>The volume for the given destination.</returns>
public float GetMixVolumePrev(int destinationIndex)
{
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
return PreviousMixBufferVolume[destinationIndex];
}
/// <summary>
/// Clear the volumes.
/// </summary>
public void ClearVolumes()
{
MixBufferVolume.Clear();
PreviousMixBufferVolume.Clear();
}
/// <summary>
/// Link the next element to the given <see cref="SplitterDestinationVersion1"/>.
/// </summary>
/// <param name="next">The given <see cref="SplitterDestinationVersion1"/> to link.</param>
public void Link(ref SplitterDestinationVersion1 next)
{
unsafe
{
fixed (SplitterDestinationVersion1* nextPtr = &next)
{
_next = nextPtr;
}
}
}
/// <summary>
/// Remove the link to the next element.
/// </summary>
public void Unlink()
{
unsafe
{
_next = null;
}
}
}
}

View File

@@ -1,250 +0,0 @@
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter
{
/// <summary>
/// Server state for a splitter destination (version 2).
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)]
public struct SplitterDestinationVersion2
{
public const int Alignment = 0x10;
/// <summary>
/// The unique id of this <see cref="SplitterDestinationVersion2"/>.
/// </summary>
public int Id;
/// <summary>
/// The mix to output the result of the splitter.
/// </summary>
public int DestinationId;
/// <summary>
/// Mix buffer volumes storage.
/// </summary>
private MixArray _mix;
private MixArray _previousMix;
/// <summary>
/// Pointer to the next linked element.
/// </summary>
private unsafe SplitterDestinationVersion2* _next;
/// <summary>
/// Set to true if in use.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
/// <summary>
/// Set to true if the internal state need to be updated.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool NeedToUpdateInternalState;
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
/// <summary>
/// Mix buffer volumes.
/// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
/// <summary>
/// Previous mix buffer volumes.
/// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
/// <summary>
/// Get the reference of the next element or null if not present.
/// </summary>
public readonly ref SplitterDestinationVersion2 Next
{
get
{
unsafe
{
return ref Unsafe.AsRef<SplitterDestinationVersion2>(_next);
}
}
}
private Array2<BiquadFilterParameter> _biquadFilters;
private Array2<bool> _isPreviousBiquadFilterEnabled;
/// <summary>
/// Create a new <see cref="SplitterDestinationVersion2"/>.
/// </summary>
/// <param name="id">The unique id of this <see cref="SplitterDestinationVersion2"/>.</param>
public SplitterDestinationVersion2(int id) : this()
{
Id = id;
DestinationId = Constants.UnusedMixId;
ClearVolumes();
}
/// <summary>
/// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
/// </summary>
/// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
{
Debug.Assert(Id == parameter.Id);
if (parameter.IsMagicValid() && Id == parameter.Id)
{
DestinationId = parameter.DestinationId;
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
_biquadFilters = parameter.BiquadFilters;
if (!IsUsed && parameter.IsUsed)
{
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
NeedToUpdateInternalState = false;
}
IsUsed = parameter.IsUsed;
}
}
/// <summary>
/// Update the internal state of the instance.
/// </summary>
public void UpdateInternalState()
{
if (IsUsed && NeedToUpdateInternalState)
{
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
}
NeedToUpdateInternalState = false;
}
/// <summary>
/// Set the update internal state marker.
/// </summary>
public void MarkAsNeedToUpdateInternalState()
{
NeedToUpdateInternalState = true;
}
/// <summary>
/// Return true if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.
/// </summary>
/// <returns>True if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.</returns>
public readonly bool IsConfigured()
{
return IsUsed && DestinationId != Constants.UnusedMixId;
}
/// <summary>
/// Get the volume for a given destination.
/// </summary>
/// <param name="destinationIndex">The destination index to use.</param>
/// <returns>The volume for the given destination.</returns>
public float GetMixVolume(int destinationIndex)
{
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
return MixBufferVolume[destinationIndex];
}
/// <summary>
/// Get the previous volume for a given destination.
/// </summary>
/// <param name="destinationIndex">The destination index to use.</param>
/// <returns>The volume for the given destination.</returns>
public float GetMixVolumePrev(int destinationIndex)
{
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
return PreviousMixBufferVolume[destinationIndex];
}
/// <summary>
/// Clear the volumes.
/// </summary>
public void ClearVolumes()
{
MixBufferVolume.Clear();
PreviousMixBufferVolume.Clear();
}
/// <summary>
/// Link the next element to the given <see cref="SplitterDestinationVersion2"/>.
/// </summary>
/// <param name="next">The given <see cref="SplitterDestinationVersion2"/> to link.</param>
public void Link(ref SplitterDestinationVersion2 next)
{
unsafe
{
fixed (SplitterDestinationVersion2* nextPtr = &next)
{
_next = nextPtr;
}
}
}
/// <summary>
/// Remove the link to the next element.
/// </summary>
public void Unlink()
{
unsafe
{
_next = null;
}
}
/// <summary>
/// Checks if any biquad filter is enabled.
/// </summary>
/// <returns>True if any biquad filter is enabled.</returns>
public bool IsBiquadFilterEnabled()
{
return _biquadFilters[0].Enable || _biquadFilters[1].Enable;
}
/// <summary>
/// Checks if any biquad filter was previously enabled.
/// </summary>
/// <returns>True if any biquad filter was previously enabled.</returns>
public bool IsBiquadFilterEnabledPrev()
{
return _isPreviousBiquadFilterEnabled[0];
}
/// <summary>
/// Gets the biquad filter parameters.
/// </summary>
/// <param name="index">Biquad filter index (0 or 1).</param>
/// <returns>Biquad filter parameters.</returns>
public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
{
return ref _biquadFilters[index];
}
/// <summary>
/// Checks if any biquad filter was previously enabled.
/// </summary>
/// <param name="index">Biquad filter index (0 or 1).</param>
public void UpdateBiquadFilterEnabledPrev(int index)
{
_isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable;
}
}
}

View File

@@ -15,8 +15,6 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
public const int Alignment = 0x10; public const int Alignment = 0x10;
private delegate void SplitterDestinationAction(SplitterDestination destination, int index);
/// <summary> /// <summary>
/// The unique id of this <see cref="SplitterState"/>. /// The unique id of this <see cref="SplitterState"/>.
/// </summary> /// </summary>
@@ -28,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
public uint SampleRate; public uint SampleRate;
/// <summary> /// <summary>
/// Count of splitter destinations. /// Count of splitter destinations (<see cref="SplitterDestination"/>).
/// </summary> /// </summary>
public int DestinationCount; public int DestinationCount;
@@ -39,25 +37,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
public bool HasNewConnection; public bool HasNewConnection;
/// <summary> /// <summary>
/// Linked list of <see cref="SplitterDestinationVersion1"/>. /// Linked list of <see cref="SplitterDestination"/>.
/// </summary> /// </summary>
private unsafe SplitterDestinationVersion1* _destinationDataV1; private unsafe SplitterDestination* _destinationsData;
/// <summary> /// <summary>
/// Linked list of <see cref="SplitterDestinationVersion2"/>. /// Span to the first element of the linked list of <see cref="SplitterDestination"/>.
/// </summary> /// </summary>
private unsafe SplitterDestinationVersion2* _destinationDataV2; public readonly Span<SplitterDestination> Destinations
/// <summary>
/// First element of the linked list of splitter destinations data.
/// </summary>
public readonly SplitterDestination Destination
{ {
get get
{ {
unsafe unsafe
{ {
return new SplitterDestination(_destinationDataV1, _destinationDataV2); return (IntPtr)_destinationsData != IntPtr.Zero ? new Span<SplitterDestination>(_destinationsData, 1) : Span<SplitterDestination>.Empty;
} }
} }
} }
@@ -71,20 +64,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
Id = id; Id = id;
} }
public readonly SplitterDestination GetData(int index) public readonly Span<SplitterDestination> GetData(int index)
{ {
int i = 0; int i = 0;
SplitterDestination result = Destination; Span<SplitterDestination> result = Destinations;
while (i < index) while (i < index)
{ {
if (result.IsNull) if (result.IsEmpty)
{ {
break; break;
} }
result = result.Next; result = result[0].Next;
i++; i++;
} }
@@ -100,25 +93,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
} }
/// <summary> /// <summary>
/// Utility function to apply an action to all <see cref="Destination"/>. /// Utility function to apply a given <see cref="SpanAction{T, TArg}"/> to all <see cref="Destinations"/>.
/// </summary> /// </summary>
/// <param name="action">The action to execute on each elements.</param> /// <param name="action">The action to execute on each elements.</param>
private readonly void ForEachDestination(SplitterDestinationAction action) private readonly void ForEachDestination(SpanAction<SplitterDestination, int> action)
{ {
SplitterDestination temp = Destination; Span<SplitterDestination> temp = Destinations;
int i = 0; int i = 0;
while (true) while (true)
{ {
if (temp.IsNull) if (temp.IsEmpty)
{ {
break; break;
} }
SplitterDestination next = temp.Next; Span<SplitterDestination> next = temp[0].Next;
action(temp, i++); action.Invoke(temp, i++);
temp = next; temp = next;
} }
@@ -149,9 +142,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
input.ReadLittleEndian(out int destinationId); input.ReadLittleEndian(out int destinationId);
SplitterDestination destination = context.GetDestination(destinationId); Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationId);
SetDestination(destination); SetDestination(ref destination.Span[0]);
DestinationCount = destinationCount; DestinationCount = destinationCount;
@@ -159,9 +152,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
input.ReadLittleEndian(out destinationId); input.ReadLittleEndian(out destinationId);
SplitterDestination nextDestination = context.GetDestination(destinationId); Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationId);
destination.Link(nextDestination); destination.Span[0].Link(ref nextDestination.Span[0]);
destination = nextDestination; destination = nextDestination;
} }
} }
@@ -181,21 +174,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
} }
/// <summary> /// <summary>
/// Set the head of the linked list of <see cref="Destination"/>. /// Set the head of the linked list of <see cref="Destinations"/>.
/// </summary> /// </summary>
/// <param name="newValue">New destination value.</param> /// <param name="newValue">A reference to a <see cref="SplitterDestination"/>.</param>
public void SetDestination(SplitterDestination newValue) public void SetDestination(ref SplitterDestination newValue)
{ {
unsafe unsafe
{ {
fixed (SplitterDestinationVersion1* newValuePtr = &newValue.GetV1RefOrNull()) fixed (SplitterDestination* newValuePtr = &newValue)
{ {
_destinationDataV1 = newValuePtr; _destinationsData = newValuePtr;
}
fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull())
{
_destinationDataV2 = newValuePtr;
} }
} }
} }
@@ -205,20 +193,19 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
public readonly void UpdateInternalState() public readonly void UpdateInternalState()
{ {
ForEachDestination((destination, _) => destination.UpdateInternalState()); ForEachDestination((destination, _) => destination[0].UpdateInternalState());
} }
/// <summary> /// <summary>
/// Clear all links from the <see cref="Destination"/>. /// Clear all links from the <see cref="Destinations"/>.
/// </summary> /// </summary>
public void ClearLinks() public void ClearLinks()
{ {
ForEachDestination((destination, _) => destination.Unlink()); ForEachDestination((destination, _) => destination[0].Unlink());
unsafe unsafe
{ {
_destinationDataV1 = null; _destinationsData = (SplitterDestination*)IntPtr.Zero;
_destinationDataV2 = null;
} }
} }
@@ -232,8 +219,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
unsafe unsafe
{ {
splitter._destinationDataV1 = null; splitter._destinationsData = (SplitterDestination*)IntPtr.Zero;
splitter._destinationDataV2 = null;
} }
splitter.DestinationCount = 0; splitter.DestinationCount = 0;

View File

@@ -6,13 +6,8 @@ namespace Ryujinx.Graphics.GAL
public enum BufferAccess public enum BufferAccess
{ {
Default = 0, Default = 0,
HostMemory = 1, FlushPersistent = 1 << 0,
DeviceMemory = 2, Stream = 1 << 1,
DeviceMemoryMapped = 3, SparseCompatible = 1 << 2,
MemoryTypeMask = 0xf,
Stream = 1 << 4,
SparseCompatible = 1 << 5,
} }
} }

View File

@@ -6,7 +6,6 @@ namespace Ryujinx.Graphics.GAL
{ {
public readonly TargetApi Api; public readonly TargetApi Api;
public readonly string VendorName; public readonly string VendorName;
public readonly SystemMemoryType MemoryType;
public readonly bool HasFrontFacingBug; public readonly bool HasFrontFacingBug;
public readonly bool HasVectorIndexingBug; public readonly bool HasVectorIndexingBug;
@@ -37,7 +36,6 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsMismatchingViewFormat; public readonly bool SupportsMismatchingViewFormat;
public readonly bool SupportsCubemapView; public readonly bool SupportsCubemapView;
public readonly bool SupportsNonConstantTextureOffset; public readonly bool SupportsNonConstantTextureOffset;
public readonly bool SupportsQuads;
public readonly bool SupportsSeparateSampler; public readonly bool SupportsSeparateSampler;
public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderBarrierDivergence;
@@ -67,7 +65,6 @@ namespace Ryujinx.Graphics.GAL
public Capabilities( public Capabilities(
TargetApi api, TargetApi api,
string vendorName, string vendorName,
SystemMemoryType memoryType,
bool hasFrontFacingBug, bool hasFrontFacingBug,
bool hasVectorIndexingBug, bool hasVectorIndexingBug,
bool needsFragmentOutputSpecialization, bool needsFragmentOutputSpecialization,
@@ -96,7 +93,6 @@ namespace Ryujinx.Graphics.GAL
bool supportsMismatchingViewFormat, bool supportsMismatchingViewFormat,
bool supportsCubemapView, bool supportsCubemapView,
bool supportsNonConstantTextureOffset, bool supportsNonConstantTextureOffset,
bool supportsQuads,
bool supportsSeparateSampler, bool supportsSeparateSampler,
bool supportsShaderBallot, bool supportsShaderBallot,
bool supportsShaderBarrierDivergence, bool supportsShaderBarrierDivergence,
@@ -122,7 +118,6 @@ namespace Ryujinx.Graphics.GAL
{ {
Api = api; Api = api;
VendorName = vendorName; VendorName = vendorName;
MemoryType = memoryType;
HasFrontFacingBug = hasFrontFacingBug; HasFrontFacingBug = hasFrontFacingBug;
HasVectorIndexingBug = hasVectorIndexingBug; HasVectorIndexingBug = hasVectorIndexingBug;
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization; NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
@@ -151,7 +146,6 @@ namespace Ryujinx.Graphics.GAL
SupportsMismatchingViewFormat = supportsMismatchingViewFormat; SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
SupportsCubemapView = supportsCubemapView; SupportsCubemapView = supportsCubemapView;
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
SupportsQuads = supportsQuads;
SupportsSeparateSampler = supportsSeparateSampler; SupportsSeparateSampler = supportsSeparateSampler;
SupportsShaderBallot = supportsShaderBallot; SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;

View File

@@ -17,6 +17,7 @@ namespace Ryujinx.Graphics.GAL
void BackgroundContextAction(Action action, bool alwaysBackground = false); void BackgroundContextAction(Action action, bool alwaysBackground = false);
BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default); BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default);
BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint);
BufferHandle CreateBuffer(nint pointer, int size); BufferHandle CreateBuffer(nint pointer, int size);
BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers); BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers);

View File

@@ -44,6 +44,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
} }
Register<ActionCommand>(CommandType.Action); Register<ActionCommand>(CommandType.Action);
Register<CreateBufferCommand>(CommandType.CreateBuffer);
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess); Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse); Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse);
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer); Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);

View File

@@ -3,6 +3,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
enum CommandType : byte enum CommandType : byte
{ {
Action, Action,
CreateBuffer,
CreateBufferAccess, CreateBufferAccess,
CreateBufferSparse, CreateBufferSparse,
CreateHostBuffer, CreateHostBuffer,

View File

@@ -0,0 +1,31 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateBufferCommand : IGALCommand, IGALCommand<CreateBufferCommand>
{
public readonly CommandType CommandType => CommandType.CreateBuffer;
private BufferHandle _threadedHandle;
private int _size;
private BufferAccess _access;
private BufferHandle _storageHint;
public void Set(BufferHandle threadedHandle, int size, BufferAccess access, BufferHandle storageHint)
{
_threadedHandle = threadedHandle;
_size = size;
_access = access;
_storageHint = storageHint;
}
public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
BufferHandle hint = BufferHandle.Null;
if (command._storageHint != BufferHandle.Null)
{
hint = threaded.Buffers.MapBuffer(command._storageHint);
}
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, command._access, hint));
}
}
}

View File

@@ -272,6 +272,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle; return handle;
} }
public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint)
{
BufferHandle handle = Buffers.CreateBufferHandle();
New<CreateBufferCommand>().Set(handle, size, access, storageHint);
QueueCommand();
return handle;
}
public BufferHandle CreateBuffer(nint pointer, int size) public BufferHandle CreateBuffer(nint pointer, int size)
{ {
BufferHandle handle = Buffers.CreateBufferHandle(); BufferHandle handle = Buffers.CreateBufferHandle();

View File

@@ -1,29 +0,0 @@
namespace Ryujinx.Graphics.GAL
{
public enum SystemMemoryType
{
/// <summary>
/// The backend manages the ownership of memory. This mode never supports host imported memory.
/// </summary>
BackendManaged,
/// <summary>
/// Device memory has similar performance to host memory, usually because it's shared between CPU/GPU.
/// Use host memory whenever possible.
/// </summary>
UnifiedMemory,
/// <summary>
/// GPU storage to host memory goes though a slow interconnect, but it would still be preferable to use it if the data is flushed back often.
/// Assumes constant buffer access to host memory is rather fast.
/// </summary>
DedicatedMemory,
/// <summary>
/// GPU storage to host memory goes though a slow interconnect, that is very slow when doing access from storage.
/// When frequently accessed, copy buffers to host memory using DMA.
/// Assumes constant buffer access to host memory is rather fast.
/// </summary>
DedicatedMemorySlowStorage
}
}

View File

@@ -5,7 +5,6 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo; using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -496,8 +495,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride; ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect); MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize);
MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect); MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4);
_processor.ThreedClass.DrawIndirect( _processor.ThreedClass.DrawIndirect(
topology, topology,

View File

@@ -438,7 +438,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
ReadOnlySpan<byte> dataBytes = MemoryMarshal.Cast<int, byte>(data); ReadOnlySpan<byte> dataBytes = MemoryMarshal.Cast<int, byte>(data);
BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length, BufferAccess.DeviceMemory); BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length);
_context.Renderer.SetBufferData(buffer, 0, dataBytes); _context.Renderer.SetBufferData(buffer, 0, dataBytes);
return new IndexBuffer(buffer, count, dataBytes.Length); return new IndexBuffer(buffer, count, dataBytes.Length);
@@ -529,7 +529,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{ {
if (_dummyBuffer == BufferHandle.Null) if (_dummyBuffer == BufferHandle.Null)
{ {
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory); _dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize);
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0); _context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
} }
@@ -550,7 +550,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.DeleteBuffer(_sequentialIndexBuffer); _context.Renderer.DeleteBuffer(_sequentialIndexBuffer);
} }
_sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint), BufferAccess.DeviceMemory); _sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint));
_sequentialIndexBufferCount = count; _sequentialIndexBufferCount = count;
Span<int> data = new int[count]; Span<int> data = new int[count];
@@ -583,7 +583,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.DeleteBuffer(buffer.Handle); _context.Renderer.DeleteBuffer(buffer.Handle);
} }
buffer.Handle = _context.Renderer.CreateBuffer(newSize, BufferAccess.DeviceMemory); buffer.Handle = _context.Renderer.CreateBuffer(newSize);
buffer.Size = newSize; buffer.Size = newSize;
} }

View File

@@ -3,7 +3,6 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
@@ -371,7 +370,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{ {
var memoryManager = _channel.MemoryManager; var memoryManager = _channel.MemoryManager;
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer); BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size));
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format); ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
bufferTexture.SetStorage(range); bufferTexture.SetStorage(range);
@@ -413,9 +412,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
var memoryManager = _channel.MemoryManager; var memoryManager = _channel.MemoryManager;
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1); ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange( BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign));
memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
BufferStage.IndexBuffer);
misalignedOffset = (int)misalign >> shift; misalignedOffset = (int)misalign >> shift;
SetIndexBufferTexture(reservations, range, format); SetIndexBufferTexture(reservations, range, format);

View File

@@ -684,8 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (hasCount) if (hasCount)
{ {
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect); var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange);
var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect); var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange);
if (indexed) if (indexed)
{ {
@@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
} }
else else
{ {
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect); var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange);
if (indexed) if (indexed)
{ {

View File

@@ -393,18 +393,11 @@ namespace Ryujinx.Graphics.Gpu
if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0)) if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
{ {
foreach (var action in SyncActions)
{
action.SyncPreAction(syncpoint);
}
foreach (var action in SyncpointActions)
{
action.SyncPreAction(syncpoint);
}
Renderer.CreateSync(SyncNumber, strict); Renderer.CreateSync(SyncNumber, strict);
SyncActions.ForEach(action => action.SyncPreAction(syncpoint));
SyncpointActions.ForEach(action => action.SyncPreAction(syncpoint));
SyncNumber++; SyncNumber++;
SyncActions.RemoveAll(action => action.SyncAction(syncpoint)); SyncActions.RemoveAll(action => action.SyncAction(syncpoint));

View File

@@ -390,7 +390,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
_views.Remove(texture); _views.Remove(texture);
Group.RemoveView(_views, texture); Group.RemoveView(texture);
texture._viewStorage = texture; texture._viewStorage = texture;

View File

@@ -708,11 +708,11 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format; format = texture.Format;
} }
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
else else
{ {
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
} }
else if (isImage) else if (isImage)
@@ -921,11 +921,11 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format; format = texture.Format;
} }
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
else else
{ {
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
} }
else if (isImage) else if (isImage)

View File

@@ -8,7 +8,6 @@ using Ryujinx.Graphics.Texture;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@@ -40,8 +39,6 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly MultiRangeList<Texture> _textures; private readonly MultiRangeList<Texture> _textures;
private readonly HashSet<Texture> _partiallyMappedTextures; private readonly HashSet<Texture> _partiallyMappedTextures;
private readonly ReaderWriterLockSlim _texturesLock;
private Texture[] _textureOverlaps; private Texture[] _textureOverlaps;
private OverlapInfo[] _overlapInfo; private OverlapInfo[] _overlapInfo;
@@ -60,8 +57,6 @@ namespace Ryujinx.Graphics.Gpu.Image
_textures = new MultiRangeList<Texture>(); _textures = new MultiRangeList<Texture>();
_partiallyMappedTextures = new HashSet<Texture>(); _partiallyMappedTextures = new HashSet<Texture>();
_texturesLock = new ReaderWriterLockSlim();
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; _textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
@@ -80,16 +75,10 @@ namespace Ryujinx.Graphics.Gpu.Image
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size); MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
_texturesLock.EnterReadLock(); lock (_textures)
try
{ {
overlapCount = _textures.FindOverlaps(unmapped, ref overlaps); overlapCount = _textures.FindOverlaps(unmapped, ref overlaps);
} }
finally
{
_texturesLock.ExitReadLock();
}
if (overlapCount > 0) if (overlapCount > 0)
{ {
@@ -228,18 +217,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public bool UpdateMapping(Texture texture, MultiRange range) public bool UpdateMapping(Texture texture, MultiRange range)
{ {
// There cannot be an existing texture compatible with this mapping in the texture cache already. // There cannot be an existing texture compatible with this mapping in the texture cache already.
int overlapCount; int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
_texturesLock.EnterReadLock();
try
{
overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
}
finally
{
_texturesLock.ExitReadLock();
}
for (int i = 0; i < overlapCount; i++) for (int i = 0; i < overlapCount; i++)
{ {
@@ -253,20 +231,11 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
_texturesLock.EnterWriteLock(); _textures.Remove(texture);
try texture.ReplaceRange(range);
{
_textures.Remove(texture);
texture.ReplaceRange(range); _textures.Add(texture);
_textures.Add(texture);
}
finally
{
_texturesLock.ExitWriteLock();
}
return true; return true;
} }
@@ -642,17 +611,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int sameAddressOverlapsCount; int sameAddressOverlapsCount;
_texturesLock.EnterReadLock(); lock (_textures)
try
{ {
// Try to find a perfect texture match, with the same address and parameters. // Try to find a perfect texture match, with the same address and parameters.
sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps); sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps);
} }
finally
{
_texturesLock.ExitReadLock();
}
Texture texture = null; Texture texture = null;
@@ -735,16 +698,10 @@ namespace Ryujinx.Graphics.Gpu.Image
if (info.Target != Target.TextureBuffer) if (info.Target != Target.TextureBuffer)
{ {
_texturesLock.EnterReadLock(); lock (_textures)
try
{ {
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
} }
finally
{
_texturesLock.ExitReadLock();
}
} }
if (_overlapInfo.Length != _textureOverlaps.Length) if (_overlapInfo.Length != _textureOverlaps.Length)
@@ -1068,16 +1025,10 @@ namespace Ryujinx.Graphics.Gpu.Image
_cache.Add(texture); _cache.Add(texture);
} }
_texturesLock.EnterWriteLock(); lock (_textures)
try
{ {
_textures.Add(texture); _textures.Add(texture);
} }
finally
{
_texturesLock.ExitWriteLock();
}
if (partiallyMapped) if (partiallyMapped)
{ {
@@ -1140,19 +1091,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return null; return null;
} }
int addressMatches; int addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps);
_texturesLock.EnterReadLock();
try
{
addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps);
}
finally
{
_texturesLock.ExitReadLock();
}
Texture textureMatch = null; Texture textureMatch = null;
for (int i = 0; i < addressMatches; i++) for (int i = 0; i < addressMatches; i++)
@@ -1293,16 +1232,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="texture">The texture to be removed</param> /// <param name="texture">The texture to be removed</param>
public void RemoveTextureFromCache(Texture texture) public void RemoveTextureFromCache(Texture texture)
{ {
_texturesLock.EnterWriteLock(); lock (_textures)
try
{ {
_textures.Remove(texture); _textures.Remove(texture);
} }
finally
{
_texturesLock.ExitWriteLock();
}
lock (_partiallyMappedTextures) lock (_partiallyMappedTextures)
{ {
@@ -1391,19 +1324,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
_texturesLock.EnterReadLock(); lock (_textures)
try
{ {
foreach (Texture texture in _textures) foreach (Texture texture in _textures)
{ {
texture.Dispose(); texture.Dispose();
} }
} }
finally
{
_texturesLock.ExitReadLock();
}
} }
} }
} }

View File

@@ -88,9 +88,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private MultiRange TextureRange => Storage.Range; private MultiRange TextureRange => Storage.Range;
/// <summary> /// <summary>
/// The views array from the storage texture. /// The views list from the storage texture.
/// </summary> /// </summary>
private Texture[] _views; private List<Texture> _views;
private TextureGroupHandle[] _handles; private TextureGroupHandle[] _handles;
private bool[] _loadNeeded; private bool[] _loadNeeded;
@@ -645,7 +645,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
_flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.HostMemory); _flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.FlushPersistent);
_flushBufferImported = false; _flushBufferImported = false;
} }
@@ -1074,7 +1074,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public void UpdateViews(List<Texture> views, Texture texture) public void UpdateViews(List<Texture> views, Texture texture)
{ {
// This is saved to calculate overlapping views for each handle. // This is saved to calculate overlapping views for each handle.
_views = views.ToArray(); _views = views;
bool layerViews = _hasLayerViews; bool layerViews = _hasLayerViews;
bool mipViews = _hasMipViews; bool mipViews = _hasMipViews;
@@ -1136,13 +1136,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Removes a view from the group, removing it from all overlap lists. /// Removes a view from the group, removing it from all overlap lists.
/// </summary> /// </summary>
/// <param name="views">The views list of the storage texture</param>
/// <param name="view">View to remove from the group</param> /// <param name="view">View to remove from the group</param>
public void RemoveView(List<Texture> views, Texture view) public void RemoveView(Texture view)
{ {
// This is saved to calculate overlapping views for each handle.
_views = views.ToArray();
int offset = FindOffset(view); int offset = FindOffset(view);
foreach (TextureGroupHandle handle in _handles) foreach (TextureGroupHandle handle in _handles)
@@ -1609,11 +1605,9 @@ namespace Ryujinx.Graphics.Gpu.Image
Storage.SignalModifiedDirty(); Storage.SignalModifiedDirty();
Texture[] views = _views; if (_views != null)
if (views != null)
{ {
foreach (Texture texture in views) foreach (Texture texture in _views)
{ {
texture.SignalModifiedDirty(); texture.SignalModifiedDirty();
} }

View File

@@ -121,7 +121,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureGroupHandle(TextureGroup group, public TextureGroupHandle(TextureGroup group,
int offset, int offset,
ulong size, ulong size,
IEnumerable<Texture> views, List<Texture> views,
int firstLayer, int firstLayer,
int firstLevel, int firstLevel,
int baseSlice, int baseSlice,
@@ -201,8 +201,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Calculate a list of which views overlap this handle. /// Calculate a list of which views overlap this handle.
/// </summary> /// </summary>
/// <param name="group">The parent texture group, used to find a view's base CPU VA offset</param> /// <param name="group">The parent texture group, used to find a view's base CPU VA offset</param>
/// <param name="views">The views to search for overlaps</param> /// <param name="views">The list of views to search for overlaps</param>
public void RecalculateOverlaps(TextureGroup group, IEnumerable<Texture> views) public void RecalculateOverlaps(TextureGroup group, List<Texture> views)
{ {
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
lock (Overlaps) lock (Overlaps)

View File

@@ -10,8 +10,6 @@ using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
{ {
delegate void BufferFlushAction(ulong address, ulong size, ulong syncNumber);
/// <summary> /// <summary>
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others. /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
/// </summary> /// </summary>
@@ -25,7 +23,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Host buffer handle. /// Host buffer handle.
/// </summary> /// </summary>
public BufferHandle Handle { get; private set; } public BufferHandle Handle { get; }
/// <summary> /// <summary>
/// Start address of the buffer in guest memory. /// Start address of the buffer in guest memory.
@@ -62,17 +60,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </remarks> /// </remarks>
private BufferModifiedRangeList _modifiedRanges = null; private BufferModifiedRangeList _modifiedRanges = null;
/// <summary>
/// A structure that is used to flush buffer data back to a host mapped buffer for cached readback.
/// Only used if the buffer data is explicitly owned by device local memory.
/// </summary>
private BufferPreFlush _preFlush = null;
/// <summary>
/// Usage tracking state that determines what type of backing the buffer should use.
/// </summary>
public BufferBackingState BackingState;
private readonly MultiRegionHandle _memoryTrackingGranular; private readonly MultiRegionHandle _memoryTrackingGranular;
private readonly RegionHandle _memoryTracking; private readonly RegionHandle _memoryTracking;
@@ -100,7 +87,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="physicalMemory">Physical memory where the buffer is mapped</param> /// <param name="physicalMemory">Physical memory where the buffer is mapped</param>
/// <param name="address">Start address of the buffer</param> /// <param name="address">Start address of the buffer</param>
/// <param name="size">Size of the buffer in bytes</param> /// <param name="size">Size of the buffer in bytes</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param> /// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param> /// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param>
public Buffer( public Buffer(
@@ -108,7 +94,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
PhysicalMemory physicalMemory, PhysicalMemory physicalMemory,
ulong address, ulong address,
ulong size, ulong size,
BufferStage stage,
bool sparseCompatible, bool sparseCompatible,
IEnumerable<Buffer> baseBuffers = null) IEnumerable<Buffer> baseBuffers = null)
{ {
@@ -118,11 +103,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
Size = size; Size = size;
SparseCompatible = sparseCompatible; SparseCompatible = sparseCompatible;
BackingState = new BufferBackingState(_context, this, stage, baseBuffers); BufferAccess access = sparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default;
BufferAccess access = BackingState.SwitchAccess(this); Handle = context.Renderer.CreateBuffer((int)size, access, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
Handle = context.Renderer.CreateBuffer((int)size, access);
_useGranular = size > GranularBufferThreshold; _useGranular = size > GranularBufferThreshold;
@@ -178,29 +161,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
_virtualDependenciesLock = new ReaderWriterLockSlim(); _virtualDependenciesLock = new ReaderWriterLockSlim();
} }
/// <summary>
/// Recreates the backing buffer based on the desired access type
/// reported by the backing state struct.
/// </summary>
private void ChangeBacking()
{
BufferAccess access = BackingState.SwitchAccess(this);
BufferHandle newHandle = _context.Renderer.CreateBuffer((int)Size, access);
_context.Renderer.Pipeline.CopyBuffer(Handle, newHandle, 0, 0, (int)Size);
_modifiedRanges?.SelfMigration();
// If swtiching from device local to host mapped, pre-flushing data no longer makes sense.
// This is set to null and disposed when the migration fully completes.
_preFlush = null;
Handle = newHandle;
_physicalMemory.BufferCache.BufferBackingChanged(this);
}
/// <summary> /// <summary>
/// Gets a sub-range from the buffer, from a start address til a page boundary after the given size. /// Gets a sub-range from the buffer, from a start address til a page boundary after the given size.
/// </summary> /// </summary>
@@ -286,7 +246,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
BackingState.RecordSet();
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size)); _context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
CopyToDependantVirtualBuffers(); CopyToDependantVirtualBuffers();
} }
@@ -324,35 +283,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush); _modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush);
} }
/// <summary>
/// Checks if a backing change is deemed necessary from the given usage.
/// If it is, queues a backing change to happen on the next sync action.
/// </summary>
/// <param name="stage">Buffer stage that can change backing type</param>
private void TryQueueBackingChange(BufferStage stage)
{
if (BackingState.ShouldChangeBacking(stage))
{
if (!_syncActionRegistered)
{
_context.RegisterSyncAction(this);
_syncActionRegistered = true;
}
}
}
/// <summary> /// <summary>
/// Signal that the given region of the buffer has been modified. /// Signal that the given region of the buffer has been modified.
/// </summary> /// </summary>
/// <param name="address">The start address of the modified region</param> /// <param name="address">The start address of the modified region</param>
/// <param name="size">The size of the modified region</param> /// <param name="size">The size of the modified region</param>
/// <param name="stage">Buffer stage that triggered the modification</param> public void SignalModified(ulong address, ulong size)
public void SignalModified(ulong address, ulong size, BufferStage stage)
{ {
EnsureRangeList(); EnsureRangeList();
TryQueueBackingChange(stage);
_modifiedRanges.SignalModified(address, size); _modifiedRanges.SignalModified(address, size);
if (!_syncActionRegistered) if (!_syncActionRegistered)
@@ -372,37 +311,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.Clear(address, size); _modifiedRanges?.Clear(address, size);
} }
/// <summary>
/// Action to be performed immediately before sync is created.
/// This will copy any buffer ranges designated for pre-flushing.
/// </summary>
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
public void SyncPreAction(bool syncpoint)
{
if (_referenceCount == 0)
{
return;
}
if (BackingState.ShouldChangeBacking())
{
ChangeBacking();
}
if (BackingState.IsDeviceLocal)
{
_preFlush ??= new BufferPreFlush(_context, this, FlushImpl);
if (_preFlush.ShouldCopy)
{
_modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, (address, size) =>
{
_preFlush.CopyModified(address, size);
});
}
}
}
/// <summary> /// <summary>
/// Action to be performed when a syncpoint is reached after modification. /// Action to be performed when a syncpoint is reached after modification.
/// This will register read/write tracking to flush the buffer from GPU when its memory is used. /// This will register read/write tracking to flush the buffer from GPU when its memory is used.
@@ -558,8 +466,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="mSize">Size of the modified region</param> /// <param name="mSize">Size of the modified region</param>
private void LoadRegion(ulong mAddress, ulong mSize) private void LoadRegion(ulong mAddress, ulong mSize)
{ {
BackingState.RecordSet();
int offset = (int)(mAddress - Address); int offset = (int)(mAddress - Address);
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize)); _context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
@@ -633,84 +539,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Flushes a range of the buffer. /// Flushes a range of the buffer.
/// This writes the range data back into guest memory. /// This writes the range data back into guest memory.
/// </summary> /// </summary>
/// <param name="handle">Buffer handle to flush data from</param>
/// <param name="address">Start address of the range</param> /// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
private void FlushImpl(BufferHandle handle, ulong address, ulong size) public void Flush(ulong address, ulong size)
{ {
int offset = (int)(address - Address); int offset = (int)(address - Address);
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(handle, offset, (int)size); using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers. // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size)); _physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
} }
/// <summary>
/// Flushes a range of the buffer.
/// This writes the range data back into guest memory.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
private void FlushImpl(ulong address, ulong size)
{
FlushImpl(Handle, address, size);
}
/// <summary>
/// Flushes a range of the buffer from the most optimal source.
/// This writes the range data back into guest memory.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
/// <param name="syncNumber">Sync number waited for before flushing the data</param>
public void Flush(ulong address, ulong size, ulong syncNumber)
{
BackingState.RecordFlush();
BufferPreFlush preFlush = _preFlush;
if (preFlush != null)
{
preFlush.FlushWithAction(address, size, syncNumber);
}
else
{
FlushImpl(address, size);
}
}
/// <summary>
/// Gets an action that disposes the backing buffer using its current handle.
/// Useful for deleting an old copy of the buffer after the handle changes.
/// </summary>
/// <returns>An action that flushes data from the specified range, using the buffer handle at the time the method is generated</returns>
public Action GetSnapshotDisposeAction()
{
BufferHandle handle = Handle;
BufferPreFlush preFlush = _preFlush;
return () =>
{
_context.Renderer.DeleteBuffer(handle);
preFlush?.Dispose();
};
}
/// <summary>
/// Gets an action that flushes a range of the buffer using its current handle.
/// Useful for flushing data from old copies of the buffer after the handle changes.
/// </summary>
/// <returns>An action that flushes data from the specified range, using the buffer handle at the time the method is generated</returns>
public BufferFlushAction GetSnapshotFlushAction()
{
BufferHandle handle = Handle;
return (ulong address, ulong size, ulong _) =>
{
FlushImpl(handle, address, size);
};
}
/// <summary> /// <summary>
/// Align a given address and size region to page boundaries. /// Align a given address and size region to page boundaries.
/// </summary> /// </summary>
@@ -1017,8 +857,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.Clear(); _modifiedRanges?.Clear();
_context.Renderer.DeleteBuffer(Handle); _context.Renderer.DeleteBuffer(Handle);
_preFlush?.Dispose();
_preFlush = null;
UnmappedSequence++; UnmappedSequence++;
} }

View File

@@ -1,294 +0,0 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// Type of backing memory.
/// In ascending order of priority when merging multiple buffer backing states.
/// </summary>
internal enum BufferBackingType
{
HostMemory,
DeviceMemory,
DeviceMemoryWithFlush
}
/// <summary>
/// Keeps track of buffer usage to decide what memory heap that buffer memory is placed on.
/// Dedicated GPUs prefer certain types of resources to be device local,
/// and if we need data to be read back, we might prefer that they're in host memory.
///
/// The measurements recorded here compare to a set of heruristics (thresholds and conditions)
/// that appear to produce good performance in most software.
/// </summary>
internal struct BufferBackingState
{
private const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
private const int SetCountThreshold = 100;
private const int WriteCountThreshold = 50;
private const int FlushCountThreshold = 5;
private const int DeviceLocalForceExpiry = 100;
public readonly bool IsDeviceLocal => _activeType != BufferBackingType.HostMemory;
private readonly SystemMemoryType _systemMemoryType;
private BufferBackingType _activeType;
private BufferBackingType _desiredType;
private bool _canSwap;
private int _setCount;
private int _writeCount;
private int _flushCount;
private int _flushTemp;
private int _lastFlushWrite;
private int _deviceLocalForceCount;
private readonly int _size;
/// <summary>
/// Initialize the buffer backing state for a given parent buffer.
/// </summary>
/// <param name="context">GPU context</param>
/// <param name="parent">Parent buffer</param>
/// <param name="stage">Initial buffer stage</param>
/// <param name="baseBuffers">Buffers to inherit state from</param>
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, IEnumerable<Buffer> baseBuffers = null)
{
_size = (int)parent.Size;
_systemMemoryType = context.Capabilities.MemoryType;
// Backend managed is always auto, unified memory is always host.
_desiredType = BufferBackingType.HostMemory;
_canSwap = _systemMemoryType != SystemMemoryType.BackendManaged && _systemMemoryType != SystemMemoryType.UnifiedMemory;
if (_canSwap)
{
// Might want to start certain buffers as being device local,
// and the usage might also lock those buffers into being device local.
BufferStage storageFlags = stage & BufferStage.StorageMask;
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers == null)
{
_desiredType = BufferBackingType.DeviceMemory;
}
if (storageFlags != 0)
{
// Storage buffer bindings may require special treatment.
var rawStage = stage & BufferStage.StageMask;
if (rawStage == BufferStage.Fragment)
{
// Fragment read should start device local.
_desiredType = BufferBackingType.DeviceMemory;
if (storageFlags != BufferStage.StorageRead)
{
// Fragment write should stay device local until the use doesn't happen anymore.
_deviceLocalForceCount = DeviceLocalForceExpiry;
}
}
// TODO: Might be nice to force atomic access to be device local for any stage.
}
if (baseBuffers != null)
{
foreach (Buffer buffer in baseBuffers)
{
CombineState(buffer.BackingState);
}
}
}
}
/// <summary>
/// Combine buffer backing types, selecting the one with highest priority.
/// </summary>
/// <param name="left">First buffer backing type</param>
/// <param name="right">Second buffer backing type</param>
/// <returns>Combined buffer backing type</returns>
private static BufferBackingType CombineTypes(BufferBackingType left, BufferBackingType right)
{
return (BufferBackingType)Math.Max((int)left, (int)right);
}
/// <summary>
/// Combine the state from the given buffer backing state with this one,
/// so that the state isn't lost when migrating buffers.
/// </summary>
/// <param name="oldState">Buffer state to combine into this state</param>
private void CombineState(BufferBackingState oldState)
{
_setCount += oldState._setCount;
_writeCount += oldState._writeCount;
_flushCount += oldState._flushCount;
_flushTemp += oldState._flushTemp;
_lastFlushWrite = -1;
_deviceLocalForceCount = Math.Max(_deviceLocalForceCount, oldState._deviceLocalForceCount);
_canSwap &= oldState._canSwap;
_desiredType = CombineTypes(_desiredType, oldState._desiredType);
}
/// <summary>
/// Get the buffer access for the desired backing type, and record that type as now being active.
/// </summary>
/// <param name="parent">Parent buffer</param>
/// <returns>Buffer access</returns>
public BufferAccess SwitchAccess(Buffer parent)
{
BufferAccess access = parent.SparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default;
bool isBackendManaged = _systemMemoryType == SystemMemoryType.BackendManaged;
if (!isBackendManaged)
{
switch (_desiredType)
{
case BufferBackingType.HostMemory:
access |= BufferAccess.HostMemory;
break;
case BufferBackingType.DeviceMemory:
access |= BufferAccess.DeviceMemory;
break;
case BufferBackingType.DeviceMemoryWithFlush:
access |= BufferAccess.DeviceMemoryMapped;
break;
}
}
_activeType = _desiredType;
return access;
}
/// <summary>
/// Record when data has been uploaded to the buffer.
/// </summary>
public void RecordSet()
{
_setCount++;
ConsiderUseCounts();
}
/// <summary>
/// Record when data has been flushed from the buffer.
/// </summary>
public void RecordFlush()
{
if (_lastFlushWrite != _writeCount)
{
// If it's on the same page as the last flush, ignore it.
_lastFlushWrite = _writeCount;
_flushCount++;
}
}
/// <summary>
/// Determine if the buffer backing should be changed.
/// </summary>
/// <returns>True if the desired backing type is different from the current type</returns>
public readonly bool ShouldChangeBacking()
{
return _desiredType != _activeType;
}
/// <summary>
/// Determine if the buffer backing should be changed, considering a new use with the given buffer stage.
/// </summary>
/// <param name="stage">Buffer stage for the use</param>
/// <returns>True if the desired backing type is different from the current type</returns>
public bool ShouldChangeBacking(BufferStage stage)
{
if (!_canSwap)
{
return false;
}
BufferStage storageFlags = stage & BufferStage.StorageMask;
if (storageFlags != 0)
{
if (storageFlags != BufferStage.StorageRead)
{
// Storage write.
_writeCount++;
var rawStage = stage & BufferStage.StageMask;
if (rawStage == BufferStage.Fragment)
{
// Switch to device memory, swap back only if this use disappears.
_desiredType = CombineTypes(_desiredType, BufferBackingType.DeviceMemory);
_deviceLocalForceCount = DeviceLocalForceExpiry;
// TODO: Might be nice to force atomic access to be device local for any stage.
}
}
ConsiderUseCounts();
}
return _desiredType != _activeType;
}
/// <summary>
/// Evaluate the current counts to determine what the buffer's desired backing type is.
/// This method depends on heuristics devised by testing a variety of software.
/// </summary>
private void ConsiderUseCounts()
{
if (_canSwap)
{
if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
{
if (_deviceLocalForceCount > 0 && --_deviceLocalForceCount != 0)
{
// Some buffer usage demanded that the buffer stay device local.
// The desired type was selected when this counter was set.
}
else if (_flushCount > 0 || _flushTemp-- > 0)
{
// Buffers that flush should ideally be mapped in host address space for easy copies.
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
_desiredType = _size > DeviceLocalSizeThreshold ? BufferBackingType.DeviceMemoryWithFlush : BufferBackingType.HostMemory;
}
else if (_writeCount >= WriteCountThreshold)
{
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
_desiredType = BufferBackingType.DeviceMemory;
}
else if (_setCount > SetCountThreshold)
{
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
_desiredType = BufferBackingType.HostMemory;
}
// It's harder for a buffer that is flushed to revert to another type of mapping.
if (_flushCount > 0)
{
_flushTemp = 1000;
}
_lastFlushWrite = -1;
_flushCount = 0;
_writeCount = 0;
_setCount = 0;
}
}
}
}
}

View File

@@ -107,9 +107,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param> /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param> /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <returns>Contiguous physical range of the buffer, after address translation</returns> /// <returns>Contiguous physical range of the buffer, after address translation</returns>
public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage) public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size)
{ {
if (gpuVa == 0) if (gpuVa == 0)
{ {
@@ -120,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (address != MemoryManager.PteUnmapped) if (address != MemoryManager.PteUnmapped)
{ {
CreateBuffer(address, size, stage); CreateBuffer(address, size);
} }
return new MultiRange(address, size); return new MultiRange(address, size);
@@ -133,9 +132,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param> /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param> /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <returns>Physical ranges of the buffer, after address translation</returns> /// <returns>Physical ranges of the buffer, after address translation</returns>
public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage) public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size)
{ {
if (gpuVa == 0) if (gpuVa == 0)
{ {
@@ -151,7 +149,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
return range; return range;
} }
CreateBuffer(range, stage); CreateBuffer(range);
return range; return range;
} }
@@ -163,9 +161,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param> /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param> /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <returns>Physical ranges of the buffer, after address translation</returns> /// <returns>Physical ranges of the buffer, after address translation</returns>
public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage) public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size)
{ {
if (gpuVa == 0) if (gpuVa == 0)
{ {
@@ -189,11 +186,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (range.Count > 1) if (range.Count > 1)
{ {
CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize); CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
} }
else else
{ {
CreateBuffer(subRange.Address, subRange.Size, stage); CreateBuffer(subRange.Address, subRange.Size);
} }
} }
} }
@@ -206,12 +203,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// This can be used to ensure the existance of a buffer. /// This can be used to ensure the existance of a buffer.
/// </summary> /// </summary>
/// <param name="range">Physical ranges of memory where the buffer data is located</param> /// <param name="range">Physical ranges of memory where the buffer data is located</param>
/// <param name="stage">The type of usage that created the buffer</param> public void CreateBuffer(MultiRange range)
public void CreateBuffer(MultiRange range, BufferStage stage)
{ {
if (range.Count > 1) if (range.Count > 1)
{ {
CreateMultiRangeBuffer(range, stage); CreateMultiRangeBuffer(range);
} }
else else
{ {
@@ -219,7 +215,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped) if (subRange.Address != MemoryManager.PteUnmapped)
{ {
CreateBuffer(subRange.Address, subRange.Size, stage); CreateBuffer(subRange.Address, subRange.Size);
} }
} }
} }
@@ -230,8 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in memory</param> /// <param name="address">Address of the buffer in memory</param>
/// <param name="size">Size of the buffer in bytes</param> /// <param name="size">Size of the buffer in bytes</param>
/// <param name="stage">The type of usage that created the buffer</param> public void CreateBuffer(ulong address, ulong size)
public void CreateBuffer(ulong address, ulong size, BufferStage stage)
{ {
ulong endAddress = address + size; ulong endAddress = address + size;
@@ -244,7 +239,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
alignedEndAddress += BufferAlignmentSize; alignedEndAddress += BufferAlignmentSize;
} }
CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage); CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress);
} }
/// <summary> /// <summary>
@@ -253,9 +248,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in memory</param> /// <param name="address">Address of the buffer in memory</param>
/// <param name="size">Size of the buffer in bytes</param> /// <param name="size">Size of the buffer in bytes</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="alignment">Alignment of the start address of the buffer in bytes</param> /// <param name="alignment">Alignment of the start address of the buffer in bytes</param>
public void CreateBuffer(ulong address, ulong size, BufferStage stage, ulong alignment) public void CreateBuffer(ulong address, ulong size, ulong alignment)
{ {
ulong alignmentMask = alignment - 1; ulong alignmentMask = alignment - 1;
ulong pageAlignmentMask = BufferAlignmentMask; ulong pageAlignmentMask = BufferAlignmentMask;
@@ -270,7 +264,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
alignedEndAddress += pageAlignmentMask; alignedEndAddress += pageAlignmentMask;
} }
CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage, alignment); CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, alignment);
} }
/// <summary> /// <summary>
@@ -278,8 +272,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// if it does not exist yet. /// if it does not exist yet.
/// </summary> /// </summary>
/// <param name="range">Physical ranges of memory</param> /// <param name="range">Physical ranges of memory</param>
/// <param name="stage">The type of usage that created the buffer</param> private void CreateMultiRangeBuffer(MultiRange range)
private void CreateMultiRangeBuffer(MultiRange range, BufferStage stage)
{ {
// Ensure all non-contiguous buffer we might use are sparse aligned. // Ensure all non-contiguous buffer we might use are sparse aligned.
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
@@ -288,7 +281,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped) if (subRange.Address != MemoryManager.PteUnmapped)
{ {
CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize); CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
} }
} }
@@ -438,9 +431,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
result.EndGpuAddress < gpuVa + size || result.EndGpuAddress < gpuVa + size ||
result.UnmappedSequence != result.Buffer.UnmappedSequence) result.UnmappedSequence != result.Buffer.UnmappedSequence)
{ {
MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size, BufferStage.Internal); MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size);
ulong address = range.GetSubRange(0).Address; ulong address = range.GetSubRange(0).Address;
result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size, BufferStage.Internal)); result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size));
_dirtyCache[gpuVa] = result; _dirtyCache[gpuVa] = result;
} }
@@ -473,9 +466,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
result.EndGpuAddress < alignedEndGpuVa || result.EndGpuAddress < alignedEndGpuVa ||
result.UnmappedSequence != result.Buffer.UnmappedSequence) result.UnmappedSequence != result.Buffer.UnmappedSequence)
{ {
MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size, BufferStage.None); MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size);
ulong address = range.GetSubRange(0).Address; ulong address = range.GetSubRange(0).Address;
result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size, BufferStage.None)); result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size));
_modifiedCache[alignedGpuVa] = result; _modifiedCache[alignedGpuVa] = result;
} }
@@ -492,8 +485,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in guest memory</param> /// <param name="address">Address of the buffer in guest memory</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param> private void CreateBufferAligned(ulong address, ulong size)
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{ {
Buffer[] overlaps = _bufferOverlaps; Buffer[] overlaps = _bufferOverlaps;
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
@@ -554,13 +546,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address; ulong newSize = endAddress - address;
CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps, overlapsCount); CreateBufferAligned(address, newSize, anySparseCompatible, overlaps, overlapsCount);
} }
} }
else else
{ {
// No overlap, just create a new buffer. // No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false); Buffer buffer = new(_context, _physicalMemory, address, size, sparseCompatible: false);
lock (_buffers) lock (_buffers)
{ {
@@ -578,9 +570,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in guest memory</param> /// <param name="address">Address of the buffer in guest memory</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="alignment">Alignment of the start address of the buffer</param> /// <param name="alignment">Alignment of the start address of the buffer</param>
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment) private void CreateBufferAligned(ulong address, ulong size, ulong alignment)
{ {
Buffer[] overlaps = _bufferOverlaps; Buffer[] overlaps = _bufferOverlaps;
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
@@ -633,13 +624,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address; ulong newSize = endAddress - address;
CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps, overlapsCount); CreateBufferAligned(address, newSize, sparseAligned, overlaps, overlapsCount);
} }
} }
else else
{ {
// No overlap, just create a new buffer. // No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned); Buffer buffer = new(_context, _physicalMemory, address, size, sparseAligned);
lock (_buffers) lock (_buffers)
{ {
@@ -657,13 +648,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in guest memory</param> /// <param name="address">Address of the buffer in guest memory</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param> /// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="overlaps">Buffers overlapping the range</param> /// <param name="overlaps">Buffers overlapping the range</param>
/// <param name="overlapsCount">Total of overlaps</param> /// <param name="overlapsCount">Total of overlaps</param>
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps, int overlapsCount) private void CreateBufferAligned(ulong address, ulong size, bool sparseCompatible, Buffer[] overlaps, int overlapsCount)
{ {
Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps.Take(overlapsCount)); Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, sparseCompatible, overlaps.Take(overlapsCount));
lock (_buffers) lock (_buffers)
{ {
@@ -714,7 +704,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int index = 0; index < overlapCount; index++) for (int index = 0; index < overlapCount; index++)
{ {
CreateMultiRangeBuffer(overlaps[index].Range, BufferStage.None); CreateMultiRangeBuffer(overlaps[index].Range);
} }
} }
@@ -741,8 +731,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the copy</param> /// <param name="size">Size in bytes of the copy</param>
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size) public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
{ {
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy); MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size);
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy); MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size);
if (srcRange.Count == 1 && dstRange.Count == 1) if (srcRange.Count == 1 && dstRange.Count == 1)
{ {
@@ -798,8 +788,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the copy</param> /// <param name="size">Size in bytes of the copy</param>
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size) private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
{ {
Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy); Buffer srcBuffer = GetBuffer(srcAddress, size);
Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy); Buffer dstBuffer = GetBuffer(dstAddress, size);
int srcOffset = (int)(srcAddress - srcBuffer.Address); int srcOffset = (int)(srcAddress - srcBuffer.Address);
int dstOffset = (int)(dstAddress - dstBuffer.Address); int dstOffset = (int)(dstAddress - dstBuffer.Address);
@@ -813,7 +803,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (srcBuffer.IsModified(srcAddress, size)) if (srcBuffer.IsModified(srcAddress, size))
{ {
dstBuffer.SignalModified(dstAddress, size, BufferStage.Copy); dstBuffer.SignalModified(dstAddress, size);
} }
else else
{ {
@@ -838,12 +828,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="value">Value to be written into the buffer</param> /// <param name="value">Value to be written into the buffer</param>
public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value) public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
{ {
MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size, BufferStage.Copy); MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size);
for (int index = 0; index < range.Count; index++) for (int index = 0; index < range.Count; index++)
{ {
MemoryRange subRange = range.GetSubRange(index); MemoryRange subRange = range.GetSubRange(index);
Buffer buffer = GetBuffer(subRange.Address, subRange.Size, BufferStage.Copy); Buffer buffer = GetBuffer(subRange.Address, subRange.Size);
int offset = (int)(subRange.Address - buffer.Address); int offset = (int)(subRange.Address - buffer.Address);
@@ -859,19 +849,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary. /// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary.
/// </summary> /// </summary>
/// <param name="range">Physical regions of memory where the buffer is mapped</param> /// <param name="range">Physical regions of memory where the buffer is mapped</param>
/// <param name="stage">Buffer stage that triggered the access</param>
/// <param name="write">Whether the buffer will be written to by this use</param> /// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer sub-range starting at the given memory address</returns> /// <returns>The buffer sub-range starting at the given memory address</returns>
public BufferRange GetBufferRangeAligned(MultiRange range, BufferStage stage, bool write = false) public BufferRange GetBufferRangeAligned(MultiRange range, bool write = false)
{ {
if (range.Count > 1) if (range.Count > 1)
{ {
return GetBuffer(range, stage, write).GetRange(range); return GetBuffer(range, write).GetRange(range);
} }
else else
{ {
MemoryRange subRange = range.GetSubRange(0); MemoryRange subRange = range.GetSubRange(0);
return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRangeAligned(subRange.Address, subRange.Size, write); return GetBuffer(subRange.Address, subRange.Size, write).GetRangeAligned(subRange.Address, subRange.Size, write);
} }
} }
@@ -879,19 +868,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Gets a buffer sub-range for a given memory range. /// Gets a buffer sub-range for a given memory range.
/// </summary> /// </summary>
/// <param name="range">Physical regions of memory where the buffer is mapped</param> /// <param name="range">Physical regions of memory where the buffer is mapped</param>
/// <param name="stage">Buffer stage that triggered the access</param>
/// <param name="write">Whether the buffer will be written to by this use</param> /// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer sub-range for the given range</returns> /// <returns>The buffer sub-range for the given range</returns>
public BufferRange GetBufferRange(MultiRange range, BufferStage stage, bool write = false) public BufferRange GetBufferRange(MultiRange range, bool write = false)
{ {
if (range.Count > 1) if (range.Count > 1)
{ {
return GetBuffer(range, stage, write).GetRange(range); return GetBuffer(range, write).GetRange(range);
} }
else else
{ {
MemoryRange subRange = range.GetSubRange(0); MemoryRange subRange = range.GetSubRange(0);
return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRange(subRange.Address, subRange.Size, write); return GetBuffer(subRange.Address, subRange.Size, write).GetRange(subRange.Address, subRange.Size, write);
} }
} }
@@ -900,10 +888,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// A buffer overlapping with the specified range is assumed to already exist on the cache. /// A buffer overlapping with the specified range is assumed to already exist on the cache.
/// </summary> /// </summary>
/// <param name="range">Physical regions of memory where the buffer is mapped</param> /// <param name="range">Physical regions of memory where the buffer is mapped</param>
/// <param name="stage">Buffer stage that triggered the access</param>
/// <param name="write">Whether the buffer will be written to by this use</param> /// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer where the range is fully contained</returns> /// <returns>The buffer where the range is fully contained</returns>
private MultiRangeBuffer GetBuffer(MultiRange range, BufferStage stage, bool write = false) private MultiRangeBuffer GetBuffer(MultiRange range, bool write = false)
{ {
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
{ {
@@ -915,7 +902,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (write) if (write)
{ {
subBuffer.SignalModified(subRange.Address, subRange.Size, stage); subBuffer.SignalModified(subRange.Address, subRange.Size);
} }
} }
@@ -948,10 +935,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Start address of the memory range</param> /// <param name="address">Start address of the memory range</param>
/// <param name="size">Size in bytes of the memory range</param> /// <param name="size">Size in bytes of the memory range</param>
/// <param name="stage">Buffer stage that triggered the access</param>
/// <param name="write">Whether the buffer will be written to by this use</param> /// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer where the range is fully contained</returns> /// <returns>The buffer where the range is fully contained</returns>
private Buffer GetBuffer(ulong address, ulong size, BufferStage stage, bool write = false) private Buffer GetBuffer(ulong address, ulong size, bool write = false)
{ {
Buffer buffer; Buffer buffer;
@@ -964,7 +950,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (write) if (write)
{ {
buffer.SignalModified(address, size, stage); buffer.SignalModified(address, size);
} }
} }
else else
@@ -1018,18 +1004,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
/// <summary>
/// Signal that the given buffer's handle has changed,
/// forcing rebind and any overlapping multi-range buffers to be recreated.
/// </summary>
/// <param name="buffer">The buffer that has changed handle</param>
public void BufferBackingChanged(Buffer buffer)
{
NotifyBuffersModified?.Invoke();
RecreateMultiRangeBuffers(buffer.Address, buffer.Size);
}
/// <summary> /// <summary>
/// Prune any invalid entries from a quick access dictionary. /// Prune any invalid entries from a quick access dictionary.
/// </summary> /// </summary>

View File

@@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="type">Type of each index buffer element</param> /// <param name="type">Type of each index buffer element</param>
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type) public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
_indexBuffer.Range = range; _indexBuffer.Range = range;
_indexBuffer.Type = type; _indexBuffer.Type = type;
@@ -186,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param> /// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param>
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor) public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
_vertexBuffers[index].Range = range; _vertexBuffers[index].Range = range;
_vertexBuffers[index].Stride = stride; _vertexBuffers[index].Stride = stride;
@@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the transform feedback buffer</param> /// <param name="size">Size in bytes of the transform feedback buffer</param>
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size) public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
_transformFeedbackBuffers[index] = new BufferBounds(range); _transformFeedbackBuffers[index] = new BufferBounds(range);
_transformFeedbackBuffersDirty = true; _transformFeedbackBuffersDirty = true;
@@ -260,7 +260,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment); gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags)); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
_cpStorageBuffers.SetBounds(index, range, flags); _cpStorageBuffers.SetBounds(index, range, flags);
} }
@@ -284,7 +284,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment); gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags)); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
if (!buffers.Buffers[index].Range.Equals(range)) if (!buffers.Buffers[index].Range.Equals(range))
{ {
@@ -303,7 +303,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the storage buffer</param> /// <param name="size">Size in bytes of the storage buffer</param>
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size) public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
_cpUniformBuffers.SetBounds(index, range); _cpUniformBuffers.SetBounds(index, range);
} }
@@ -318,7 +318,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the storage buffer</param> /// <param name="size">Size in bytes of the storage buffer</param>
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size) public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage)); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
_gpUniformBuffers[stage].SetBounds(index, range); _gpUniformBuffers[stage].SetBounds(index, range);
_gpUniformBuffersDirty = true; _gpUniformBuffersDirty = true;
@@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferTextures) foreach (var binding in _bufferTextures)
{ {
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
var range = bufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore); var range = bufferCache.GetBufferRange(binding.Range, isStore);
binding.Texture.SetStorage(range); binding.Texture.SetStorage(range);
// The texture must be rebound to use the new storage if it was updated. // The texture must be rebound to use the new storage if it was updated.
@@ -526,7 +526,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferTextureArrays) foreach (var binding in _bufferTextureArrays)
{ {
var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None); var range = bufferCache.GetBufferRange(binding.Range);
binding.Texture.SetStorage(range); binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture; textureArray[0] = binding.Texture;
@@ -536,7 +536,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferImageArrays) foreach (var binding in _bufferImageArrays)
{ {
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore); var range = bufferCache.GetBufferRange(binding.Range, isStore);
binding.Texture.SetStorage(range); binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture; textureArray[0] = binding.Texture;
@@ -565,7 +565,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (!_indexBuffer.Range.IsUnmapped) if (!_indexBuffer.Range.IsUnmapped)
{ {
BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer); BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range);
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type); _context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
} }
@@ -597,7 +597,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue; continue;
} }
BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer); BufferRange buffer = bufferCache.GetBufferRange(vb.Range);
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor); vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
} }
@@ -637,7 +637,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue; continue;
} }
tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true); tfbs[index] = bufferCache.GetBufferRange(tfb.Range, write: true);
} }
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs); _context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
@@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset); _context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true)); buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, write: true));
} }
} }
@@ -751,7 +751,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{ {
ref var buffers = ref bindings[(int)stage - 1]; ref var buffers = ref bindings[(int)stage - 1];
BufferStage bufferStage = BufferStageUtils.FromShaderStage(stage);
for (int index = 0; index < buffers.Count; index++) for (int index = 0; index < buffers.Count; index++)
{ {
@@ -763,8 +762,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage var range = isStorage
? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite) ? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite)
: bufferCache.GetBufferRange(bounds.Range, bufferStage); : bufferCache.GetBufferRange(bounds.Range);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range); ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
} }
@@ -800,8 +799,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage var range = isStorage
? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite) ? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite)
: bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute); : bufferCache.GetBufferRange(bounds.Range);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range); ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
} }
@@ -876,7 +875,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Format format, Format format,
bool isImage) bool isImage)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage));
} }
@@ -884,7 +883,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings. /// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
/// </summary> /// </summary>
/// <param name="stage">Shader stage accessing the texture</param>
/// <param name="array">Texture array where the element will be inserted</param> /// <param name="array">Texture array where the element will be inserted</param>
/// <param name="texture">Buffer texture</param> /// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param> /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
@@ -892,7 +890,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="index">Index of the binding on the array</param> /// <param name="index">Index of the binding on the array</param>
/// <param name="format">Format of the buffer texture</param> /// <param name="format">Format of the buffer texture</param>
public void SetBufferTextureStorage( public void SetBufferTextureStorage(
ShaderStage stage,
ITextureArray array, ITextureArray array,
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
@@ -900,7 +897,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int index, int index,
Format format) Format format)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format)); _bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format));
} }
@@ -908,7 +905,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings. /// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
/// </summary> /// </summary>
/// <param name="stage">Shader stage accessing the texture</param>
/// <param name="array">Image array where the element will be inserted</param> /// <param name="array">Image array where the element will be inserted</param>
/// <param name="texture">Buffer texture</param> /// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param> /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
@@ -916,7 +912,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="index">Index of the binding on the array</param> /// <param name="index">Index of the binding on the array</param>
/// <param name="format">Format of the buffer texture</param> /// <param name="format">Format of the buffer texture</param>
public void SetBufferTextureStorage( public void SetBufferTextureStorage(
ShaderStage stage,
IImageArray array, IImageArray array,
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
@@ -924,7 +919,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int index, int index,
Format format) Format format)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format)); _bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format));
} }

View File

@@ -1,21 +1,37 @@
using System; using System;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
{ {
/// <summary> /// <summary>
/// A record of when buffer data was copied from multiple buffers to one migration target, /// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete.
/// along with the SyncNumber when the migration will be complete. /// Keeps the source buffer alive for data flushes until the migration is complete.
/// Keeps the source buffers alive for data flushes until the migration is complete.
/// All spans cover the full range of the "destination" buffer.
/// </summary> /// </summary>
internal class BufferMigration : IDisposable internal class BufferMigration : IDisposable
{ {
/// <summary> /// <summary>
/// Ranges from source buffers that were copied as part of this migration. /// The offset for the migrated region.
/// Ordered by increasing base address.
/// </summary> /// </summary>
public BufferMigrationSpan[] Spans { get; private set; } private readonly ulong _offset;
/// <summary>
/// The size for the migrated region.
/// </summary>
private readonly ulong _size;
/// <summary>
/// The buffer that was migrated from.
/// </summary>
private readonly Buffer _buffer;
/// <summary>
/// The source range action, to be called on overlap with an unreached sync number.
/// </summary>
private readonly Action<ulong, ulong> _sourceRangeAction;
/// <summary>
/// The source range list.
/// </summary>
private readonly BufferModifiedRangeList _source;
/// <summary> /// <summary>
/// The destination range list. This range list must be updated when flushing the source. /// The destination range list. This range list must be updated when flushing the source.
@@ -27,193 +43,55 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public readonly ulong SyncNumber; public readonly ulong SyncNumber;
/// <summary>
/// Number of active users there are traversing this migration's spans.
/// </summary>
private int _refCount;
/// <summary>
/// Create a new buffer migration.
/// </summary>
/// <param name="spans">Source spans for the migration</param>
/// <param name="destination">Destination buffer range list</param>
/// <param name="syncNumber">Sync number where this migration will be complete</param>
public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber)
{
Spans = spans;
Destination = destination;
SyncNumber = syncNumber;
}
/// <summary>
/// Add a span to the migration. Allocates a new array with the target size, and replaces it.
/// </summary>
/// <remarks>
/// The base address for the span is assumed to be higher than all other spans in the migration,
/// to keep the span array ordered.
/// </remarks>
public void AddSpanToEnd(BufferMigrationSpan span)
{
BufferMigrationSpan[] oldSpans = Spans;
BufferMigrationSpan[] newSpans = new BufferMigrationSpan[oldSpans.Length + 1];
oldSpans.CopyTo(newSpans, 0);
newSpans[oldSpans.Length] = span;
Spans = newSpans;
}
/// <summary>
/// Performs the given range action, or one from a migration that overlaps and has not synced yet.
/// </summary>
/// <param name="offset">The offset to pass to the action</param>
/// <param name="size">The size to pass to the action</param>
/// <param name="syncNumber">The sync number that has been reached</param>
/// <param name="rangeAction">The action to perform</param>
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
{
long syncDiff = (long)(syncNumber - SyncNumber);
if (syncDiff >= 0)
{
// The migration has completed. Run the parent action.
rangeAction(offset, size, syncNumber);
}
else
{
Interlocked.Increment(ref _refCount);
ulong prevAddress = offset;
ulong endAddress = offset + size;
foreach (BufferMigrationSpan span in Spans)
{
if (!span.Overlaps(offset, size))
{
continue;
}
if (span.Address > prevAddress)
{
// There's a gap between this span and the last (or the start address). Flush the range using the parent action.
rangeAction(prevAddress, span.Address - prevAddress, syncNumber);
}
span.RangeActionWithMigration(offset, size, syncNumber);
prevAddress = span.Address + span.Size;
}
if (endAddress > prevAddress)
{
// There's a gap at the end of the range with no migration. Flush the range using the parent action.
rangeAction(prevAddress, endAddress - prevAddress, syncNumber);
}
Interlocked.Decrement(ref _refCount);
}
}
/// <summary>
/// Dispose the buffer migration. This removes the reference from the destination range list,
/// and runs all the dispose buffers for the migration spans. (typically disposes the source buffer)
/// </summary>
public void Dispose()
{
while (Volatile.Read(ref _refCount) > 0)
{
// Coming into this method, the sync for the migration will be met, so nothing can increment the ref count.
// However, an existing traversal of the spans for data flush could still be in progress.
// Spin if this is ever the case, so they don't get disposed before the operation is complete.
}
Destination.RemoveMigration(this);
foreach (BufferMigrationSpan span in Spans)
{
span.Dispose();
}
}
}
/// <summary>
/// A record of when buffer data was copied from one buffer to another, for a specific range in a source buffer.
/// Keeps the source buffer alive for data flushes until the migration is complete.
/// </summary>
internal readonly struct BufferMigrationSpan : IDisposable
{
/// <summary>
/// The offset for the migrated region.
/// </summary>
public readonly ulong Address;
/// <summary>
/// The size for the migrated region.
/// </summary>
public readonly ulong Size;
/// <summary>
/// The action to perform when the migration isn't needed anymore.
/// </summary>
private readonly Action _disposeAction;
/// <summary>
/// The source range action, to be called on overlap with an unreached sync number.
/// </summary>
private readonly BufferFlushAction _sourceRangeAction;
/// <summary>
/// Optional migration for the source data. Can chain together if many migrations happen in a short time.
/// If this is null, then _sourceRangeAction will always provide up to date data.
/// </summary>
private readonly BufferMigration _source;
/// <summary> /// <summary>
/// Creates a record for a buffer migration. /// Creates a record for a buffer migration.
/// </summary> /// </summary>
/// <param name="buffer">The source buffer for this migration</param> /// <param name="buffer">The source buffer for this migration</param>
/// <param name="disposeAction">The action to perform when the migration isn't needed anymore</param>
/// <param name="sourceRangeAction">The flush action for the source buffer</param> /// <param name="sourceRangeAction">The flush action for the source buffer</param>
/// <param name="source">Pending migration for the source buffer</param> /// <param name="source">The modified range list for the source buffer</param>
public BufferMigrationSpan( /// <param name="dest">The modified range list for the destination buffer</param>
/// <param name="syncNumber">The sync number for when the migration is complete</param>
public BufferMigration(
Buffer buffer, Buffer buffer,
Action disposeAction, Action<ulong, ulong> sourceRangeAction,
BufferFlushAction sourceRangeAction, BufferModifiedRangeList source,
BufferMigration source) BufferModifiedRangeList dest,
ulong syncNumber)
{ {
Address = buffer.Address; _offset = buffer.Address;
Size = buffer.Size; _size = buffer.Size;
_disposeAction = disposeAction; _buffer = buffer;
_sourceRangeAction = sourceRangeAction; _sourceRangeAction = sourceRangeAction;
_source = source; _source = source;
Destination = dest;
SyncNumber = syncNumber;
} }
/// <summary>
/// Creates a record for a buffer migration, using the default buffer dispose action.
/// </summary>
/// <param name="buffer">The source buffer for this migration</param>
/// <param name="sourceRangeAction">The flush action for the source buffer</param>
/// <param name="source">Pending migration for the source buffer</param>
public BufferMigrationSpan(
Buffer buffer,
BufferFlushAction sourceRangeAction,
BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { }
/// <summary> /// <summary>
/// Determine if the given range overlaps this migration, and has not been completed yet. /// Determine if the given range overlaps this migration, and has not been completed yet.
/// </summary> /// </summary>
/// <param name="offset">Start offset</param> /// <param name="offset">Start offset</param>
/// <param name="size">Range size</param> /// <param name="size">Range size</param>
/// <param name="syncNumber">The sync number that was waited on</param>
/// <returns>True if overlapping and in progress, false otherwise</returns> /// <returns>True if overlapping and in progress, false otherwise</returns>
public bool Overlaps(ulong offset, ulong size) public bool Overlaps(ulong offset, ulong size, ulong syncNumber)
{ {
ulong end = offset + size; ulong end = offset + size;
ulong destEnd = Address + Size; ulong destEnd = _offset + _size;
long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed.
return !(end <= Address || offset >= destEnd); return !(end <= _offset || offset >= destEnd) && syncDiff < 0;
}
/// <summary>
/// Determine if the given range matches this migration.
/// </summary>
/// <param name="offset">Start offset</param>
/// <param name="size">Range size</param>
/// <returns>True if the range exactly matches, false otherwise</returns>
public bool FullyMatches(ulong offset, ulong size)
{
return _offset == offset && _size == size;
} }
/// <summary> /// <summary>
@@ -222,30 +100,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="offset">Start offset</param> /// <param name="offset">Start offset</param>
/// <param name="size">Range size</param> /// <param name="size">Range size</param>
/// <param name="syncNumber">Current sync number</param> /// <param name="syncNumber">Current sync number</param>
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber) /// <param name="parent">The modified range list that originally owned this range</param>
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
{ {
ulong end = offset + size; ulong end = offset + size;
end = Math.Min(Address + Size, end); end = Math.Min(_offset + _size, end);
offset = Math.Max(Address, offset); offset = Math.Max(_offset, offset);
size = end - offset; size = end - offset;
if (_source != null) _source.RangeActionWithMigration(offset, size, syncNumber, parent, _sourceRangeAction);
{
_source.RangeActionWithMigration(offset, size, syncNumber, _sourceRangeAction);
}
else
{
_sourceRangeAction(offset, size, syncNumber);
}
} }
/// <summary> /// <summary>
/// Removes this migration span, potentially allowing for the source buffer to be disposed. /// Removes this reference to the range list, potentially allowing for the source buffer to be disposed.
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
_disposeAction(); Destination.RemoveMigration(this);
_buffer.DecrementReferenceCount();
} }
} }
} }

View File

@@ -1,6 +1,7 @@
using Ryujinx.Common.Pools; using Ryujinx.Common.Pools;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
@@ -71,10 +72,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly Buffer _parent; private readonly Buffer _parent;
private readonly BufferFlushAction _flushAction; private readonly Action<ulong, ulong> _flushAction;
private BufferMigration _source; private List<BufferMigration> _sources;
private BufferModifiedRangeList _migrationTarget; private BufferMigration _migrationTarget;
private readonly object _lock = new(); private readonly object _lock = new();
@@ -98,7 +99,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="context">GPU context that the buffer range list belongs to</param> /// <param name="context">GPU context that the buffer range list belongs to</param>
/// <param name="parent">The parent buffer that owns this range list</param> /// <param name="parent">The parent buffer that owns this range list</param>
/// <param name="flushAction">The flush action for the parent buffer</param> /// <param name="flushAction">The flush action for the parent buffer</param>
public BufferModifiedRangeList(GpuContext context, Buffer parent, BufferFlushAction flushAction) : base(BackingInitialSize) public BufferModifiedRangeList(GpuContext context, Buffer parent, Action<ulong, ulong> flushAction) : base(BackingInitialSize)
{ {
_context = context; _context = context;
_parent = parent; _parent = parent;
@@ -198,36 +199,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
/// <summary>
/// Gets modified ranges within the specified region, and then fires the given action for each range individually.
/// </summary>
/// <param name="address">Start address to query</param>
/// <param name="size">Size to query</param>
/// <param name="syncNumber">Sync number required for a range to be signalled</param>
/// <param name="rangeAction">The action to call for each modified range</param>
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
{
int count = 0;
ref var overlaps = ref ThreadStaticArray<BufferModifiedRange>.Get();
// Range list must be consistent for this operation.
lock (_lock)
{
count = FindOverlapsNonOverlapping(address, size, ref overlaps);
}
for (int i = 0; i < count; i++)
{
BufferModifiedRange overlap = overlaps[i];
if (overlap.SyncNumber == syncNumber)
{
rangeAction(overlap.Address, overlap.Size);
}
}
}
/// <summary> /// <summary>
/// Gets modified ranges within the specified region, and then fires the given action for each range individually. /// Gets modified ranges within the specified region, and then fires the given action for each range individually.
/// </summary> /// </summary>
@@ -274,16 +245,41 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="offset">The offset to pass to the action</param> /// <param name="offset">The offset to pass to the action</param>
/// <param name="size">The size to pass to the action</param> /// <param name="size">The size to pass to the action</param>
/// <param name="syncNumber">The sync number that has been reached</param> /// <param name="syncNumber">The sync number that has been reached</param>
/// <param name="parent">The modified range list that originally owned this range</param>
/// <param name="rangeAction">The action to perform</param> /// <param name="rangeAction">The action to perform</param>
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction) public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action<ulong, ulong> rangeAction)
{ {
if (_source != null) bool firstSource = true;
if (parent != this)
{ {
_source.RangeActionWithMigration(offset, size, syncNumber, rangeAction); lock (_lock)
{
if (_sources != null)
{
foreach (BufferMigration source in _sources)
{
if (source.Overlaps(offset, size, syncNumber))
{
if (firstSource && !source.FullyMatches(offset, size))
{
// Perform this buffer's action first. The migrations will run after.
rangeAction(offset, size);
}
source.RangeActionWithMigration(offset, size, syncNumber, parent);
firstSource = false;
}
}
}
}
} }
else
if (firstSource)
{ {
rangeAction(offset, size, syncNumber); // No overlapping migrations, or they are not meant for this range, flush the data using the given action.
rangeAction(offset, size);
} }
} }
@@ -323,7 +319,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ClearPart(overlap, clampAddress, clampEnd); ClearPart(overlap, clampAddress, clampEnd);
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction); RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, overlap.Parent, _flushAction);
} }
} }
@@ -333,7 +329,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
// There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine. // There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine.
_migrationTarget.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); _migrationTarget.Destination.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
} }
/// <summary> /// <summary>
@@ -371,7 +367,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (rangeCount == -1) if (rangeCount == -1)
{ {
_migrationTarget.WaitForAndFlushRanges(address, size); _migrationTarget.Destination.WaitForAndFlushRanges(address, size);
return; return;
} }
@@ -411,9 +407,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Inherit ranges from another modified range list. /// Inherit ranges from another modified range list.
/// </summary> /// </summary>
/// <remarks>
/// Assumes that ranges will be inherited in address ascending order.
/// </remarks>
/// <param name="ranges">The range list to inherit from</param> /// <param name="ranges">The range list to inherit from</param>
/// <param name="registerRangeAction">The action to call for each modified range</param> /// <param name="registerRangeAction">The action to call for each modified range</param>
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction) public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
@@ -422,31 +415,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
lock (ranges._lock) lock (ranges._lock)
{ {
BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber);
ranges._parent.IncrementReferenceCount();
ranges._migrationTarget = migration;
_context.RegisterBufferMigration(migration);
inheritRanges = ranges.ToArray(); inheritRanges = ranges.ToArray();
lock (_lock) lock (_lock)
{ {
// Copy over the migration from the previous range list (_sources ??= new List<BufferMigration>()).Add(migration);
BufferMigration oldMigration = ranges._source;
BufferMigrationSpan span = new BufferMigrationSpan(ranges._parent, ranges._flushAction, oldMigration);
ranges._parent.IncrementReferenceCount();
if (_source == null)
{
// Create a new migration.
_source = new BufferMigration(new BufferMigrationSpan[] { span }, this, _context.SyncNumber);
_context.RegisterBufferMigration(_source);
}
else
{
// Extend the migration
_source.AddSpanToEnd(span);
}
ranges._migrationTarget = this;
foreach (BufferModifiedRange range in inheritRanges) foreach (BufferModifiedRange range in inheritRanges)
{ {
@@ -465,27 +445,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
/// <summary>
/// Register a migration from previous buffer storage. This migration is from a snapshot of the buffer's
/// current handle to its handle in the future, and is assumed to be complete when the sync action completes.
/// When the migration completes, the handle is disposed.
/// </summary>
public void SelfMigration()
{
lock (_lock)
{
BufferMigrationSpan span = new(_parent, _parent.GetSnapshotDisposeAction(), _parent.GetSnapshotFlushAction(), _source);
BufferMigration migration = new(new BufferMigrationSpan[] { span }, this, _context.SyncNumber);
// Migration target is used to redirect flush actions to the latest range list,
// so we don't need to set it here. (this range list is still the latest)
_context.RegisterBufferMigration(migration);
_source = migration;
}
}
/// <summary> /// <summary>
/// Removes a source buffer migration, indicating its copy has completed. /// Removes a source buffer migration, indicating its copy has completed.
/// </summary> /// </summary>
@@ -494,10 +453,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
lock (_lock) lock (_lock)
{ {
if (_source == migration) _sources.Remove(migration);
{
_source = null;
}
} }
} }

View File

@@ -1,295 +0,0 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// Manages flushing ranges from buffers in advance for easy access, if they are flushed often.
/// Typically, from device local memory to a host mapped target for cached access.
/// </summary>
internal class BufferPreFlush : IDisposable
{
private const ulong PageSize = MemoryManager.PageSize;
/// <summary>
/// Threshold for the number of copies without a flush required to disable preflush on a page.
/// </summary>
private const int DeactivateCopyThreshold = 200;
/// <summary>
/// Value that indicates whether a page has been flushed or copied before.
/// </summary>
private enum PreFlushState
{
None,
HasFlushed,
HasCopied
}
/// <summary>
/// Flush state for each page of the buffer.
/// Controls whether data should be copied to the flush buffer, what sync is expected
/// and unflushed copy counting for stopping copies that are no longer needed.
/// </summary>
private struct PreFlushPage
{
public PreFlushState State;
public ulong FirstActivatedSync;
public ulong LastCopiedSync;
public int CopyCount;
}
/// <summary>
/// True if there are ranges that should copy to the flush buffer, false otherwise.
/// </summary>
public bool ShouldCopy { get; private set; }
private readonly GpuContext _context;
private readonly Buffer _buffer;
private readonly PreFlushPage[] _pages;
private readonly ulong _address;
private readonly ulong _size;
private readonly ulong _misalignment;
private readonly Action<BufferHandle, ulong, ulong> _flushAction;
private BufferHandle _flushBuffer;
public BufferPreFlush(GpuContext context, Buffer parent, Action<BufferHandle, ulong, ulong> flushAction)
{
_context = context;
_buffer = parent;
_address = parent.Address;
_size = parent.Size;
_pages = new PreFlushPage[BitUtils.DivRoundUp(_size, PageSize)];
_misalignment = _address & (PageSize - 1);
_flushAction = flushAction;
}
/// <summary>
/// Ensure that the flush buffer exists.
/// </summary>
private void EnsureFlushBuffer()
{
if (_flushBuffer == BufferHandle.Null)
{
_flushBuffer = _context.Renderer.CreateBuffer((int)_size, BufferAccess.HostMemory);
}
}
/// <summary>
/// Gets a page range from an address and size byte range.
/// </summary>
/// <param name="address">Range address</param>
/// <param name="size">Range size</param>
/// <returns>A page index and count</returns>
private (int index, int count) GetPageRange(ulong address, ulong size)
{
ulong offset = address - _address;
ulong endOffset = offset + size;
int basePage = (int)(offset / PageSize);
int endPage = (int)((endOffset - 1) / PageSize);
return (basePage, 1 + endPage - basePage);
}
/// <summary>
/// Gets an offset and size range in the parent buffer from a page index and count.
/// </summary>
/// <param name="startPage">Range start page</param>
/// <param name="count">Range page count</param>
/// <returns>Offset and size range</returns>
private (int offset, int size) GetOffset(int startPage, int count)
{
int offset = (int)((ulong)startPage * PageSize - _misalignment);
int endOffset = (int)((ulong)(startPage + count) * PageSize - _misalignment);
offset = Math.Max(0, offset);
endOffset = Math.Min((int)_size, endOffset);
return (offset, endOffset - offset);
}
/// <summary>
/// Copy a range of pages from the parent buffer into the flush buffer.
/// </summary>
/// <param name="startPage">Range start page</param>
/// <param name="count">Range page count</param>
private void CopyPageRange(int startPage, int count)
{
(int offset, int size) = GetOffset(startPage, count);
EnsureFlushBuffer();
_context.Renderer.Pipeline.CopyBuffer(_buffer.Handle, _flushBuffer, offset, offset, size);
}
/// <summary>
/// Copy a modified range into the flush buffer if it's marked as flushed.
/// Any pages the range overlaps are copied, and copies aren't repeated in the same sync number.
/// </summary>
/// <param name="address">Range address</param>
/// <param name="size">Range size</param>
public void CopyModified(ulong address, ulong size)
{
(int baseIndex, int count) = GetPageRange(address, size);
ulong syncNumber = _context.SyncNumber;
int startPage = -1;
for (int i = 0; i < count; i++)
{
int pageIndex = baseIndex + i;
ref PreFlushPage page = ref _pages[pageIndex];
if (page.State > PreFlushState.None)
{
// Perform the copy, and update the state of each page.
if (startPage == -1)
{
startPage = pageIndex;
}
if (page.State != PreFlushState.HasCopied)
{
page.FirstActivatedSync = syncNumber;
page.State = PreFlushState.HasCopied;
}
else if (page.CopyCount++ >= DeactivateCopyThreshold)
{
page.CopyCount = 0;
page.State = PreFlushState.None;
}
if (page.LastCopiedSync != syncNumber)
{
page.LastCopiedSync = syncNumber;
}
}
else if (startPage != -1)
{
CopyPageRange(startPage, pageIndex - startPage);
startPage = -1;
}
}
if (startPage != -1)
{
CopyPageRange(startPage, (baseIndex + count) - startPage);
}
}
/// <summary>
/// Flush the given page range back into guest memory, optionally using data from the flush buffer.
/// The actual flushed range is an intersection of the page range and the address range.
/// </summary>
/// <param name="address">Address range start</param>
/// <param name="size">Address range size</param>
/// <param name="startPage">Page range start</param>
/// <param name="count">Page range count</param>
/// <param name="preFlush">True if the data should come from the flush buffer</param>
private void FlushPageRange(ulong address, ulong size, int startPage, int count, bool preFlush)
{
(int pageOffset, int pageSize) = GetOffset(startPage, count);
int offset = (int)(address - _address);
int end = offset + (int)size;
offset = Math.Max(offset, pageOffset);
end = Math.Min(end, pageOffset + pageSize);
if (end >= offset)
{
BufferHandle handle = preFlush ? _flushBuffer : _buffer.Handle;
_flushAction(handle, _address + (ulong)offset, (ulong)(end - offset));
}
}
/// <summary>
/// Flush the given address range back into guest memory, optionally using data from the flush buffer.
/// When a copy has been performed on or before the waited sync number, the data can come from the flush buffer.
/// Otherwise, it flushes the parent buffer directly.
/// </summary>
/// <param name="address">Range address</param>
/// <param name="size">Range size</param>
/// <param name="syncNumber">Sync number that has been waited for</param>
public void FlushWithAction(ulong address, ulong size, ulong syncNumber)
{
// Copy the parts of the range that have pre-flush copies that have been completed.
// Run the flush action for ranges that don't have pre-flush copies.
// If a range doesn't have a pre-flush copy, consider adding one.
(int baseIndex, int count) = GetPageRange(address, size);
bool rangePreFlushed = false;
int startPage = -1;
for (int i = 0; i < count; i++)
{
int pageIndex = baseIndex + i;
ref PreFlushPage page = ref _pages[pageIndex];
bool flushPage = false;
page.CopyCount = 0;
if (page.State == PreFlushState.HasCopied)
{
if (syncNumber >= page.FirstActivatedSync)
{
// After the range is first activated, its data will always be copied to the preflush buffer on each sync.
flushPage = true;
}
}
else if (page.State == PreFlushState.None)
{
page.State = PreFlushState.HasFlushed;
ShouldCopy = true;
}
if (flushPage)
{
if (!rangePreFlushed || startPage == -1)
{
if (startPage != -1)
{
FlushPageRange(address, size, startPage, pageIndex - startPage, false);
}
rangePreFlushed = true;
startPage = pageIndex;
}
}
else if (rangePreFlushed || startPage == -1)
{
if (startPage != -1)
{
FlushPageRange(address, size, startPage, pageIndex - startPage, true);
}
rangePreFlushed = false;
startPage = pageIndex;
}
}
if (startPage != -1)
{
FlushPageRange(address, size, startPage, (baseIndex + count) - startPage, rangePreFlushed);
}
}
/// <summary>
/// Dispose the flush buffer, if present.
/// </summary>
public void Dispose()
{
if (_flushBuffer != BufferHandle.Null)
{
_context.Renderer.DeleteBuffer(_flushBuffer);
}
}
}
}

View File

@@ -1,99 +0,0 @@
using Ryujinx.Graphics.Shader;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// Pipeline stages that can modify buffer data, as well as flags indicating storage usage.
/// Must match ShaderStage for the shader stages, though anything after that can be in any order.
/// </summary>
internal enum BufferStage : byte
{
Compute,
Vertex,
TessellationControl,
TessellationEvaluation,
Geometry,
Fragment,
Indirect,
VertexBuffer,
IndexBuffer,
Copy,
TransformFeedback,
Internal,
None,
StageMask = 0x3f,
StorageMask = 0xc0,
StorageRead = 0x40,
StorageWrite = 0x80,
#pragma warning disable CA1069 // Enums values should not be duplicated
StorageAtomic = 0xc0
#pragma warning restore CA1069 // Enums values should not be duplicated
}
/// <summary>
/// Utility methods to convert shader stages and binding flags into buffer stages.
/// </summary>
internal static class BufferStageUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage FromShaderStage(ShaderStage stage)
{
return (BufferStage)stage;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage FromShaderStage(int stageIndex)
{
return (BufferStage)(stageIndex + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage FromUsage(BufferUsageFlags flags)
{
if (flags.HasFlag(BufferUsageFlags.Write))
{
return BufferStage.StorageWrite;
}
else
{
return BufferStage.StorageRead;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage FromUsage(TextureUsageFlags flags)
{
if (flags.HasFlag(TextureUsageFlags.ImageStore))
{
return BufferStage.StorageWrite;
}
else
{
return BufferStage.StorageRead;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage TextureBuffer(ShaderStage shaderStage, TextureUsageFlags flags)
{
return FromShaderStage(shaderStage) | FromUsage(flags);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage GraphicsStorage(int stageIndex, BufferUsageFlags flags)
{
return FromShaderStage(stageIndex) | FromUsage(flags);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage ComputeStorage(BufferUsageFlags flags)
{
return BufferStage.Compute | FromUsage(flags);
}
}
}

View File

@@ -141,7 +141,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
if (algorithm == CompressionAlgorithm.Deflate) if (algorithm == CompressionAlgorithm.Deflate)
{ {
_activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true); _activeStream = new DeflateStream(_stream, CompressionLevel.SmallestSize, true);
} }
} }
@@ -206,7 +206,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
stream.Write(data); stream.Write(data);
break; break;
case CompressionAlgorithm.Deflate: case CompressionAlgorithm.Deflate:
stream = new DeflateStream(stream, CompressionLevel.Fastest, true); stream = new DeflateStream(stream, CompressionLevel.SmallestSize, true);
stream.Write(data); stream.Write(data);
stream.Dispose(); stream.Dispose();
break; break;

View File

@@ -18,8 +18,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private readonly ShaderSpecializationState _newSpecState; private readonly ShaderSpecializationState _newSpecState;
private readonly int _stageIndex; private readonly int _stageIndex;
private readonly bool _isVulkan; private readonly bool _isVulkan;
private readonly bool _hasGeometryShader;
private readonly bool _supportsQuads;
/// <summary> /// <summary>
/// Creates a new instance of the cached GPU state accessor for shader translation. /// Creates a new instance of the cached GPU state accessor for shader translation.
@@ -31,7 +29,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <param name="newSpecState">Shader specialization state of the recompiled shader</param> /// <param name="newSpecState">Shader specialization state of the recompiled shader</param>
/// <param name="counts">Resource counts shared across all shader stages</param> /// <param name="counts">Resource counts shared across all shader stages</param>
/// <param name="stageIndex">Shader stage index</param> /// <param name="stageIndex">Shader stage index</param>
/// <param name="hasGeometryShader">Indicates if a geometry shader is present</param>
public DiskCacheGpuAccessor( public DiskCacheGpuAccessor(
GpuContext context, GpuContext context,
ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> data,
@@ -39,8 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
ShaderSpecializationState oldSpecState, ShaderSpecializationState oldSpecState,
ShaderSpecializationState newSpecState, ShaderSpecializationState newSpecState,
ResourceCounts counts, ResourceCounts counts,
int stageIndex, int stageIndex) : base(context, counts, stageIndex)
bool hasGeometryShader) : base(context, counts, stageIndex)
{ {
_data = data; _data = data;
_cb1Data = cb1Data; _cb1Data = cb1Data;
@@ -48,8 +44,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_newSpecState = newSpecState; _newSpecState = newSpecState;
_stageIndex = stageIndex; _stageIndex = stageIndex;
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan; _isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_hasGeometryShader = hasGeometryShader;
_supportsQuads = context.Capabilities.SupportsQuads;
if (stageIndex == (int)ShaderStage.Geometry - 1) if (stageIndex == (int)ShaderStage.Geometry - 1)
{ {
@@ -106,11 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <inheritdoc/> /// <inheritdoc/>
public GpuGraphicsState QueryGraphicsState() public GpuGraphicsState QueryGraphicsState()
{ {
return _oldSpecState.GraphicsState.CreateShaderGraphicsState( return _oldSpecState.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled);
!_isVulkan,
_supportsQuads,
_hasGeometryShader,
_isVulkan || _oldSpecState.GraphicsState.YNegateEnabled);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 5936; private const uint CodeGenVersion = 6577;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@@ -601,8 +601,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
TargetApi api = _context.Capabilities.Api; TargetApi api = _context.Capabilities.Api;
bool hasCachedGs = guestShaders[4].HasValue;
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--) for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
{ {
if (guestShaders[stageIndex + 1].HasValue) if (guestShaders[stageIndex + 1].HasValue)
@@ -612,7 +610,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
byte[] guestCode = shader.Code; byte[] guestCode = shader.Code;
byte[] cb1Data = shader.Cb1Data; byte[] cb1Data = shader.Cb1Data;
DiskCacheGpuAccessor gpuAccessor = new(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex, hasCachedGs); DiskCacheGpuAccessor gpuAccessor = new(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0); TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0);
if (nextStage != null) if (nextStage != null)
@@ -625,7 +623,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
byte[] guestCodeA = guestShaders[0].Value.Code; byte[] guestCodeA = guestShaders[0].Value.Code;
byte[] cb1DataA = guestShaders[0].Value.Cb1Data; byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
DiskCacheGpuAccessor gpuAccessorA = new(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0, hasCachedGs); DiskCacheGpuAccessor gpuAccessorA = new(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0); translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
} }
@@ -713,7 +711,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
GuestCodeAndCbData shader = guestShaders[0].Value; GuestCodeAndCbData shader = guestShaders[0].Value;
ResourceCounts counts = new(); ResourceCounts counts = new();
ShaderSpecializationState newSpecState = new(ref specState.ComputeState); ShaderSpecializationState newSpecState = new(ref specState.ComputeState);
DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0, false); DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0); TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);

View File

@@ -17,8 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly int _stageIndex; private readonly int _stageIndex;
private readonly bool _compute; private readonly bool _compute;
private readonly bool _isVulkan; private readonly bool _isVulkan;
private readonly bool _hasGeometryShader;
private readonly bool _supportsQuads;
/// <summary> /// <summary>
/// Creates a new instance of the GPU state accessor for graphics shader translation. /// Creates a new instance of the GPU state accessor for graphics shader translation.
@@ -27,20 +25,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="state">Current GPU state</param> /// <param name="state">Current GPU state</param>
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param> /// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
/// <param name="hasGeometryShader">Indicates if a geometry shader is present</param> public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context, state.ResourceCounts, stageIndex)
public GpuAccessor(
GpuContext context,
GpuChannel channel,
GpuAccessorState state,
int stageIndex,
bool hasGeometryShader) : base(context, state.ResourceCounts, stageIndex)
{ {
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_channel = channel; _channel = channel;
_state = state; _state = state;
_stageIndex = stageIndex; _stageIndex = stageIndex;
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_hasGeometryShader = hasGeometryShader;
_supportsQuads = context.Capabilities.SupportsQuads;
if (stageIndex == (int)ShaderStage.Geometry - 1) if (stageIndex == (int)ShaderStage.Geometry - 1)
{ {
@@ -115,11 +105,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <inheritdoc/> /// <inheritdoc/>
public GpuGraphicsState QueryGraphicsState() public GpuGraphicsState QueryGraphicsState()
{ {
return _state.GraphicsState.CreateShaderGraphicsState( return _state.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _state.GraphicsState.YNegateEnabled);
!_isVulkan,
_supportsQuads,
_hasGeometryShader,
_isVulkan || _state.GraphicsState.YNegateEnabled);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -106,11 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Creates a new graphics state from this state that can be used for shader generation. /// Creates a new graphics state from this state that can be used for shader generation.
/// </summary> /// </summary>
/// <param name="hostSupportsAlphaTest">Indicates if the host API supports alpha test operations</param> /// <param name="hostSupportsAlphaTest">Indicates if the host API supports alpha test operations</param>
/// <param name="hostSupportsQuads">Indicates if the host API supports quad primitives</param>
/// <param name="hasGeometryShader">Indicates if a geometry shader is used</param>
/// <param name="originUpperLeft">If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner</param>
/// <returns>GPU graphics state that can be used for shader translation</returns> /// <returns>GPU graphics state that can be used for shader translation</returns>
public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool hostSupportsQuads, bool hasGeometryShader, bool originUpperLeft) public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool originUpperLeft)
{ {
AlphaTestOp alphaTestOp; AlphaTestOp alphaTestOp;
@@ -133,9 +130,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
}; };
} }
bool isQuad = Topology == PrimitiveTopology.Quads || Topology == PrimitiveTopology.QuadStrip;
bool halvePrimitiveId = !hostSupportsQuads && !hasGeometryShader && isQuad;
return new GpuGraphicsState( return new GpuGraphicsState(
EarlyZForce, EarlyZForce,
ConvertToInputTopology(Topology, TessellationMode), ConvertToInputTopology(Topology, TessellationMode),
@@ -155,8 +149,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
in FragmentOutputTypes, in FragmentOutputTypes,
DualSourceBlendEnable, DualSourceBlendEnable,
YNegateEnabled, YNegateEnabled,
originUpperLeft, originUpperLeft);
halvePrimitiveId);
} }
/// <summary> /// <summary>

View File

@@ -339,7 +339,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (gpuVa != 0) if (gpuVa != 0)
{ {
GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex, addresses.Geometry != 0); GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex);
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa); TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
if (nextStage != null) if (nextStage != null)

View File

@@ -61,9 +61,7 @@ namespace Ryujinx.Graphics.OpenGL
{ {
BufferCount++; BufferCount++;
var memType = access & GAL.BufferAccess.MemoryTypeMask; if (access.HasFlag(GAL.BufferAccess.FlushPersistent))
if (memType == GAL.BufferAccess.HostMemory)
{ {
BufferHandle handle = Buffer.CreatePersistent(size); BufferHandle handle = Buffer.CreatePersistent(size);
@@ -77,6 +75,11 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public BufferHandle CreateBuffer(int size, GAL.BufferAccess access, BufferHandle storageHint)
{
return CreateBuffer(size, access);
}
public BufferHandle CreateBuffer(nint pointer, int size) public BufferHandle CreateBuffer(nint pointer, int size)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
@@ -145,7 +148,6 @@ namespace Ryujinx.Graphics.OpenGL
return new Capabilities( return new Capabilities(
api: TargetApi.OpenGL, api: TargetApi.OpenGL,
vendorName: GpuVendor, vendorName: GpuVendor,
memoryType: SystemMemoryType.BackendManaged,
hasFrontFacingBug: intelWindows, hasFrontFacingBug: intelWindows,
hasVectorIndexingBug: amdWindows, hasVectorIndexingBug: amdWindows,
needsFragmentOutputSpecialization: false, needsFragmentOutputSpecialization: false,
@@ -159,7 +161,6 @@ namespace Ryujinx.Graphics.OpenGL
supportsBgraFormat: false, supportsBgraFormat: false,
supportsR4G4Format: false, supportsR4G4Format: false,
supportsR4G4B4A4Format: true, supportsR4G4B4A4Format: true,
supportsScaledVertexFormats: true,
supportsSnormBufferTextureFormat: false, supportsSnormBufferTextureFormat: false,
supports5BitComponentFormat: true, supports5BitComponentFormat: true,
supportsSparseBuffer: false, supportsSparseBuffer: false,
@@ -174,7 +175,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
supportsCubemapView: true, supportsCubemapView: true,
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsQuads: HwCapabilities.SupportsQuads, supportsScaledVertexFormats: true,
supportsSeparateSampler: false, supportsSeparateSampler: false,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot, supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsShaderBarrierDivergence: !(intelWindows || intelUnix), supportsShaderBarrierDivergence: !(intelWindows || intelUnix),

View File

@@ -102,11 +102,6 @@ namespace Ryujinx.Graphics.Shader
/// </summary> /// </summary>
public readonly bool OriginUpperLeft; public readonly bool OriginUpperLeft;
/// <summary>
/// Indicates that the primitive ID values on the shader should be halved due to quad to triangles conversion.
/// </summary>
public readonly bool HalvePrimitiveId;
/// <summary> /// <summary>
/// Creates a new GPU graphics state. /// Creates a new GPU graphics state.
/// </summary> /// </summary>
@@ -129,7 +124,6 @@ namespace Ryujinx.Graphics.Shader
/// <param name="dualSourceBlendEnable">Indicates whether dual source blend is enabled</param> /// <param name="dualSourceBlendEnable">Indicates whether dual source blend is enabled</param>
/// <param name="yNegateEnabled">Indicates if negation of the viewport Y axis is enabled</param> /// <param name="yNegateEnabled">Indicates if negation of the viewport Y axis is enabled</param>
/// <param name="originUpperLeft">If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner</param> /// <param name="originUpperLeft">If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner</param>
/// <param name="halvePrimitiveId">Indicates that the primitive ID values on the shader should be halved due to quad to triangles conversion</param>
public GpuGraphicsState( public GpuGraphicsState(
bool earlyZForce, bool earlyZForce,
InputTopology topology, InputTopology topology,
@@ -149,8 +143,7 @@ namespace Ryujinx.Graphics.Shader
in Array8<AttributeType> fragmentOutputTypes, in Array8<AttributeType> fragmentOutputTypes,
bool dualSourceBlendEnable, bool dualSourceBlendEnable,
bool yNegateEnabled, bool yNegateEnabled,
bool originUpperLeft, bool originUpperLeft)
bool halvePrimitiveId)
{ {
EarlyZForce = earlyZForce; EarlyZForce = earlyZForce;
Topology = topology; Topology = topology;
@@ -171,7 +164,6 @@ namespace Ryujinx.Graphics.Shader
DualSourceBlendEnable = dualSourceBlendEnable; DualSourceBlendEnable = dualSourceBlendEnable;
YNegateEnabled = yNegateEnabled; YNegateEnabled = yNegateEnabled;
OriginUpperLeft = originUpperLeft; OriginUpperLeft = originUpperLeft;
HalvePrimitiveId = halvePrimitiveId;
} }
} }
} }

View File

@@ -135,7 +135,6 @@ namespace Ryujinx.Graphics.Shader
default, default,
false, false,
false, false,
false,
false); false);
} }

View File

@@ -84,10 +84,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
value = context.IConvertU32ToFP32(value); value = context.IConvertU32ToFP32(value);
} }
} }
else if (offset == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId)
{
value = context.ShiftRightS32(value, Const(1));
}
context.Copy(Register(rd), value); context.Copy(Register(rd), value);
} }
@@ -191,12 +187,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
} }
} }
} }
else if (op.Imm10 == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId)
{
// If quads are used, but the host does not support them, they need to be converted to triangles.
// Since each quad becomes 2 triangles, we need to compensate here and divide primitive ID by 2.
res = context.ShiftRightS32(res, Const(1));
}
else if (op.Imm10 == AttributeConsts.FrontFacing && context.TranslatorContext.GpuAccessor.QueryHostHasFrontFacingBug()) else if (op.Imm10 == AttributeConsts.FrontFacing && context.TranslatorContext.GpuAccessor.QueryHostHasFrontFacingBug())
{ {
// gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs. // gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs.

View File

@@ -66,9 +66,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (nvHandle.AsgOp is not Operation handleOp || if (nvHandle.AsgOp is not Operation handleOp ||
handleOp.Inst != Instruction.Load || handleOp.Inst != Instruction.Load ||
(handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer)) handleOp.StorageKind != StorageKind.Input)
{ {
// Right now, we only allow bindless access when the handle comes from a shader input or storage buffer. // Right now, we only allow bindless access when the handle comes from a shader input.
// This is an artificial limitation to prevent it from being used in cases where it // This is an artificial limitation to prevent it from being used in cases where it
// would have a large performance impact of loading all textures in the pool. // would have a large performance impact of loading all textures in the pool.
// It might be removed in the future, if we can mitigate the performance impact. // It might be removed in the future, if we can mitigate the performance impact.

View File

@@ -45,8 +45,6 @@ namespace Ryujinx.Graphics.Shader.Translation
public bool YNegateEnabled => _graphicsState.YNegateEnabled; public bool YNegateEnabled => _graphicsState.YNegateEnabled;
public bool OriginUpperLeft => _graphicsState.OriginUpperLeft; public bool OriginUpperLeft => _graphicsState.OriginUpperLeft;
public bool HalvePrimitiveId => _graphicsState.HalvePrimitiveId;
public ImapPixelType[] ImapTypes { get; } public ImapPixelType[] ImapTypes { get; }
public bool IaIndexing { get; private set; } public bool IaIndexing { get; private set; }
public bool OaIndexing { get; private set; } public bool OaIndexing { get; private set; }

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
@@ -30,29 +31,40 @@ namespace Ryujinx.Graphics.Vulkan
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
private readonly MemoryAllocation _allocation; private MemoryAllocation _allocation;
private readonly Auto<DisposableBuffer> _buffer; private Auto<DisposableBuffer> _buffer;
private readonly Auto<MemoryAllocation> _allocationAuto; private Auto<MemoryAllocation> _allocationAuto;
private readonly bool _allocationImported; private readonly bool _allocationImported;
private readonly ulong _bufferHandle; private ulong _bufferHandle;
private CacheByRange<BufferHolder> _cachedConvertedBuffers; private CacheByRange<BufferHolder> _cachedConvertedBuffers;
public int Size { get; } public int Size { get; }
private readonly IntPtr _map; private IntPtr _map;
private readonly MultiFenceHolder _waitable; private MultiFenceHolder _waitable;
private bool _lastAccessIsWrite; private bool _lastAccessIsWrite;
private readonly BufferAllocationType _baseType; private BufferAllocationType _baseType;
private readonly BufferAllocationType _activeType; private BufferAllocationType _currentType;
private bool _swapQueued;
public BufferAllocationType DesiredType { get; private set; }
private int _setCount;
private int _writeCount;
private int _flushCount;
private int _flushTemp;
private int _lastFlushWrite = -1;
private readonly ReaderWriterLockSlim _flushLock; private readonly ReaderWriterLockSlim _flushLock;
private FenceHolder _flushFence; private FenceHolder _flushFence;
private int _flushWaiting; private int _flushWaiting;
private List<Action> _swapActions;
private byte[] _pendingData; private byte[] _pendingData;
private BufferMirrorRangeList _pendingDataRanges; private BufferMirrorRangeList _pendingDataRanges;
private Dictionary<ulong, StagingBufferReserved> _mirrors; private Dictionary<ulong, StagingBufferReserved> _mirrors;
@@ -71,7 +83,8 @@ namespace Ryujinx.Graphics.Vulkan
_map = allocation.HostPointer; _map = allocation.HostPointer;
_baseType = type; _baseType = type;
_activeType = currentType; _currentType = currentType;
DesiredType = currentType;
_flushLock = new ReaderWriterLockSlim(); _flushLock = new ReaderWriterLockSlim();
_useMirrors = gd.IsTBDR; _useMirrors = gd.IsTBDR;
@@ -91,7 +104,8 @@ namespace Ryujinx.Graphics.Vulkan
_map = _allocation.HostPointer + offset; _map = _allocation.HostPointer + offset;
_baseType = type; _baseType = type;
_activeType = currentType; _currentType = currentType;
DesiredType = currentType;
_flushLock = new ReaderWriterLockSlim(); _flushLock = new ReaderWriterLockSlim();
} }
@@ -106,11 +120,164 @@ namespace Ryujinx.Graphics.Vulkan
Size = size; Size = size;
_baseType = BufferAllocationType.Sparse; _baseType = BufferAllocationType.Sparse;
_activeType = BufferAllocationType.Sparse; _currentType = BufferAllocationType.Sparse;
DesiredType = BufferAllocationType.Sparse;
_flushLock = new ReaderWriterLockSlim(); _flushLock = new ReaderWriterLockSlim();
} }
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
{
if (_swapQueued && DesiredType != _currentType)
{
// Only swap if the buffer is not used in any queued command buffer.
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReadLockHeld && (_pendingData == null || cbs != null))
{
var currentAllocation = _allocationAuto;
var currentBuffer = _buffer;
IntPtr currentMap = _map;
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, false, _currentType);
if (buffer.Handle != 0)
{
if (cbs != null)
{
ClearMirrors(cbs.Value, 0, Size);
}
_flushLock.EnterWriteLock();
ClearFlushFence();
_waitable = new MultiFenceHolder(Size);
_allocation = allocation;
_allocationAuto = new Auto<MemoryAllocation>(allocation);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), this, _waitable, _allocationAuto);
_bufferHandle = buffer.Handle;
_map = allocation.HostPointer;
if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
{
// Copy data directly. Readbacks don't have to wait if this is done.
unsafe
{
new Span<byte>((void*)currentMap, Size).CopyTo(new Span<byte>((void*)_map, Size));
}
}
else
{
cbs ??= _gd.CommandBufferPool.Rent();
CommandBufferScoped cbsV = cbs.Value;
Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
// Need to wait for the data to reach the new buffer before data can be flushed.
_flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
_flushFence.Get();
}
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
_currentType = resultType;
if (_swapActions != null)
{
foreach (var action in _swapActions)
{
action();
}
_swapActions.Clear();
}
currentBuffer.Dispose();
currentAllocation.Dispose();
_gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
_flushLock.ExitWriteLock();
}
_swapQueued = false;
return true;
}
return false;
}
_swapQueued = false;
return true;
}
private void ConsiderBackingSwap()
{
if (_baseType == BufferAllocationType.Auto)
{
// When flushed, wait for a bit more info to make a decision.
bool wasFlushed = _flushTemp > 0;
int multiplier = wasFlushed ? 2 : 0;
if (_writeCount >= (WriteCountThreshold << multiplier) || _setCount >= (SetCountThreshold << multiplier) || _flushCount >= (FlushCountThreshold << multiplier))
{
if (_flushCount > 0 || _flushTemp-- > 0)
{
// Buffers that flush should ideally be mapped in host address space for easy copies.
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
bool hostMappingSensitive = _gd.Vendor == Vendor.Nvidia;
bool deviceLocalMapped = Size > DeviceLocalSizeThreshold || (wasFlushed && _writeCount > _flushCount * 10 && hostMappingSensitive) || _currentType == BufferAllocationType.DeviceLocalMapped;
DesiredType = deviceLocalMapped ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
// It's harder for a buffer that is flushed to revert to another type of mapping.
if (_flushCount > 0)
{
_flushTemp = 1000;
}
}
else if (_writeCount >= (WriteCountThreshold << multiplier))
{
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
DesiredType = BufferAllocationType.DeviceLocal;
}
else if (_setCount > (SetCountThreshold << multiplier))
{
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
DesiredType = BufferAllocationType.HostMapped;
}
_lastFlushWrite = -1;
_flushCount = 0;
_writeCount = 0;
_setCount = 0;
}
if (!_swapQueued && DesiredType != _currentType)
{
_swapQueued = true;
_gd.PipelineInternal.AddBackingSwap(this);
}
}
}
public void Pin()
{
if (_baseType == BufferAllocationType.Auto)
{
_baseType = _currentType;
}
}
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView) public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView)
{ {
var bufferViewCreateInfo = new BufferViewCreateInfo var bufferViewCreateInfo = new BufferViewCreateInfo
@@ -124,9 +291,19 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError(); _gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
(_swapActions ??= new List<Action>()).Add(invalidateView);
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer); return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer);
} }
public void InheritMetrics(BufferHolder other)
{
_setCount = other._setCount;
_writeCount = other._writeCount;
_flushCount = other._flushCount;
_flushTemp = other._flushTemp;
}
public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite) public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
{ {
// If the last access is write, we always need a barrier to be sure we will read or modify // If the last access is write, we always need a barrier to be sure we will read or modify
@@ -246,8 +423,18 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (isWrite) if (isWrite)
{ {
_writeCount++;
SignalWrite(0, Size); SignalWrite(0, Size);
} }
else if (isSSBO)
{
// Always consider SSBO access for swapping to device local memory.
_writeCount++;
ConsiderBackingSwap();
}
return _buffer; return _buffer;
} }
@@ -256,6 +443,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (isWrite) if (isWrite)
{ {
_writeCount++;
SignalWrite(offset, size); SignalWrite(offset, size);
} }
@@ -354,6 +543,8 @@ namespace Ryujinx.Graphics.Vulkan
public void SignalWrite(int offset, int size) public void SignalWrite(int offset, int size)
{ {
ConsiderBackingSwap();
if (offset == 0 && size == Size) if (offset == 0 && size == Size)
{ {
_cachedConvertedBuffers.Clear(); _cachedConvertedBuffers.Clear();
@@ -433,6 +624,13 @@ namespace Ryujinx.Graphics.Vulkan
WaitForFlushFence(); WaitForFlushFence();
if (_lastFlushWrite != _writeCount)
{
// If it's on the same page as the last flush, ignore it.
_lastFlushWrite = _writeCount;
_flushCount++;
}
Span<byte> result; Span<byte> result;
if (_map != IntPtr.Zero) if (_map != IntPtr.Zero)
@@ -513,7 +711,8 @@ namespace Ryujinx.Graphics.Vulkan
return; return;
} }
bool allowMirror = _useMirrors && allowCbsWait && cbs != null && _activeType <= BufferAllocationType.HostMapped; _setCount++;
bool allowMirror = _useMirrors && allowCbsWait && cbs != null && _currentType <= BufferAllocationType.HostMapped;
if (_map != IntPtr.Zero) if (_map != IntPtr.Zero)
{ {
@@ -664,6 +863,8 @@ namespace Ryujinx.Graphics.Vulkan
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length, true).Value; var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length, true).Value;
_writeCount--;
InsertBufferBarrier( InsertBufferBarrier(
_gd, _gd,
cbs.CommandBuffer, cbs.CommandBuffer,
@@ -899,6 +1100,8 @@ namespace Ryujinx.Graphics.Vulkan
public void Dispose() public void Dispose()
{ {
_swapQueued = false;
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size); _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
_buffer.Dispose(); _buffer.Dispose();

View File

@@ -165,6 +165,10 @@ namespace Ryujinx.Graphics.Vulkan
if (TryGetBuffer(range.Handle, out var existingHolder)) if (TryGetBuffer(range.Handle, out var existingHolder))
{ {
// Since this buffer now also owns the memory from the referenced buffer,
// we pin it to ensure the memory location will not change.
existingHolder.Pin();
(var memory, var offset) = existingHolder.GetDeviceMemoryAndOffset(); (var memory, var offset) = existingHolder.GetDeviceMemoryAndOffset();
memoryBinds[index] = new SparseMemoryBind() memoryBinds[index] = new SparseMemoryBind()
@@ -231,9 +235,10 @@ namespace Ryujinx.Graphics.Vulkan
int size, int size,
bool sparseCompatible = false, bool sparseCompatible = false,
BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferAllocationType baseType = BufferAllocationType.HostMapped,
BufferHandle storageHint = default,
bool forceMirrors = false) bool forceMirrors = false)
{ {
return CreateWithHandle(gd, size, out _, sparseCompatible, baseType, forceMirrors); return CreateWithHandle(gd, size, out _, sparseCompatible, baseType, storageHint, forceMirrors);
} }
public BufferHandle CreateWithHandle( public BufferHandle CreateWithHandle(
@@ -242,9 +247,10 @@ namespace Ryujinx.Graphics.Vulkan
out BufferHolder holder, out BufferHolder holder,
bool sparseCompatible = false, bool sparseCompatible = false,
BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferAllocationType baseType = BufferAllocationType.HostMapped,
BufferHandle storageHint = default,
bool forceMirrors = false) bool forceMirrors = false)
{ {
holder = Create(gd, size, forConditionalRendering: false, sparseCompatible, baseType); holder = Create(gd, size, forConditionalRendering: false, sparseCompatible, baseType, storageHint);
if (holder == null) if (holder == null)
{ {
return BufferHandle.Null; return BufferHandle.Null;
@@ -381,13 +387,31 @@ namespace Ryujinx.Graphics.Vulkan
int size, int size,
bool forConditionalRendering = false, bool forConditionalRendering = false,
bool sparseCompatible = false, bool sparseCompatible = false,
BufferAllocationType baseType = BufferAllocationType.HostMapped) BufferAllocationType baseType = BufferAllocationType.HostMapped,
BufferHandle storageHint = default)
{ {
BufferAllocationType type = baseType; BufferAllocationType type = baseType;
BufferHolder storageHintHolder = null;
if (baseType == BufferAllocationType.Auto) if (baseType == BufferAllocationType.Auto)
{ {
type = BufferAllocationType.HostMapped; if (gd.IsSharedMemory)
{
baseType = BufferAllocationType.HostMapped;
type = baseType;
}
else
{
type = size >= BufferHolder.DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocal : BufferAllocationType.HostMapped;
}
if (storageHint != BufferHandle.Null)
{
if (TryGetBuffer(storageHint, out storageHintHolder))
{
type = storageHintHolder.DesiredType;
}
}
} }
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) =
@@ -397,6 +421,11 @@ namespace Ryujinx.Graphics.Vulkan
{ {
var holder = new BufferHolder(gd, _device, buffer, allocation, size, baseType, resultType); var holder = new BufferHolder(gd, _device, buffer, allocation, size, baseType, resultType);
if (storageHintHolder != null)
{
holder.InheritMetrics(storageHintHolder);
}
return holder; return holder;
} }

View File

@@ -424,20 +424,10 @@ namespace Ryujinx.Graphics.Vulkan
public static BufferAllocationType Convert(this BufferAccess access) public static BufferAllocationType Convert(this BufferAccess access)
{ {
BufferAccess memType = access & BufferAccess.MemoryTypeMask; if (access.HasFlag(BufferAccess.FlushPersistent) || access.HasFlag(BufferAccess.Stream))
if (memType == BufferAccess.HostMemory || access.HasFlag(BufferAccess.Stream))
{ {
return BufferAllocationType.HostMapped; return BufferAllocationType.HostMapped;
} }
else if (memType == BufferAccess.DeviceMemory)
{
return BufferAllocationType.DeviceLocal;
}
else if (memType == BufferAccess.DeviceMemoryMapped)
{
return BufferAllocationType.DeviceLocalMapped;
}
return BufferAllocationType.Auto; return BufferAllocationType.Auto;
} }

View File

@@ -222,6 +222,20 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
private void TryBackingSwaps()
{
CommandBufferScoped? cbs = null;
_backingSwaps.RemoveAll(holder => holder.TryBackingSwap(ref cbs));
cbs?.Dispose();
}
public void AddBackingSwap(BufferHolder holder)
{
_backingSwaps.Add(holder);
}
public void Restore() public void Restore()
{ {
if (Pipeline != null) if (Pipeline != null)
@@ -277,6 +291,8 @@ namespace Ryujinx.Graphics.Vulkan
Gd.ResetCounterPool(); Gd.ResetCounterPool();
TryBackingSwaps();
Restore(); Restore();
} }

View File

@@ -486,7 +486,12 @@ namespace Ryujinx.Graphics.Vulkan
public BufferHandle CreateBuffer(int size, BufferAccess access) public BufferHandle CreateBuffer(int size, BufferAccess access)
{ {
return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), access.HasFlag(BufferAccess.Stream)); return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), default, access == BufferAccess.Stream);
}
public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint)
{
return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), storageHint);
} }
public BufferHandle CreateBuffer(nint pointer, int size) public BufferHandle CreateBuffer(nint pointer, int size)
@@ -670,23 +675,9 @@ namespace Ryujinx.Graphics.Vulkan
var limits = _physicalDevice.PhysicalDeviceProperties.Limits; var limits = _physicalDevice.PhysicalDeviceProperties.Limits;
var mainQueueProperties = _physicalDevice.QueueFamilyProperties[QueueFamilyIndex]; var mainQueueProperties = _physicalDevice.QueueFamilyProperties[QueueFamilyIndex];
SystemMemoryType memoryType;
if (IsSharedMemory)
{
memoryType = SystemMemoryType.UnifiedMemory;
}
else
{
memoryType = Vendor == Vendor.Nvidia ?
SystemMemoryType.DedicatedMemorySlowStorage :
SystemMemoryType.DedicatedMemory;
}
return new Capabilities( return new Capabilities(
api: TargetApi.Vulkan, api: TargetApi.Vulkan,
GpuVendor, GpuVendor,
memoryType: memoryType,
hasFrontFacingBug: IsIntelWindows, hasFrontFacingBug: IsIntelWindows,
hasVectorIndexingBug: Vendor == Vendor.Qualcomm, hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
needsFragmentOutputSpecialization: IsMoltenVk, needsFragmentOutputSpecialization: IsMoltenVk,
@@ -700,7 +691,6 @@ namespace Ryujinx.Graphics.Vulkan
supportsBgraFormat: true, supportsBgraFormat: true,
supportsR4G4Format: false, supportsR4G4Format: false,
supportsR4G4B4A4Format: supportsR4G4B4A4Format, supportsR4G4B4A4Format: supportsR4G4B4A4Format,
supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(),
supportsSnormBufferTextureFormat: true, supportsSnormBufferTextureFormat: true,
supports5BitComponentFormat: supports5BitComponentFormat, supports5BitComponentFormat: supports5BitComponentFormat,
supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit), supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit),
@@ -715,7 +705,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsMismatchingViewFormat: true, supportsMismatchingViewFormat: true,
supportsCubemapView: !IsAmdGcn, supportsCubemapView: !IsAmdGcn,
supportsNonConstantTextureOffset: false, supportsNonConstantTextureOffset: false,
supportsQuads: false, supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(),
supportsSeparateSampler: true, supportsSeparateSampler: true,
supportsShaderBallot: false, supportsShaderBallot: false,
supportsShaderBarrierDivergence: Vendor != Vendor.Intel, supportsShaderBarrierDivergence: Vendor != Vendor.Intel,

View File

@@ -81,11 +81,6 @@ namespace Ryujinx.Input.GTK3
return _pressedKeys.Contains(nativeKey); return _pressedKeys.Contains(nativeKey);
} }
public void Clear()
{
_pressedKeys.Clear();
}
public IGamepad GetGamepad(string id) public IGamepad GetGamepad(string id)
{ {
if (!_keyboardIdentifers[0].Equals(id)) if (!_keyboardIdentifers[0].Equals(id))

View File

@@ -75,7 +75,7 @@ namespace Ryujinx
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{ {
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning); MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning);
} }
// Parse arguments // Parse arguments
@@ -181,24 +181,21 @@ namespace Ryujinx
{ {
// No configuration, we load the default values and save it to disk // No configuration, we load the default values and save it to disk
ConfigurationPath = appDataConfigurationPath; ConfigurationPath = appDataConfigurationPath;
Logger.Notice.Print(LogClass.Application, $"No configuration file found. Saving default configuration to: {ConfigurationPath}");
ConfigurationState.Instance.LoadDefault(); ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
} }
else else
{ {
Logger.Notice.Print(LogClass.Application, $"Loading configuration from: {ConfigurationPath}");
if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat)) if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
{ {
ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath); ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
} }
else else
{ {
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {ConfigurationPath}");
ConfigurationState.Instance.LoadDefault(); ConfigurationState.Instance.LoadDefault();
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}");
} }
} }

View File

@@ -63,7 +63,7 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64' OR ('$(RuntimeIdentifier)' == '' AND $([MSBuild]::IsOSPlatform('Linux')))"> <ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
<Content Include="..\..\distribution\linux\Ryujinx.sh"> <Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>

View File

@@ -107,8 +107,6 @@ namespace Ryujinx.UI.Applet
swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
swkbdDialog.SetInputValidation(args.KeyboardMode); swkbdDialog.SetInputValidation(args.KeyboardMode);
((MainWindow)_parent).RendererWidget.NpadManager.BlockInputUpdates();
if (swkbdDialog.Run() == (int)ResponseType.Ok) if (swkbdDialog.Run() == (int)ResponseType.Ok)
{ {
inputText = swkbdDialog.InputEntry.Text; inputText = swkbdDialog.InputEntry.Text;
@@ -130,7 +128,6 @@ namespace Ryujinx.UI.Applet
}); });
dialogCloseEvent.WaitOne(); dialogCloseEvent.WaitOne();
((MainWindow)_parent).RendererWidget.NpadManager.UnblockInputUpdates();
userText = error ? null : inputText; userText = error ? null : inputText;

View File

@@ -22,7 +22,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private bool _sixAxisSensorFusionEnabled; private bool _sixAxisSensorFusionEnabled;
private bool _unintendedHomeButtonInputProtectionEnabled; private bool _unintendedHomeButtonInputProtectionEnabled;
private bool _npadAnalogStickCenterClampEnabled;
private bool _vibrationPermitted; private bool _vibrationPermitted;
private bool _usbFullKeyControllerEnabled; private bool _usbFullKeyControllerEnabled;
private readonly bool _isFirmwareUpdateAvailableForSixAxisSensor; private readonly bool _isFirmwareUpdateAvailableForSixAxisSensor;
@@ -1108,19 +1107,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// If not, it returns nothing. // If not, it returns nothing.
} }
[CommandCmif(134)] // 6.1.0+
// SetNpadUseAnalogStickUseCenterClamp(bool Enable, nn::applet::AppletResourceUserId)
public ResultCode SetNpadUseAnalogStickUseCenterClamp(ServiceCtx context)
{
ulong pid = context.RequestData.ReadUInt64();
_npadAnalogStickCenterClampEnabled = context.RequestData.ReadUInt32() != 0;
long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, _npadAnalogStickCenterClampEnabled });
return ResultCode.Success;
}
[CommandCmif(200)] [CommandCmif(200)]
// GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo
public ResultCode GetVibrationDeviceInfo(ServiceCtx context) public ResultCode GetVibrationDeviceInfo(ServiceCtx context)

View File

@@ -48,7 +48,7 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64' OR ('$(RuntimeIdentifier)' == '' AND $([MSBuild]::IsOSPlatform('Linux')))"> <ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
<Content Include="..\..\distribution\linux\Ryujinx.sh"> <Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
@@ -16,4 +16,10 @@
<PackageReference Include="Concentus" /> <PackageReference Include="Concentus" />
<PackageReference Include="LibHac" /> <PackageReference Include="LibHac" />
</ItemGroup> </ItemGroup>
<!-- Due to Concentus. -->
<PropertyGroup>
<NoWarn>NU1605</NoWarn>
</PropertyGroup>
</Project> </Project>

View File

@@ -14,11 +14,6 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
{ {
partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable
{ {
static HardwareOpusDecoder()
{
OpusCodecFactory.AttemptToUseNativeLibrary = false;
}
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
private struct OpusPacketHeader private struct OpusPacketHeader
{ {
@@ -35,87 +30,60 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
} }
} }
private interface IDecoder : IDisposable private interface IDecoder
{ {
int SampleRate { get; } int SampleRate { get; }
int ChannelsCount { get; } int ChannelsCount { get; }
int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize); int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
void ResetState(); void ResetState();
} }
private class Decoder : IDecoder private class Decoder : IDecoder
{ {
private readonly IOpusDecoder _decoder; private readonly OpusDecoder _decoder;
public int SampleRate => _decoder.SampleRate; public int SampleRate => _decoder.SampleRate;
public int ChannelsCount => _decoder.NumChannels; public int ChannelsCount => _decoder.NumChannels;
public Decoder(int sampleRate, int channelsCount) public Decoder(int sampleRate, int channelsCount)
{ {
_decoder = OpusCodecFactory.CreateDecoder(sampleRate, channelsCount); _decoder = new OpusDecoder(sampleRate, channelsCount);
} }
public int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize) public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
{ {
return _decoder.Decode(inData, outPcm, frameSize); return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
} }
public void ResetState() public void ResetState()
{ {
_decoder.ResetState(); _decoder.ResetState();
} }
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_decoder?.Dispose();
}
}
} }
private class MultiSampleDecoder : IDecoder private class MultiSampleDecoder : IDecoder
{ {
private readonly IOpusMultiStreamDecoder _decoder; private readonly OpusMSDecoder _decoder;
public int SampleRate => _decoder.SampleRate; public int SampleRate => _decoder.SampleRate;
public int ChannelsCount => _decoder.NumChannels; public int ChannelsCount { get; }
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping) public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
{ {
_decoder = OpusCodecFactory.CreateMultiStreamDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); ChannelsCount = channelsCount;
_decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
} }
public int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize) public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
{ {
return _decoder.DecodeMultistream(inData, outPcm, frameSize, false); return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
} }
public void ResetState() public void ResetState()
{ {
_decoder.ResetState(); _decoder.ResetState();
} }
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_decoder?.Dispose();
}
}
} }
private readonly IDecoder _decoder; private readonly IDecoder _decoder;
@@ -253,8 +221,7 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
{ {
timeTaken = 0; timeTaken = 0;
Span<short> outPcmSpace = MemoryMarshal.Cast<byte, short>(output); Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples);
Result result = DecodeInterleaved(_decoder, reset, input, outPcmSpace, output.Length, out outConsumed, out outSamples);
if (withPerf) if (withPerf)
{ {
@@ -262,12 +229,14 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
timeTaken = 0; timeTaken = 0;
} }
MemoryMarshal.Cast<short, byte>(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]);
return result; return result;
} }
private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, ReadOnlySpan<byte> packet) private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet)
{ {
int result = OpusPacketInfo.GetNumSamples(packet, decoder.SampleRate); int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
numSamples = result; numSamples = result;
@@ -287,11 +256,12 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
IDecoder decoder, IDecoder decoder,
bool reset, bool reset,
ReadOnlySpan<byte> input, ReadOnlySpan<byte> input,
Span<short> outPcmData, out short[] outPcmData,
int outputSize, int outputSize,
out int outConsumed, out int outConsumed,
out int outSamples) out int outSamples)
{ {
outPcmData = null;
outConsumed = 0; outConsumed = 0;
outSamples = 0; outSamples = 0;
@@ -311,7 +281,7 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
return CodecResult.InvalidLength; return CodecResult.InvalidLength;
} }
ReadOnlySpan<byte> opusData = input.Slice(headerSize, (int)header.Length); byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray();
Result result = GetPacketNumSamples(decoder, out int numSamples, opusData); Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
@@ -322,6 +292,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
return CodecResult.InvalidLength; return CodecResult.InvalidLength;
} }
outPcmData = new short[numSamples * decoder.ChannelsCount];
if (reset) if (reset)
{ {
decoder.ResetState(); decoder.ResetState();
@@ -329,22 +301,13 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
try try
{ {
outSamples = decoder.Decode(opusData, outPcmData, numSamples); outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
outConsumed = (int)totalSize; outConsumed = (int)totalSize;
} }
catch (OpusException e) catch (OpusException)
{ {
switch (e.OpusErrorCode) // TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases...
{ return CodecResult.InvalidLength;
case OpusError.OPUS_BUFFER_TOO_SMALL:
return CodecResult.InvalidLength;
case OpusError.OPUS_BAD_ARG:
return CodecResult.OpusBadArg;
case OpusError.OPUS_INVALID_PACKET:
return CodecResult.OpusInvalidPacket;
default:
return CodecResult.InvalidLength;
}
} }
} }
@@ -361,8 +324,6 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
_workBufferHandle = 0; _workBufferHandle = 0;
} }
_decoder?.Dispose();
} }
} }

View File

@@ -174,11 +174,6 @@ namespace Ryujinx.Input.HLE
{ {
lock (_lock) lock (_lock)
{ {
foreach (InputConfig inputConfig in _inputConfig)
{
_controllers[(int)inputConfig.PlayerIndex]?.GamepadDriver?.Clear();
}
_blockInputUpdates = false; _blockInputUpdates = false;
} }
} }

View File

@@ -33,11 +33,5 @@ namespace Ryujinx.Input
/// <param name="id">The unique id of the gamepad</param> /// <param name="id">The unique id of the gamepad</param>
/// <returns>An instance of <see cref="IGamepad"/> associated to the gamepad id given or null if not found</returns> /// <returns>An instance of <see cref="IGamepad"/> associated to the gamepad id given or null if not found</returns>
IGamepad GetGamepad(string id); IGamepad GetGamepad(string id);
/// <summary>
/// Clear the internal state of the driver.
/// </summary>
/// <remarks>Does nothing by default.</remarks>
void Clear() { }
} }
} }

View File

@@ -52,9 +52,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -80,9 +78,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -108,9 +104,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -136,9 +130,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -164,9 +156,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -192,9 +182,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -220,9 +208,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -248,9 +234,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -276,9 +260,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -304,69 +286,11 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
} }
[Test]
public void TestRevision11()
{
BehaviourContext behaviourContext = new();
behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision11);
Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
Assert.IsTrue(behaviourContext.IsSplitterSupported());
Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
}
[Test]
public void TestRevision12()
{
BehaviourContext behaviourContext = new();
behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision12);
Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
Assert.IsTrue(behaviourContext.IsSplitterSupported());
Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
}
} }
} }

View File

@@ -9,8 +9,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
[Test] [Test]
public void EnsureTypeSize() public void EnsureTypeSize()
{ {
Assert.AreEqual(0xE0, Unsafe.SizeOf<SplitterDestinationVersion1>()); Assert.AreEqual(0xE0, Unsafe.SizeOf<SplitterDestination>());
Assert.AreEqual(0x110, Unsafe.SizeOf<SplitterDestinationVersion2>());
} }
} }
} }

View File

@@ -45,7 +45,7 @@ namespace Ryujinx.UI.App.Common
if (!System.IO.Path.Exists(titleFilePath)) if (!System.IO.Path.Exists(titleFilePath))
{ {
Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist."); Logger.Error?.Print(LogClass.Application, $"File does not exists. {titleFilePath}");
return string.Empty; return string.Empty;
} }

View File

@@ -106,7 +106,7 @@ namespace Ryujinx.UI.App.Common
if (!Directory.Exists(appDir)) if (!Directory.Exists(appDir))
{ {
Logger.Warning?.Print(LogClass.Application, $"The specified game directory \"{appDir}\" does not exist."); Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid directory: \"{appDir}\"");
continue; continue;
} }

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration
/// <summary> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 51; public const int CurrentVersion = 50;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format
@@ -162,11 +162,6 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary> /// </summary>
public bool ShowConfirmExit { get; set; } public bool ShowConfirmExit { get; set; }
/// <summary>
/// Enables or disables save window size, position and state on close.
/// </summary>
public bool RememberWindowState { get; set; }
/// <summary> /// <summary>
/// Enables hardware-accelerated rendering for Avalonia /// Enables hardware-accelerated rendering for Avalonia
/// </summary> /// </summary>

View File

@@ -11,7 +11,6 @@ using Ryujinx.UI.Common.Configuration.UI;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
namespace Ryujinx.UI.Common.Configuration namespace Ryujinx.UI.Common.Configuration
@@ -627,11 +626,6 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary> /// </summary>
public ReactiveObject<bool> ShowConfirmExit { get; private set; } public ReactiveObject<bool> ShowConfirmExit { get; private set; }
/// <summary>
/// Enables or disables save window size, position and state on close.
/// </summary>
public ReactiveObject<bool> RememberWindowState { get; private set; }
/// <summary> /// <summary>
/// Enables hardware-accelerated rendering for Avalonia /// Enables hardware-accelerated rendering for Avalonia
/// </summary> /// </summary>
@@ -653,7 +647,6 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration = new ReactiveObject<bool>(); EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>(); CheckUpdatesOnStart = new ReactiveObject<bool>();
ShowConfirmExit = new ReactiveObject<bool>(); ShowConfirmExit = new ReactiveObject<bool>();
RememberWindowState = new ReactiveObject<bool>();
EnableHardwareAcceleration = new ReactiveObject<bool>(); EnableHardwareAcceleration = new ReactiveObject<bool>();
HideCursor = new ReactiveObject<HideCursorMode>(); HideCursor = new ReactiveObject<HideCursorMode>();
} }
@@ -691,7 +684,6 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration = EnableDiscordIntegration, EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart, CheckUpdatesOnStart = CheckUpdatesOnStart,
ShowConfirmExit = ShowConfirmExit, ShowConfirmExit = ShowConfirmExit,
RememberWindowState = RememberWindowState,
EnableHardwareAcceleration = EnableHardwareAcceleration, EnableHardwareAcceleration = EnableHardwareAcceleration,
HideCursor = HideCursor, HideCursor = HideCursor,
EnableVsync = Graphics.EnableVsync, EnableVsync = Graphics.EnableVsync,
@@ -800,7 +792,6 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration.Value = true; EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true; CheckUpdatesOnStart.Value = true;
ShowConfirmExit.Value = true; ShowConfirmExit.Value = true;
RememberWindowState.Value = true;
EnableHardwareAcceleration.Value = true; EnableHardwareAcceleration.Value = true;
HideCursor.Value = HideCursorMode.OnIdle; HideCursor.Value = HideCursorMode.OnIdle;
Graphics.EnableVsync.Value = true; Graphics.EnableVsync.Value = true;
@@ -1468,15 +1459,6 @@ namespace Ryujinx.UI.Common.Configuration
configurationFileUpdated = true; configurationFileUpdated = true;
} }
if (configurationFileFormat.Version < 51)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 51.");
configurationFileFormat.RememberWindowState = true;
configurationFileUpdated = true;
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@@ -1507,7 +1489,6 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart; CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart;
ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit; ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit;
RememberWindowState.Value = configurationFileFormat.RememberWindowState;
EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration; EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration;
HideCursor.Value = configurationFileFormat.HideCursor; HideCursor.Value = configurationFileFormat.HideCursor;
Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;
@@ -1595,9 +1576,7 @@ namespace Ryujinx.UI.Common.Configuration
private static void LogValueChange<T>(ReactiveEventArgs<T> eventArgs, string valueName) private static void LogValueChange<T>(ReactiveEventArgs<T> eventArgs, string valueName)
{ {
string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}"); Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, $"{valueName} set to: {eventArgs.NewValue}");
Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, message);
} }
public static void Initialize() public static void Initialize()

View File

@@ -1,7 +1,6 @@
using DiscordRPC; using DiscordRPC;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using System.Text;
namespace Ryujinx.UI.Common namespace Ryujinx.UI.Common
{ {
@@ -10,9 +9,6 @@ namespace Ryujinx.UI.Common
private const string Description = "A simple, experimental Nintendo Switch emulator."; private const string Description = "A simple, experimental Nintendo Switch emulator.";
private const string ApplicationId = "1216775165866807456"; private const string ApplicationId = "1216775165866807456";
private const int ApplicationByteLimit = 128;
private const string Ellipsis = "…";
private static DiscordRpcClient _discordClient; private static DiscordRpcClient _discordClient;
private static RichPresence _discordPresenceMain; private static RichPresence _discordPresenceMain;
@@ -64,18 +60,18 @@ namespace Ryujinx.UI.Common
} }
} }
public static void SwitchToPlayingState(string titleId, string applicationName) public static void SwitchToPlayingState(string titleId, string titleName)
{ {
_discordClient?.SetPresence(new RichPresence _discordClient?.SetPresence(new RichPresence
{ {
Assets = new Assets Assets = new Assets
{ {
LargeImageKey = "game", LargeImageKey = "game",
LargeImageText = TruncateToByteLength(applicationName, ApplicationByteLimit), LargeImageText = titleName,
SmallImageKey = "ryujinx", SmallImageKey = "ryujinx",
SmallImageText = Description, SmallImageText = Description,
}, },
Details = TruncateToByteLength($"Playing {applicationName}", ApplicationByteLimit), Details = $"Playing {titleName}",
State = (titleId == "0000000000000000") ? "Homebrew" : titleId.ToUpper(), State = (titleId == "0000000000000000") ? "Homebrew" : titleId.ToUpper(),
Timestamps = Timestamps.Now, Timestamps = Timestamps.Now,
Buttons = Buttons =
@@ -94,28 +90,6 @@ namespace Ryujinx.UI.Common
_discordClient?.SetPresence(_discordPresenceMain); _discordClient?.SetPresence(_discordPresenceMain);
} }
private static string TruncateToByteLength(string input, int byteLimit)
{
if (Encoding.UTF8.GetByteCount(input) <= byteLimit)
{
return input;
}
// Find the length to trim the string to guarantee we have space for the trailing ellipsis.
int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis);
// Basic trim to best case scenario of 1 byte characters.
input = input[..trimLimit];
while (Encoding.UTF8.GetByteCount(input) > trimLimit)
{
// Remove one character from the end of the string at a time.
input = input[..^1];
}
return input.TrimEnd() + Ellipsis;
}
public static void Exit() public static void Exit()
{ {
_discordClient?.Dispose(); _discordClient?.Dispose();

View File

@@ -1,10 +1,8 @@
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
@@ -86,7 +84,7 @@ namespace Ryujinx.Ava
ApplyConfiguredTheme(); ApplyConfiguredTheme();
} }
public void ApplyConfiguredTheme() private void ApplyConfiguredTheme()
{ {
try try
{ {
@@ -94,18 +92,13 @@ namespace Ryujinx.Ava
if (string.IsNullOrWhiteSpace(baseStyle)) if (string.IsNullOrWhiteSpace(baseStyle))
{ {
ConfigurationState.Instance.UI.BaseStyle.Value = "Auto"; ConfigurationState.Instance.UI.BaseStyle.Value = "Dark";
baseStyle = ConfigurationState.Instance.UI.BaseStyle; baseStyle = ConfigurationState.Instance.UI.BaseStyle;
} }
ThemeVariant systemTheme = DetectSystemTheme();
ThemeManager.OnThemeChanged();
RequestedThemeVariant = baseStyle switch RequestedThemeVariant = baseStyle switch
{ {
"Auto" => systemTheme,
"Light" => ThemeVariant.Light, "Light" => ThemeVariant.Light,
"Dark" => ThemeVariant.Dark, "Dark" => ThemeVariant.Dark,
_ => ThemeVariant.Default, _ => ThemeVariant.Default,
@@ -118,28 +111,5 @@ namespace Ryujinx.Ava
ShowRestartDialog(); ShowRestartDialog();
} }
} }
/// <summary>
/// Converts a PlatformThemeVariant value to the corresponding ThemeVariant value.
/// </summary>
public static ThemeVariant ConvertThemeVariant(PlatformThemeVariant platformThemeVariant) =>
platformThemeVariant switch
{
PlatformThemeVariant.Dark => ThemeVariant.Dark,
PlatformThemeVariant.Light => ThemeVariant.Light,
_ => ThemeVariant.Default,
};
public static ThemeVariant DetectSystemTheme()
{
if (Application.Current is App app)
{
var colorValues = app.PlatformSettings.GetColorValues();
return ConvertThemeVariant(colorValues.ThemeVariant);
}
return ThemeVariant.Default;
}
} }
} }

View File

@@ -1,42 +1,38 @@
{ {
"Language": "اَلْعَرَبِيَّةُ", "Language": "العربية",
"MenuBarFileOpenApplet": "فتح التطبيق المصغر", "MenuBarFileOpenApplet": "فتح التطبيق المُصغَّر",
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "افتح تطبيق تحرير Mii في الوضع المستقل", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "افتح تطبيق محرر الـMii المُصغَّر في الوضع المستقل",
"SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة", "SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة",
"SettingsTabSystemMemoryManagerMode": "وضع إدارة الذاكرة:", "SettingsTabSystemMemoryManagerMode": "وضع إدارة الذاكرة:",
"SettingsTabSystemMemoryManagerModeSoftware": "البرنامج", "SettingsTabSystemMemoryManagerModeSoftware": "البرنامج",
"SettingsTabSystemMemoryManagerModeHost": "المُضيف (سريع)", "SettingsTabSystemMemoryManagerModeHost": "المُضيف (سريع)",
"SettingsTabSystemMemoryManagerModeHostUnchecked": "المضيف (غير مفحوص) (أسرع، غير آمن)", "SettingsTabSystemMemoryManagerModeHostUnchecked": "المضيف غير محدد (سريع، غير آمن)",
"SettingsTabSystemUseHypervisor": "استخدم مراقب الأجهزة الافتراضية", "SettingsTabSystemUseHypervisor": "استخدم الهايبرڤايزور",
"MenuBarFile": "_ملف", "MenuBarFile": "_ملف",
"MenuBarFileOpenFromFile": "_تحميل تطبيق من ملف", "MenuBarFileOpenFromFile": "_تحميل تطبيق من ملف",
"MenuBarFileOpenUnpacked": "تحميل لُعْبَة غير محزومة", "MenuBarFileOpenUnpacked": "تحميل لعبه غير محزومه",
"MenuBarFileOpenEmuFolder": "فتح مجلد Ryujinx", "MenuBarFileOpenEmuFolder": "فتح مجلد Ryujinx",
"MenuBarFileOpenLogsFolder": "فتح مجلد السجلات", "MenuBarFileOpenLogsFolder": "فتح مجلد السجلات",
"MenuBarFileExit": "_خروج", "MenuBarFileExit": "_خروج",
"MenuBarOptions": "_خيارات", "MenuBarOptions": "_خيارات",
"MenuBarOptionsToggleFullscreen": "التبديل إلى وضع ملء الشاشة", "MenuBarOptionsToggleFullscreen": "وضع ملء الشاشة",
"MenuBarOptionsStartGamesInFullscreen": "ابدأ الألعاب في وضع ملء الشاشة", "MenuBarOptionsStartGamesInFullscreen": "ابدأ الألعاب في وضع ملء الشاشة",
"MenuBarOptionsStopEmulation": "إيقاف المحاكاة", "MenuBarOptionsStopEmulation": "إيقاف المحاكاة",
"MenuBarOptionsSettings": "_الإعدادات", "MenuBarOptionsSettings": "الإعدادات",
"MenuBarOptionsManageUserProfiles": "_إدارة الملفات الشخصية للمستخدم", "MenuBarOptionsManageUserProfiles": "إدارة الملفات الشخصية للمستخدم",
"MenuBarActions": "_الإجراءات", "MenuBarActions": "الإجراءات",
"MenuBarOptionsSimulateWakeUpMessage": "محاكاة رسالة الاستيقاظ", "MenuBarOptionsSimulateWakeUpMessage": "محاكاة رسالة الاستيقاظ",
"MenuBarActionsScanAmiibo": "فحص Amiibo", "MenuBarActionsScanAmiibo": "فحص Amiibo",
"MenuBarTools": "_الأدوات", "MenuBarTools": "الأدوات",
"MenuBarToolsInstallFirmware": "تثبيت البرنامج الثابت", "MenuBarToolsInstallFirmware": "تثبيت البرامج الثابتة",
"MenuBarFileToolsInstallFirmwareFromFile": "تثبيت برنامج ثابت من XCI أو ZIP", "MenuBarFileToolsInstallFirmwareFromFile": "تثبيت البرنامج الثابت من XCI أو ZIP",
"MenuBarFileToolsInstallFirmwareFromDirectory": "تثبيت برنامج ثابت من مجلد", "MenuBarFileToolsInstallFirmwareFromDirectory": "تثبيت برنامج ثابت من مجلد",
"MenuBarToolsManageFileTypes": "إدارة أنواع الملفات", "MenuBarToolsManageFileTypes": "إدارة أنواع الملفات",
"MenuBarToolsInstallFileTypes": "تثبيت أنواع الملفات", "MenuBarToolsInstallFileTypes": "تثبيت أنواع الملفات",
"MenuBarToolsUninstallFileTypes": "إزالة أنواع الملفات", "MenuBarToolsUninstallFileTypes": "إزالة أنواع الملفات",
"MenuBarView": "_عرض",
"MenuBarViewWindow": "حجم النافذة",
"MenuBarViewWindow720": "720p",
"MenuBarViewWindow1080": "1080p",
"MenuBarHelp": "_مساعدة", "MenuBarHelp": "_مساعدة",
"MenuBarHelpCheckForUpdates": "تحقق من التحديثات", "MenuBarHelpCheckForUpdates": "تحقق من التحديثات",
"MenuBarHelpAbout": "حول", "MenuBarHelpAbout": "عن البرنامج",
"MenuSearch": "بحث...", "MenuSearch": "بحث...",
"GameListHeaderFavorite": "مفضلة", "GameListHeaderFavorite": "مفضلة",
"GameListHeaderIcon": "الأيقونة", "GameListHeaderIcon": "الأيقونة",
@@ -44,63 +40,62 @@
"GameListHeaderDeveloper": "المطور", "GameListHeaderDeveloper": "المطور",
"GameListHeaderVersion": "الإصدار", "GameListHeaderVersion": "الإصدار",
"GameListHeaderTimePlayed": "وقت اللعب", "GameListHeaderTimePlayed": "وقت اللعب",
"GameListHeaderLastPlayed": "آخر مرة لُعبت", "GameListHeaderLastPlayed": "اخر تشغيل",
"GameListHeaderFileExtension": "صيغة الملف", "GameListHeaderFileExtension": "امتداد الملف",
"GameListHeaderFileSize": "حجم الملف", "GameListHeaderFileSize": "حجم الملف",
"GameListHeaderPath": "المسار", "GameListHeaderPath": "المسار",
"GameListContextMenuOpenUserSaveDirectory": "فتح مجلد حفظ المستخدم", "GameListContextMenuOpenUserSaveDirectory": "فتح مجلد حفظ المستخدم",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ المستخدم للتطبيق", "GameListContextMenuOpenUserSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ المستخدم للتطبيق",
"GameListContextMenuOpenDeviceSaveDirectory": "فتح مجلد حفظ الجهاز", "GameListContextMenuOpenDeviceSaveDirectory": "فتح مجلد حفظ الجهاز",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الجهاز للتطبيق", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الجهاز للتطبيق",
"GameListContextMenuOpenBcatSaveDirectory": "فتح مجلد حفظ الـBCAT", "GameListContextMenuOpenBcatSaveDirectory": "فتح مجلد حفظ الـBCAT",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الـBCAT للتطبيق", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الـBCAT للتطبيق",
"GameListContextMenuManageTitleUpdates": "إدارة تحديثات اللُعبة", "GameListContextMenuManageTitleUpdates": "إدارة تحديثات العنوان",
"GameListContextMenuManageTitleUpdatesToolTip": "يفتح نافذة إدارة تحديث اللُعبة", "GameListContextMenuManageTitleUpdatesToolTip": "يفتح نافذة إدارة تحديث العنوان",
"GameListContextMenuManageDlc": "إدارة المحتوي الإضافي", "GameListContextMenuManageDlc": "إدارة المحتوي الإضافي",
"GameListContextMenuManageDlcToolTip": "يفتح نافذة إدارة المحتوي الإضافي", "GameListContextMenuManageDlcToolTip": "يفتح نافذة إدارة المحتوي الإضافي",
"GameListContextMenuCacheManagement": "إدارة ذاكرة التخزين المؤقت", "GameListContextMenuCacheManagement": "إدارة ذاكرة التخزين المؤقت",
"GameListContextMenuCacheManagementPurgePptc": "قائمة انتظار إعادة بناء الـPPTC", "GameListContextMenuCacheManagementPurgePptc": "إعادة بناء PPTC في قائمة الانتظار",
"GameListContextMenuCacheManagementPurgePptcToolTip": "تنشيط PPTC لإعادة البناء في وقت الإقلاع عند بدء تشغيل اللعبة التالي", "GameListContextMenuCacheManagementPurgePptcToolTip": "تنشيط PPTC لإعادة البناء في وقت التمهيد عند بدء تشغيل اللعبة التالية",
"GameListContextMenuCacheManagementPurgeShaderCache": "تنظيف ذاكرة مرشحات الفيديو المؤقتة", "GameListContextMenuCacheManagementPurgeShaderCache": "إزالة ذاكرة التشغيل المؤقتة للمظللات ",
"GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "يحذف ذاكرة مرشحات الفيديو المؤقتة الخاصة بالتطبيق", "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "حذف الذاكرة المؤقتة للمظللات الخاصة بالتطبيق",
"GameListContextMenuCacheManagementOpenPptcDirectory": "فتح مجلد PPTC", "GameListContextMenuCacheManagementOpenPptcDirectory": "فتح مجلد PPTC",
"GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "يفتح المجلد الذي يحتوي على ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) للتطبيق", "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "يفتح المجلد الذي يحتوي على الـPPTC للتطبيق",
"GameListContextMenuCacheManagementOpenShaderCacheDirectory": "فتح مجلد الذاكرة المؤقتة لمرشحات الفيديو ", "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "فتح مجلد الذاكرة المؤقتة للمظللات ",
"GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "يفتح المجلد الذي يحتوي على ذاكرة المظللات المؤقتة للتطبيق", "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "يفتح المجلد الذي يحتوي على ذاكرة التشغيل المؤقتة للمظللات الخاصة بالتطبيق",
"GameListContextMenuExtractData": "استخراج البيانات", "GameListContextMenuExtractData": "إستخراج البيانات",
"GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFS": "ExeFS",
"GameListContextMenuExtractDataExeFSToolTip": " استخراج قسم نظام الملفات القابل للتنفيذ (ExeFS) من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", "GameListContextMenuExtractDataExeFSToolTip": "إستخراج قسم ExeFS من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)",
"GameListContextMenuExtractDataRomFS": "RomFS", "GameListContextMenuExtractDataRomFS": "RomFS",
"GameListContextMenuExtractDataRomFSToolTip": "استخراج قسم RomFS من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", "GameListContextMenuExtractDataRomFSToolTip": "استخراج قسم RomFS من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)",
"GameListContextMenuExtractDataLogo": "شعار", "GameListContextMenuExtractDataLogo": "شعار",
"GameListContextMenuExtractDataLogoToolTip": "استخراج قسم الشعار من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", "GameListContextMenuExtractDataLogoToolTip": "استخراج قسم الشعار من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)",
"GameListContextMenuCreateShortcut": "إنشاء اختصار للتطبيق", "GameListContextMenuCreateShortcut": "إنشاء اختصار للتطبيق",
"GameListContextMenuCreateShortcutToolTip": "أنشئ اختصار سطح مكتب لتشغيل التطبيق المحدد", "GameListContextMenuCreateShortcutToolTip": "قم بإنشاء اختصار لسطح المكتب لتشغيل التطبيق المحدد",
"GameListContextMenuCreateShortcutToolTipMacOS": "أنشئ اختصار يُشغل التطبيق المحدد في مجلد تطبيقات macOS", "GameListContextMenuCreateShortcutToolTipMacOS": "قم بإنشاء اختصار في مجلد تطبيقات نظام التشغيل MacOS الذي يقوم بتشغيل التطبيق المحدد",
"GameListContextMenuOpenModsDirectory": "فتح مجلد التعديلات (Mods)", "GameListContextMenuOpenModsDirectory": "فتح مجلد التعديلات",
"GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات(mods) التطبيق", "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات التطبيق",
"GameListContextMenuOpenSdModsDirectory": "فتح مجلد تعديلات(mods) أتموسفير", "GameListContextMenuOpenSdModsDirectory": "فتح مجلد تعديلات Atmosphere",
"GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح مجلد أتموسفير لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح دليل Atmosphere لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.",
"StatusBarGamesLoaded": "{0}/{1} لعبة تم تحميلها", "StatusBarGamesLoaded": "{0}/{1} الألعاب التي تم تحميلها",
"StatusBarSystemVersion": "إصدار النظام: {0}", "StatusBarSystemVersion": "إصدار النظام: {0}",
"LinuxVmMaxMapCountDialogTitle": "الحد الأدنى لتعيينات الذاكرة المكتشفة", "LinuxVmMaxMapCountDialogTitle": "الحد الأدنى لتعيينات الذاكرة المكتشفة",
"LinuxVmMaxMapCountDialogTextPrimary": "هل ترغب في زيادة قيمة vm.max_map_count إلى {0}", "LinuxVmMaxMapCountDialogTextPrimary": "هل ترغب في زيادة قيمة vm.max_map_count إلى {0}",
"LinuxVmMaxMapCountDialogTextSecondary": "قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيغلق ريوجينكس بمجرد تجاوز هذا الحد.", "LinuxVmMaxMapCountDialogTextSecondary": "قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيتحطم Ryujinx بمجرد تجاوز هذا الحد.",
"LinuxVmMaxMapCountDialogButtonUntilRestart": "نعم، حتى إعادة التشغيل التالية", "LinuxVmMaxMapCountDialogButtonUntilRestart": "نعم، حتى إعادة التشغيل التالية",
"LinuxVmMaxMapCountDialogButtonPersistent": "نعم، دائمًا", "LinuxVmMaxMapCountDialogButtonPersistent": "نعم، بشكل دائم",
"LinuxVmMaxMapCountWarningTextPrimary": "الحد الأقصى لمقدار تعيينات الذاكرة أقل من الموصى به.", "LinuxVmMaxMapCountWarningTextPrimary": "الحد الأقصى لمقدار تعيينات الذاكرة أقل من الموصى به.",
"LinuxVmMaxMapCountWarningTextSecondary": "القيمة الحالية لـ vm.max_map_count ({0}) أقل من {1}. قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيغلق ريوجينكس بمجرد تجاوز هذا الحد.\n\nقد ترغب إما في زيادة الحد يدويا أو تثبيت pkexec، مما يسمح لـ ريوجينكس بالمساعدة في ذلك.", "LinuxVmMaxMapCountWarningTextSecondary": "القيمة الحالية لـ vm.max_map_count ({0}) أقل من {1}. قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيتحطم Ryujinx بمجرد تجاوز هذا الحد.\n\nقد ترغب في زيادة الحد اليدوي أو تثبيت pkexec، مما يسمح لـ Ryujinx بالمساعدة في ذلك.",
"Settings": "إعدادات", "Settings": "إعدادات",
"SettingsTabGeneral": "واجهة المستخدم", "SettingsTabGeneral": "واجهة المستخدم",
"SettingsTabGeneralGeneral": "عام", "SettingsTabGeneralGeneral": "العامة",
"SettingsTabGeneralEnableDiscordRichPresence": "تمكين وجود ديسكورد الغني", "SettingsTabGeneralEnableDiscordRichPresence": "تمكين وجود ديسكورد الغني",
"SettingsTabGeneralCheckUpdatesOnLaunch": "التحقق من وجود تحديثات عند التشغيل", "SettingsTabGeneralCheckUpdatesOnLaunch": "التحقق من وجود تحديثات عند التشغيل",
"SettingsTabGeneralShowConfirmExitDialog": "إظهار مربع حوار \"تأكيد الخروج\"", "SettingsTabGeneralShowConfirmExitDialog": "إظهار مربع حوار \"تأكيد الخروج\"",
"SettingsTabGeneralRememberWindowState": "تذكر حجم/موضع النافذة",
"SettingsTabGeneralHideCursor": "إخفاء المؤشر:", "SettingsTabGeneralHideCursor": "إخفاء المؤشر:",
"SettingsTabGeneralHideCursorNever": "مطلقا", "SettingsTabGeneralHideCursorNever": "مطلقاً",
"SettingsTabGeneralHideCursorOnIdle": "عند الخمول", "SettingsTabGeneralHideCursorOnIdle": "عند الخمول",
"SettingsTabGeneralHideCursorAlways": "دائما", "SettingsTabGeneralHideCursorAlways": "دائماً",
"SettingsTabGeneralGameDirectories": "مجلدات الألعاب", "SettingsTabGeneralGameDirectories": "مجلدات الألعاب",
"SettingsTabGeneralAdd": "إضافة", "SettingsTabGeneralAdd": "إضافة",
"SettingsTabGeneralRemove": "إزالة", "SettingsTabGeneralRemove": "إزالة",
@@ -132,24 +127,24 @@
"SettingsTabSystemSystemLanguageLatinAmericanSpanish": "إسبانية أمريكا اللاتينية", "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "إسبانية أمريكا اللاتينية",
"SettingsTabSystemSystemLanguageSimplifiedChinese": "الصينية المبسطة", "SettingsTabSystemSystemLanguageSimplifiedChinese": "الصينية المبسطة",
"SettingsTabSystemSystemLanguageTraditionalChinese": "الصينية التقليدية", "SettingsTabSystemSystemLanguageTraditionalChinese": "الصينية التقليدية",
"SettingsTabSystemSystemTimeZone": "النطاق الزمني للنظام:", "SettingsTabSystemSystemTimeZone": "نظام التوقيت للنظام:",
"SettingsTabSystemSystemTime": "توقيت النظام:", "SettingsTabSystemSystemTime": "توقيت النظام:",
"SettingsTabSystemEnableVsync": "VSync", "SettingsTabSystemEnableVsync": "VSync",
"SettingsTabSystemEnablePptc": "PPTC (ذاكرة التخزين المؤقت للترجمة المستمرة)", "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableFsIntegrityChecks": "التحقق من سلامة نظام الملفات", "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks",
"SettingsTabSystemAudioBackend": "خلفية الصوت:", "SettingsTabSystemAudioBackend": "خلفية الصوت:",
"SettingsTabSystemAudioBackendDummy": "زائف", "SettingsTabSystemAudioBackendDummy": "زائف",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL2": "SDL2",
"SettingsTabSystemHacks": "هاكات", "SettingsTabSystemHacks": "الاختراقات",
"SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار", "SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار",
"SettingsTabSystemExpandDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)", "SettingsTabSystemExpandDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)",
"SettingsTabSystemIgnoreMissingServices": "تجاهل الخدمات المفقودة", "SettingsTabSystemIgnoreMissingServices": "تجاهل الخدمات المفقودة",
"SettingsTabGraphics": "الرسومات", "SettingsTabGraphics": "الرسومات",
"SettingsTabGraphicsAPI": "API الرسومات ", "SettingsTabGraphicsAPI": "الرسومات API",
"SettingsTabGraphicsEnableShaderCache": "تفعيل ذاكرة المظللات المؤقتة", "SettingsTabGraphicsEnableShaderCache": "تفعيل ذاكرة المظللات المؤقتة",
"SettingsTabGraphicsAnisotropicFiltering": "تصفية:", "SettingsTabGraphicsAnisotropicFiltering": "تصفية متباين الخواص:",
"SettingsTabGraphicsAnisotropicFilteringAuto": "تلقائي", "SettingsTabGraphicsAnisotropicFilteringAuto": "تلقائي",
"SettingsTabGraphicsAnisotropicFiltering2x": "2x", "SettingsTabGraphicsAnisotropicFiltering2x": "2x",
"SettingsTabGraphicsAnisotropicFiltering4x": "4×", "SettingsTabGraphicsAnisotropicFiltering4x": "4×",
@@ -157,7 +152,7 @@
"SettingsTabGraphicsAnisotropicFiltering16x": "16x", "SettingsTabGraphicsAnisotropicFiltering16x": "16x",
"SettingsTabGraphicsResolutionScale": "مقياس الدقة", "SettingsTabGraphicsResolutionScale": "مقياس الدقة",
"SettingsTabGraphicsResolutionScaleCustom": "مخصص (لا ينصح به)", "SettingsTabGraphicsResolutionScaleCustom": "مخصص (لا ينصح به)",
"SettingsTabGraphicsResolutionScaleNative": "الأصل (720p/1080p)", "SettingsTabGraphicsResolutionScaleNative": "الأصل (720p/1080p)",
"SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)",
"SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)",
"SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (لا ينصح به)", "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (لا ينصح به)",
@@ -167,11 +162,11 @@
"SettingsTabGraphicsAspectRatio16x10": "16:10", "SettingsTabGraphicsAspectRatio16x10": "16:10",
"SettingsTabGraphicsAspectRatio21x9": "21:9", "SettingsTabGraphicsAspectRatio21x9": "21:9",
"SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatio32x9": "32:9",
"SettingsTabGraphicsAspectRatioStretch": "تمديد لتناسب النافذة", "SettingsTabGraphicsAspectRatioStretch": "تمدد لتلائم حجم النافذة",
"SettingsTabGraphicsDeveloperOptions": "خيارات المطور", "SettingsTabGraphicsDeveloperOptions": "خيارات المطور",
"SettingsTabGraphicsShaderDumpPath": "مسار تفريغ المظللات:", "SettingsTabGraphicsShaderDumpPath": "مسار تفريغ الرسومات:",
"SettingsTabLogging": "تسجيل", "SettingsTabLogging": "التسجيل",
"SettingsTabLoggingLogging": "تسجيل", "SettingsTabLoggingLogging": "التسجيل",
"SettingsTabLoggingEnableLoggingToFile": "تفعيل التسجيل إلى ملف", "SettingsTabLoggingEnableLoggingToFile": "تفعيل التسجيل إلى ملف",
"SettingsTabLoggingEnableStubLogs": "تفعيل سجلات الـStub", "SettingsTabLoggingEnableStubLogs": "تفعيل سجلات الـStub",
"SettingsTabLoggingEnableInfoLogs": "تفعيل سجلات المعلومات", "SettingsTabLoggingEnableInfoLogs": "تفعيل سجلات المعلومات",
@@ -179,8 +174,8 @@
"SettingsTabLoggingEnableErrorLogs": "تفعيل سجلات الأخطاء", "SettingsTabLoggingEnableErrorLogs": "تفعيل سجلات الأخطاء",
"SettingsTabLoggingEnableTraceLogs": "تفعيل سجلات التتبع", "SettingsTabLoggingEnableTraceLogs": "تفعيل سجلات التتبع",
"SettingsTabLoggingEnableGuestLogs": "تفعيل سجلات الضيوف", "SettingsTabLoggingEnableGuestLogs": "تفعيل سجلات الضيوف",
"SettingsTabLoggingEnableFsAccessLogs": "تمكين سجلات الوصول إلى نظام الملفات", "SettingsTabLoggingEnableFsAccessLogs": "تمكين سجلات الوصول لـ Fs",
"SettingsTabLoggingFsGlobalAccessLogMode": "وضع سجل الوصول العالمي لنظام الملفات:", "SettingsTabLoggingFsGlobalAccessLogMode": "وضع سجل وصول عالمي Fs :",
"SettingsTabLoggingDeveloperOptions": "خيارات المطور", "SettingsTabLoggingDeveloperOptions": "خيارات المطور",
"SettingsTabLoggingDeveloperOptionsNote": "تحذير: سوف يقلل من الأداء", "SettingsTabLoggingDeveloperOptionsNote": "تحذير: سوف يقلل من الأداء",
"SettingsTabLoggingGraphicsBackendLogLevel": "مستوى سجل خلفية الرسومات:", "SettingsTabLoggingGraphicsBackendLogLevel": "مستوى سجل خلفية الرسومات:",
@@ -190,15 +185,15 @@
"SettingsTabLoggingGraphicsBackendLogLevelAll": "الكل", "SettingsTabLoggingGraphicsBackendLogLevelAll": "الكل",
"SettingsTabLoggingEnableDebugLogs": "تمكين سجلات التصحيح", "SettingsTabLoggingEnableDebugLogs": "تمكين سجلات التصحيح",
"SettingsTabInput": "الإدخال", "SettingsTabInput": "الإدخال",
"SettingsTabInputEnableDockedMode": "تركيب بالمنصة", "SettingsTabInputEnableDockedMode": "مركب بالمنصة",
"SettingsTabInputDirectKeyboardAccess": "الوصول المباشر للوحة المفاتيح", "SettingsTabInputDirectKeyboardAccess": "الوصول المباشر إلى لوحة المفاتيح",
"SettingsButtonSave": "حفظ", "SettingsButtonSave": "حفظ",
"SettingsButtonClose": "إغلاق", "SettingsButtonClose": "إغلاق",
"SettingsButtonOk": "موافق", "SettingsButtonOk": "موافق",
"SettingsButtonCancel": "إلغاء", "SettingsButtonCancel": "إلغاء",
"SettingsButtonApply": "تطبيق", "SettingsButtonApply": "تطبيق",
"ControllerSettingsPlayer": "اللاعب", "ControllerSettingsPlayer": "اللاعب",
"ControllerSettingsPlayer1": "اللاعب 1", "ControllerSettingsPlayer1": "اللاعب ١",
"ControllerSettingsPlayer2": "اللاعب 2", "ControllerSettingsPlayer2": "اللاعب 2",
"ControllerSettingsPlayer3": "اللاعب 3", "ControllerSettingsPlayer3": "اللاعب 3",
"ControllerSettingsPlayer4": "اللاعب 4", "ControllerSettingsPlayer4": "اللاعب 4",
@@ -210,12 +205,12 @@
"ControllerSettingsInputDevice": "جهاز الإدخال", "ControllerSettingsInputDevice": "جهاز الإدخال",
"ControllerSettingsRefresh": "تحديث", "ControllerSettingsRefresh": "تحديث",
"ControllerSettingsDeviceDisabled": "معطل", "ControllerSettingsDeviceDisabled": "معطل",
"ControllerSettingsControllerType": "نوع وحدة التحكم", "ControllerSettingsControllerType": "نوع ذراع التحكم",
"ControllerSettingsControllerTypeHandheld": "محمول", "ControllerSettingsControllerTypeHandheld": "محمول",
"ControllerSettingsControllerTypeProController": "وحدة تحكم برو", "ControllerSettingsControllerTypeProController": "Pro Controller",
"ControllerSettingsControllerTypeJoyConPair": "زوج جوي كون", "ControllerSettingsControllerTypeJoyConPair": "اقتران جوي كون",
"ControllerSettingsControllerTypeJoyConLeft": "جوي كون اليسار ", "ControllerSettingsControllerTypeJoyConLeft": "يسار جوي كون",
"ControllerSettingsControllerTypeJoyConRight": " جوي كون اليمين", "ControllerSettingsControllerTypeJoyConRight": "يمين جوي كون",
"ControllerSettingsProfile": "الملف الشخصي", "ControllerSettingsProfile": "الملف الشخصي",
"ControllerSettingsProfileDefault": "افتراضي", "ControllerSettingsProfileDefault": "افتراضي",
"ControllerSettingsLoad": "تحميل", "ControllerSettingsLoad": "تحميل",
@@ -228,13 +223,13 @@
"ControllerSettingsButtonY": "Y", "ControllerSettingsButtonY": "Y",
"ControllerSettingsButtonPlus": "+", "ControllerSettingsButtonPlus": "+",
"ControllerSettingsButtonMinus": "-", "ControllerSettingsButtonMinus": "-",
"ControllerSettingsDPad": "أسهم الاتجاهات", "ControllerSettingsDPad": "لوحة الاتجاه",
"ControllerSettingsDPadUp": "اعلى", "ControllerSettingsDPadUp": "اعلى",
"ControllerSettingsDPadDown": "أسفل", "ControllerSettingsDPadDown": "أسفل",
"ControllerSettingsDPadLeft": "يسار", "ControllerSettingsDPadLeft": "يسار",
"ControllerSettingsDPadRight": "يمين", "ControllerSettingsDPadRight": "يمين",
"ControllerSettingsStickButton": "زر", "ControllerSettingsStickButton": "زر",
"ControllerSettingsStickUp": "فوق", "ControllerSettingsStickUp": "اعلى",
"ControllerSettingsStickDown": "أسفل", "ControllerSettingsStickDown": "أسفل",
"ControllerSettingsStickLeft": "يسار", "ControllerSettingsStickLeft": "يسار",
"ControllerSettingsStickRight": "يمين", "ControllerSettingsStickRight": "يمين",
@@ -244,11 +239,11 @@
"ControllerSettingsStickDeadzone": "المنطقة الميتة:", "ControllerSettingsStickDeadzone": "المنطقة الميتة:",
"ControllerSettingsLStick": "العصا اليسرى", "ControllerSettingsLStick": "العصا اليسرى",
"ControllerSettingsRStick": "العصا اليمنى", "ControllerSettingsRStick": "العصا اليمنى",
"ControllerSettingsTriggersLeft": "الأزندة اليسرى", "ControllerSettingsTriggersLeft": "المحفزات اليسرى",
"ControllerSettingsTriggersRight": "الأزندة اليمني", "ControllerSettingsTriggersRight": "المحفزات اليمني",
"ControllerSettingsTriggersButtonsLeft": "أزرار الزناد اليسرى", "ControllerSettingsTriggersButtonsLeft": "أزرار التحفيز اليسرى",
"ControllerSettingsTriggersButtonsRight": "أزرار الزناد اليمنى", "ControllerSettingsTriggersButtonsRight": "أزرار التحفيز اليمنى",
"ControllerSettingsTriggers": "أزندة", "ControllerSettingsTriggers": "المحفزات",
"ControllerSettingsTriggerL": "L", "ControllerSettingsTriggerL": "L",
"ControllerSettingsTriggerR": "R", "ControllerSettingsTriggerR": "R",
"ControllerSettingsTriggerZL": "ZL", "ControllerSettingsTriggerZL": "ZL",
@@ -263,128 +258,27 @@
"ControllerSettingsTriggerThreshold": "قوة التحفيز:", "ControllerSettingsTriggerThreshold": "قوة التحفيز:",
"ControllerSettingsMotion": "الحركة", "ControllerSettingsMotion": "الحركة",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "استخدام الحركة المتوافقة مع CemuHook", "ControllerSettingsMotionUseCemuhookCompatibleMotion": "استخدام الحركة المتوافقة مع CemuHook",
"ControllerSettingsMotionControllerSlot": "خانة وحدة التحكم:", "ControllerSettingsMotionControllerSlot": "خانة ذراع التحكم:",
"ControllerSettingsMotionMirrorInput": "إعادة الإدخال", "ControllerSettingsMotionMirrorInput": "إعادة الإدخال",
"ControllerSettingsMotionRightJoyConSlot": "خانة جويكون اليمين :", "ControllerSettingsMotionRightJoyConSlot": "خانة اليمين JoyCon :",
"ControllerSettingsMotionServerHost": "مضيف الخادم:", "ControllerSettingsMotionServerHost": "مضيف الخادم:",
"ControllerSettingsMotionGyroSensitivity": "حساسية مستشعر الحركة:", "ControllerSettingsMotionGyroSensitivity": "حساسية الغيرو:",
"ControllerSettingsMotionGyroDeadzone": "منطقة مستشعر الحركة الميتة:", "ControllerSettingsMotionGyroDeadzone": "منطقة الغيرو الميتة:",
"ControllerSettingsSave": "حفظ", "ControllerSettingsSave": "حفظ",
"ControllerSettingsClose": "إغلاق", "ControllerSettingsClose": "إغلاق",
"KeyUnknown": "مجهول",
"KeyShiftLeft": "زر Shift الأيسر",
"KeyShiftRight": "زر Shift الأيمن",
"KeyControlLeft": "زر Ctrl الأيسر",
"KeyMacControlLeft": "زر ⌃ الأيسر",
"KeyControlRight": "زر Ctrl الأيمن",
"KeyMacControlRight": "زر ⌃ الأيمن",
"KeyAltLeft": "زر Alt الأيسر",
"KeyMacAltLeft": "زر ⌥ الأيسر",
"KeyAltRight": "زر Alt الأيمن",
"KeyMacAltRight": "زر ⌥ الأيمن",
"KeyWinLeft": "زر ⊞ الأيسر",
"KeyMacWinLeft": "زر ⌘ الأيسر",
"KeyWinRight": "زر ⊞ الأيمن",
"KeyMacWinRight": "زر ⌘ الأيمن",
"KeyMenu": "زر القائمة",
"KeyUp": "فوق",
"KeyDown": "اسفل",
"KeyLeft": "يسار",
"KeyRight": "يمين",
"KeyEnter": "مفتاح الإدخال",
"KeyEscape": "زر Escape",
"KeySpace": "مسافة",
"KeyTab": "زر Tab",
"KeyBackSpace": "زر المسح للخلف",
"KeyInsert": "زر Insert",
"KeyDelete": "زر الحذف",
"KeyPageUp": "زر Page Up",
"KeyPageDown": "زر Page Down",
"KeyHome": "زر Home",
"KeyEnd": "زر End",
"KeyCapsLock": "زر الحروف الكبيرة",
"KeyScrollLock": "زر Scroll Lock",
"KeyPrintScreen": "زر Print Screen",
"KeyPause": "زر Pause",
"KeyNumLock": "زر Num Lock",
"KeyClear": "زر Clear",
"KeyKeypad0": "لوحة الأرقام 0",
"KeyKeypad1": "لوحة الأرقام 1",
"KeyKeypad2": "لوحة الأرقام 2",
"KeyKeypad3": "لوحة الأرقام 3",
"KeyKeypad4": "لوحة الأرقام 4",
"KeyKeypad5": "لوحة الأرقام 5",
"KeyKeypad6": "لوحة الأرقام 6",
"KeyKeypad7": "لوحة الأرقام 7",
"KeyKeypad8": "لوحة الأرقام 8",
"KeyKeypad9": "لوحة الأرقام 9",
"KeyKeypadDivide": "لوحة الأرقام علامة القسمة",
"KeyKeypadMultiply": "لوحة الأرقام علامة الضرب",
"KeyKeypadSubtract": "لوحة الأرقام علامة الطرح\n",
"KeyKeypadAdd": "لوحة الأرقام علامة الزائد",
"KeyKeypadDecimal": "لوحة الأرقام الفاصلة العشرية",
"KeyKeypadEnter": "لوحة الأرقام زر الإدخال",
"KeyNumber0": "٠",
"KeyNumber1": "١",
"KeyNumber2": "٢",
"KeyNumber3": "٣",
"KeyNumber4": "٤",
"KeyNumber5": "٥",
"KeyNumber6": "٦",
"KeyNumber7": "٧",
"KeyNumber8": "٨",
"KeyNumber9": "٩",
"KeyTilde": "~",
"KeyGrave": "`",
"KeyMinus": "-",
"KeyPlus": "+",
"KeyBracketLeft": "[",
"KeyBracketRight": "]",
"KeySemicolon": ";",
"KeyQuote": "\"",
"KeyComma": ",",
"KeyPeriod": ".",
"KeySlash": "/",
"KeyBackSlash": "\\",
"KeyUnbound": "غير مرتبط",
"GamepadLeftStick": "زر عصا التحكم اليسرى",
"GamepadRightStick": "زر عصا التحكم اليمنى",
"GamepadLeftShoulder": "زر الكتف الأيسر‫ L",
"GamepadRightShoulder": "زر الكتف الأيمن‫ R",
"GamepadLeftTrigger": "زر الزناد الأيسر‫ (ZL)",
"GamepadRightTrigger": "زر الزناد الأيمن‫ (ZR)",
"GamepadDpadUp": "فوق",
"GamepadDpadDown": "اسفل",
"GamepadDpadLeft": "يسار",
"GamepadDpadRight": "يمين",
"GamepadMinus": "-",
"GamepadPlus": "+",
"GamepadGuide": "دليل",
"GamepadMisc1": "متنوع",
"GamepadPaddle1": "Paddle 1",
"GamepadPaddle2": "Paddle 2",
"GamepadPaddle3": "Paddle 3",
"GamepadPaddle4": "Paddle 4",
"GamepadTouchpad": "لوحة اللمس",
"GamepadSingleLeftTrigger0": "زر الزناد الأيسر 0",
"GamepadSingleRightTrigger0": "زر الزناد الأيمن 0",
"GamepadSingleLeftTrigger1": "زر الزناد الأيسر 1",
"GamepadSingleRightTrigger1": "زر الزناد الأيمن 1",
"StickLeft": "عصا التحكم اليسرى",
"StickRight": "عصا التحكم اليمنى",
"UserProfilesSelectedUserProfile": "الملف الشخصي المحدد للمستخدم:", "UserProfilesSelectedUserProfile": "الملف الشخصي المحدد للمستخدم:",
"UserProfilesSaveProfileName": "حفظ اسم الملف الشخصي", "UserProfilesSaveProfileName": "حفظ اسم الملف الشخصي",
"UserProfilesChangeProfileImage": "تغيير صورة الملف الشخصي", "UserProfilesChangeProfileImage": "تغيير صورة الملف الشخصي",
"UserProfilesAvailableUserProfiles": "الملفات الشخصية للمستخدم المتاحة:", "UserProfilesAvailableUserProfiles": "الملفات الشخصية للمستخدم المتاحة:",
"UserProfilesAddNewProfile": "إنشاء ملف الشخصي", "UserProfilesAddNewProfile": "أنشئ ملف شخصي",
"UserProfilesDelete": "حذف", "UserProfilesDelete": "حذف",
"UserProfilesClose": "إغلاق", "UserProfilesClose": "إغلاق",
"ProfileNameSelectionWatermark": "اختر اسم مستعار", "ProfileNameSelectionWatermark": "اختر اسم مستعار",
"ProfileImageSelectionTitle": "تحديد صورة الملف الشخصي", "ProfileImageSelectionTitle": "تحديد صورة الملف الشخصي",
"ProfileImageSelectionHeader": "اختر صورة الملف الشخصي", "ProfileImageSelectionHeader": "اختر صورة الملف الشخصي",
"ProfileImageSelectionNote": "يمكنك استيراد صورة ملف شخصي مخصصة، أو تحديد صورة رمزية من البرامج الثابتة للنظام", "ProfileImageSelectionNote": "يمكنك استيراد صورة ملف تعريف مخصصة، أو تحديد صورة رمزية من البرامج الثابتة للنظام",
"ProfileImageSelectionImportImage": "استيراد ملف الصورة", "ProfileImageSelectionImportImage": "استيراد ملف الصورة",
"ProfileImageSelectionSelectAvatar": "حدد الصورة الرمزية من البرنامج الثابتة", "ProfileImageSelectionSelectAvatar": "حدد الصورة الرمزية للبرنامج الثابت",
"InputDialogTitle": "حوار الإدخال", "InputDialogTitle": "حوار الإدخال",
"InputDialogOk": "موافق", "InputDialogOk": "موافق",
"InputDialogCancel": "إلغاء", "InputDialogCancel": "إلغاء",
@@ -395,13 +289,13 @@
"AvatarSetBackgroundColor": "تعيين لون الخلفية", "AvatarSetBackgroundColor": "تعيين لون الخلفية",
"AvatarClose": "إغلاق", "AvatarClose": "إغلاق",
"ControllerSettingsLoadProfileToolTip": "تحميل الملف الشخصي", "ControllerSettingsLoadProfileToolTip": "تحميل الملف الشخصي",
"ControllerSettingsAddProfileToolTip": "إضافة ملف شخصي", "ControllerSettingsAddProfileToolTip": "إضافة ملف تعريف",
"ControllerSettingsRemoveProfileToolTip": "إزالة الملف الشخصي", "ControllerSettingsRemoveProfileToolTip": "إزالة ملف التعريف",
"ControllerSettingsSaveProfileToolTip": "حفظ الملف الشخصي", "ControllerSettingsSaveProfileToolTip": "حفظ ملف التعريف",
"MenuBarFileToolsTakeScreenshot": "أخذ لقطة للشاشة", "MenuBarFileToolsTakeScreenshot": "أخذ لقطة للشاشة",
"MenuBarFileToolsHideUi": "إخفاء واجهة المستخدم", "MenuBarFileToolsHideUi": "إخفاء واجهة المستخدم",
"GameListContextMenuRunApplication": "تشغيل التطبيق", "GameListContextMenuRunApplication": "تشغيل التطبيق",
"GameListContextMenuToggleFavorite": عيين كمفضل", "GameListContextMenuToggleFavorite": بديل المفضلة",
"GameListContextMenuToggleFavoriteToolTip": "تبديل الحالة المفضلة للعبة", "GameListContextMenuToggleFavoriteToolTip": "تبديل الحالة المفضلة للعبة",
"SettingsTabGeneralTheme": "السمة:", "SettingsTabGeneralTheme": "السمة:",
"SettingsTabGeneralThemeDark": "داكن", "SettingsTabGeneralThemeDark": "داكن",
@@ -410,44 +304,44 @@
"ControllerSettingsRumble": "الاهتزاز", "ControllerSettingsRumble": "الاهتزاز",
"ControllerSettingsRumbleStrongMultiplier": "مضاعف اهتزاز قوي", "ControllerSettingsRumbleStrongMultiplier": "مضاعف اهتزاز قوي",
"ControllerSettingsRumbleWeakMultiplier": "مضاعف اهتزاز ضعيف", "ControllerSettingsRumbleWeakMultiplier": "مضاعف اهتزاز ضعيف",
"DialogMessageSaveNotAvailableMessage": "لا توجد بيانات الحفظ لـ {0} [{1:x16}]", "DialogMessageSaveNotAvailableMessage": "لا يوجد حفظ لـ {0} [{1:x16}]",
"DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء بيانات الحفظ لهذه اللعبة؟", "DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء حفظ لهذه اللعبة؟",
"DialogConfirmationTitle": "ريوجينكس - تأكيد", "DialogConfirmationTitle": "Ryujinx - تأكيد",
"DialogUpdaterTitle": "ريوجينكس - المحدث", "DialogUpdaterTitle": "تحديث Ryujinx",
"DialogErrorTitle": "ريوجينكس - خطأ", "DialogErrorTitle": "Ryujinx - خطأ",
"DialogWarningTitle": "ريوجينكس - تحذير", "DialogWarningTitle": "Ryujinx - تحذير",
"DialogExitTitle": "ريوجينكس - الخروج", "DialogExitTitle": "Ryujinx - الخروج",
"DialogErrorMessage": "واجه ريوجينكس خطأ", "DialogErrorMessage": "واجه Ryujinx خطأ",
"DialogExitMessage": "هل أنت متأكد من أنك تريد إغلاق ريوجينكس؟", "DialogExitMessage": "هل أنت متأكد من أنك تريد إغلاق Ryujinx؟",
"DialogExitSubMessage": "سيتم فقدان كافة البيانات غير المحفوظة!", "DialogExitSubMessage": "سيتم فقدان كافة البيانات غير المحفوظة!",
"DialogMessageCreateSaveErrorMessage": "حدث خطأ أثناء إنشاء بيانات الحفظ المحددة: {0}", "DialogMessageCreateSaveErrorMessage": "حدث خطأ أثناء إنشاء المحفوظة المحددة: {0}",
"DialogMessageFindSaveErrorMessage": "حدث خطأ أثناء البحث عن بيانات الحفظ المحددة: {0}", "DialogMessageFindSaveErrorMessage": "حدث خطأ أثناء البحث عن البيانات المحفوظة المحددة: {0}",
"FolderDialogExtractTitle": "اختر المجلد الذي تريد الاستخراج إليه", "FolderDialogExtractTitle": "اختر المجلد الذي تريد الاستخراج إليه",
"DialogNcaExtractionMessage": "استخراج قسم {0} من {1}...", "DialogNcaExtractionMessage": "استخراج قسم {0} من {1}...",
"DialogNcaExtractionTitle": "ريوجينكس - مستخرج قسم NCA", "DialogNcaExtractionTitle": "Ryujinx - مستخرج قسم NCA",
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "فشل الاستخراج. لم يكن NCA الرئيسي موجودا في الملف المحدد.", "DialogNcaExtractionMainNcaNotFoundErrorMessage": "فشل الاستخراج. لم يكن NCA الرئيسي موجودًا في الملف المحدد.",
"DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف التسجيل لمزيد من المعلومات.", "DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف السجل لمزيد من المعلومات.",
"DialogNcaExtractionSuccessMessage": "تم الاستخراج بنجاح.", "DialogNcaExtractionSuccessMessage": "تم الاستخراج بنجاح.",
"DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار ريوجينكس الحالي.", "DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار Ryujinx الحالي.",
"DialogUpdaterCancelUpdateMessage": "إلغاء التحديث", "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث",
"DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من ريوجينكس!", "DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من Ryujinx!",
"DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار غيت هاب. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة إجراءات غيت هاب. جرب مجددا بعد دقائق.", "DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار GitHub. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة GitHub Actions. جرب مجددا بعد دقائق.",
"DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار ريوجينكس المستلم من إصدار غيت هاب.", "DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار Ryujinx المستلم من إصدار Github.",
"DialogUpdaterDownloadingMessage": "جاري تنزيل التحديث...", "DialogUpdaterDownloadingMessage": "تحميل التحديث...",
"DialogUpdaterExtractionMessage": "جاري استخراج التحديث...", "DialogUpdaterExtractionMessage": "استخراج التحديث...",
"DialogUpdaterRenamingMessage": "إعادة تسمية التحديث...", "DialogUpdaterRenamingMessage": "إعادة تسمية التحديث...",
"DialogUpdaterAddingFilesMessage": "إضافة تحديث جديد...", "DialogUpdaterAddingFilesMessage": "إضافة تحديث جديد...",
"DialogUpdaterCompleteMessage": "اكتمل التحديث", "DialogUpdaterCompleteMessage": "اكتمل التحديث",
"DialogUpdaterRestartMessage": "هل تريد إعادة تشغيل ريوجينكس الآن؟", "DialogUpdaterRestartMessage": "هل تريد إعادة تشغيل Ryujinx الآن؟",
"DialogUpdaterNoInternetMessage": "أنت غير متصل بالإنترنت.", "DialogUpdaterNoInternetMessage": "أنت غير متصل بالإنترنت.",
"DialogUpdaterNoInternetSubMessage": "يرجى التحقق من أن لديك اتصال إنترنت فعال!", "DialogUpdaterNoInternetSubMessage": "يرجى التحقق من أن لديك اتصال إنترنت فعال!",
"DialogUpdaterDirtyBuildMessage": "لا يمكنك تحديث نسخة القذرة من ريوجينكس!", "DialogUpdaterDirtyBuildMessage": "لا يمكنك تحديث البناء القذر من Ryujinx!",
"DialogUpdaterDirtyBuildSubMessage": "الرجاء تحميل ريوجينكس من https://ryujinx.org إذا كنت تبحث عن إصدار مدعوم.", "DialogUpdaterDirtyBuildSubMessage": "الرجاء تحميل Ryujinx على https://ryujinx.org/ إذا كنت تبحث عن إصدار مدعوم.",
"DialogRestartRequiredMessage": "يتطلب إعادة التشغيل", "DialogRestartRequiredMessage": "يتطلب إعادة التشغيل",
"DialogThemeRestartMessage": "تم حفظ السمة. إعادة التشغيل مطلوبة لتطبيق السمة.", "DialogThemeRestartMessage": "تم حفظ السمة. إعادة التشغيل مطلوبة لتطبيق السمة.",
"DialogThemeRestartSubMessage": "هل تريد إعادة التشغيل", "DialogThemeRestartSubMessage": "هل تريد إعادة التشغيل",
"DialogFirmwareInstallEmbeddedMessage": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})", "DialogFirmwareInstallEmbeddedMessage": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})",
"DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن ريوجينكس كان قادرا على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.", "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن Ryujinx كان قادراً على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.",
"DialogFirmwareNoFirmwareInstalledMessage": "لا يوجد برنامج ثابت مثبت", "DialogFirmwareNoFirmwareInstalledMessage": "لا يوجد برنامج ثابت مثبت",
"DialogFirmwareInstalledMessage": "تم تثبيت البرنامج الثابت {0}", "DialogFirmwareInstalledMessage": "تم تثبيت البرنامج الثابت {0}",
"DialogInstallFileTypesSuccessMessage": "تم تثبيت أنواع الملفات بنجاح!", "DialogInstallFileTypesSuccessMessage": "تم تثبيت أنواع الملفات بنجاح!",
@@ -455,38 +349,38 @@
"DialogUninstallFileTypesSuccessMessage": "تم إلغاء تثبيت أنواع الملفات بنجاح!", "DialogUninstallFileTypesSuccessMessage": "تم إلغاء تثبيت أنواع الملفات بنجاح!",
"DialogUninstallFileTypesErrorMessage": "فشل إلغاء تثبيت أنواع الملفات.", "DialogUninstallFileTypesErrorMessage": "فشل إلغاء تثبيت أنواع الملفات.",
"DialogOpenSettingsWindowLabel": "فتح نافذة الإعدادات", "DialogOpenSettingsWindowLabel": "فتح نافذة الإعدادات",
"DialogControllerAppletTitle": "تطبيق وحدة التحكم المصغر", "DialogControllerAppletTitle": "برنامج التحكم",
"DialogMessageDialogErrorExceptionMessage": "خطأ في عرض مربع حوار الرسالة: {0}", "DialogMessageDialogErrorExceptionMessage": "خطأ في عرض مربع حوار الرسالة: {0}",
"DialogSoftwareKeyboardErrorExceptionMessage": "خطأ في عرض لوحة مفاتيح البرامج: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "خطأ في عرض لوحة مفاتيح البرامج: {0}",
"DialogErrorAppletErrorExceptionMessage": "خطأ في عرض مربع حوار خطأ التطبيق المصغر: {0}", "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}",
"DialogUserErrorDialogMessage": "{0}: {1}", "DialogUserErrorDialogMessage": "{0}: {1}",
"DialogUserErrorDialogInfoMessage": "لمزيد من المعلومات حول كيفية إصلاح هذا الخطأ، اتبع دليل الإعداد الخاص بنا.", "DialogUserErrorDialogInfoMessage": "لمزيد من المعلومات حول كيفية إصلاح هذا الخطأ، اتبع دليل الإعداد الخاص بنا.",
"DialogUserErrorDialogTitle": "خطأ ريوجينكس ({0})", "DialogUserErrorDialogTitle": "خطأ ريوجينكس ({0})",
"DialogAmiiboApiTitle": "أميبو API", "DialogAmiiboApiTitle": "أميبو API",
"DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من API.", "DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من واجهة برمجة التطبيقات.",
"DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API أميبو. قد تكون الخدمة معطلة أو قد تحتاج إلى التحقق من اتصالك بالإنترنت.", "DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API Amiibo. قد تكون الخدمة متوقفة أو قد تحتاج إلى التحقق من اتصال الإنترنت الخاص بك على الإنترنت.",
"DialogProfileInvalidProfileErrorMessage": "الملف الشخصي {0} غير متوافق مع نظام تكوين الإدخال الحالي.", "DialogProfileInvalidProfileErrorMessage": "الملف الشخصي {0} غير متوافق مع نظام تكوين الإدخال الحالي.",
"DialogProfileDefaultProfileOverwriteErrorMessage": "لا يمكن الكتابة فوق الملف الشخصي الافتراضي", "DialogProfileDefaultProfileOverwriteErrorMessage": "لا يمكن الكتابة فوق الملف الشخصي الافتراضي",
"DialogProfileDeleteProfileTitle": "حذف الملف الشخصي", "DialogProfileDeleteProfileTitle": "حذف ملف التعريف",
"DialogProfileDeleteProfileMessage": "هذا الإجراء لا رجعة فيه، هل أنت متأكد من أنك تريد المتابعة؟", "DialogProfileDeleteProfileMessage": "هذا الإجراء لا رجعة فيه، هل أنت متأكد من أنك تريد المتابعة؟",
"DialogWarning": "تحذير", "DialogWarning": "تحذير",
"DialogPPTCDeletionMessage": "أنت على وشك الإنتظار لإعادة بناء ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) عند الإقلاع التالي لـ:\n\n{0}\n\nأمتأكد من رغبتك في المتابعة؟", "DialogPPTCDeletionMessage": "أنت على وشك الإنتظار لإعادة بناء PTC على التمهيد التالي :\n\n{0}\n\nهل أنت متأكد من أنك تريد المتابعة؟",
"DialogPPTCDeletionErrorMessage": "خطأ خلال تنظيف ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) في {0}: {1}", "DialogPPTCDeletionErrorMessage": "خطأ في إزالة ذاكرة التخزين المؤقت PPTC في {0}: {1}",
"DialogShaderDeletionMessage": "أنت على وشك حذف ذاكرة المظللات المؤقتة ل:\n\n{0}\n\nهل انت متأكد انك تريد المتابعة؟", "DialogShaderDeletionMessage": "أنت على وشك حذف ذاكرة التخزين المؤقت لـ Shader من أجل:\n\n{0}\n\nهل انت متأكد انك تريد المتابعة؟",
"DialogShaderDeletionErrorMessage": "حدث خطأ أثناء تنظيف ذاكرة المظللات المؤقتة في {0}: {1}", "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}",
"DialogRyujinxErrorMessage": "واجه ريوجينكس خطأ", "DialogRyujinxErrorMessage": "واجه Ryujinx خطأ",
"DialogInvalidTitleIdErrorMessage": "خطأ في واجهة المستخدم: اللعبة المحددة لم يكن لديها معرف عنوان صالح", "DialogInvalidTitleIdErrorMessage": "خطأ في واجهة المستخدم: اللعبة المحددة لم يكن لديها معرف عنوان صالح",
"DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "لم يتم العثور على برنامج ثابت للنظام صالح في {0}.", "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "لم يتم العثور على فريموير للنظام صالح في {0}.",
"DialogFirmwareInstallerFirmwareInstallTitle": "تثبيت البرنامج الثابت {0}", "DialogFirmwareInstallerFirmwareInstallTitle": "تثبيت البرنامج الثابت {0}",
"DialogFirmwareInstallerFirmwareInstallMessage": "سيتم تثبيت إصدار النظام {0}.", "DialogFirmwareInstallerFirmwareInstallMessage": "سيتم تثبيت إصدار النظام {0}.",
"DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nهذا سيحل محل إصدار النظام الحالي {0}.", "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nهذا سيحل محل إصدار النظام الحالي {0}.",
"DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\nهل تريد المتابعة؟", "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\nهل تريد المتابعة؟",
"DialogFirmwareInstallerFirmwareInstallWaitMessage": "تثبيت البرنامج الثابت...", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "تثبيت البرنامج الثابت...",
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "تم تثبيت إصدار النظام {0} بنجاح.", "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "تم تثبيت إصدار النظام {0} بنجاح.",
"DialogUserProfileDeletionWarningMessage": "لن تكون هناك ملفات الشخصية أخرى لفتحها إذا تم حذف الملف الشخصي المحدد", "DialogUserProfileDeletionWarningMessage": "لن تكون هناك ملفات تعريف أخرى لفتحها إذا تم حذف الملف الشخصي المحدد",
"DialogUserProfileDeletionConfirmMessage": "هل تريد حذف الملف الشخصي المحدد", "DialogUserProfileDeletionConfirmMessage": "هل تريد حذف الملف الشخصي المحدد",
"DialogUserProfileUnsavedChangesTitle": "تحذير - التغييرات غير محفوظة", "DialogUserProfileUnsavedChangesTitle": "تحذير - التغييرات غير المحفوظة",
"DialogUserProfileUnsavedChangesMessage": "لقد قمت بإجراء تغييرات على الملف الشخصي لهذا المستخدم هذا ولم يتم حفظها.", "DialogUserProfileUnsavedChangesMessage": "لقد قمت بإجراء تغييرات على ملف تعريف المستخدم هذا ولم يتم حفظها.",
"DialogUserProfileUnsavedChangesSubMessage": "هل تريد تجاهل التغييرات؟", "DialogUserProfileUnsavedChangesSubMessage": "هل تريد تجاهل التغييرات؟",
"DialogControllerSettingsModifiedConfirmMessage": "تم تحديث إعدادات وحدة التحكم الحالية.", "DialogControllerSettingsModifiedConfirmMessage": "تم تحديث إعدادات وحدة التحكم الحالية.",
"DialogControllerSettingsModifiedConfirmSubMessage": "هل تريد الحفظ ؟", "DialogControllerSettingsModifiedConfirmSubMessage": "هل تريد الحفظ ؟",
@@ -494,29 +388,29 @@
"DialogModAlreadyExistsMessage": "التعديل موجود بالفعل", "DialogModAlreadyExistsMessage": "التعديل موجود بالفعل",
"DialogModInvalidMessage": "المجلد المحدد لا يحتوي على تعديل!", "DialogModInvalidMessage": "المجلد المحدد لا يحتوي على تعديل!",
"DialogModDeleteNoParentMessage": "فشل الحذف: لم يمكن العثور على المجلد الرئيسي للتعديل\"{0}\"!", "DialogModDeleteNoParentMessage": "فشل الحذف: لم يمكن العثور على المجلد الرئيسي للتعديل\"{0}\"!",
"DialogDlcNoDlcErrorMessage": "الملف المحدد لا يحتوي على محتوى إضافي للعنوان المحدد!", "DialogDlcNoDlcErrorMessage": "الملف المحدد لا يحتوي على DLC للعنوان المحدد!",
"DialogPerformanceCheckLoggingEnabledMessage": "لقد تم تمكين تسجيل التتبع، والذي تم تصميمه ليتم استخدامه من قبل المطورين فقط.", "DialogPerformanceCheckLoggingEnabledMessage": "لقد تم تمكين تسجيل التتبع، والذي تم تصميمه ليتم استخدامه من قبل المطورين فقط.",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تسجيل التتبع. هل ترغب في تعطيل تسجيل التتبع الآن؟", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تسجيل التتبع. هل ترغب في تعطيل تسجيل التتبع الآن؟",
"DialogPerformanceCheckShaderDumpEnabledMessage": "لقد قمت بتمكين تفريغ المظللات، والذي تم تصميمه ليستخدمه المطورون فقط.", "DialogPerformanceCheckShaderDumpEnabledMessage": "لقد قمت بتمكين تفريغ التظليل، والذي تم تصميمه ليستخدمه المطورون فقط.",
"DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تفريغ المظللات. هل ترغب في تعطيل تفريغ المظللات الآن؟", "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?",
"DialogLoadAppGameAlreadyLoadedMessage": "تم تحميل لعبة بالفعل", "DialogLoadAppGameAlreadyLoadedMessage": "تم تحميل لعبة بالفعل",
"DialogLoadAppGameAlreadyLoadedSubMessage": "الرجاء إيقاف المحاكاة أو إغلاق المحاكي قبل بدء لعبة أخرى.", "DialogLoadAppGameAlreadyLoadedSubMessage": "الرجاء إيقاف المحاكاة أو إغلاق المحاكي قبل بدء لعبة أخرى.",
"DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للعنوان المحدد!", "DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للملف المحدد!",
"DialogSettingsBackendThreadingWarningTitle": "تحذير - خلفية متعددة المسارات", "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading",
"DialogSettingsBackendThreadingWarningMessage": "يجب إعادة تشغيل ريوجينكس بعد تغيير هذا الخيار حتى يتم تطبيقه بالكامل. اعتمادا على النظام الأساسي الخاص بك، قد تحتاج إلى تعطيل تعدد المسارات الخاص ببرنامج الرسومات التشغيل الخاص بك يدويًا عند استخدام الخاص بريوجينكس.", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.",
"DialogModManagerDeletionWarningMessage": "أنت على وشك حذف التعديل: {0}\n\nهل انت متأكد انك تريد المتابعة؟", "DialogModManagerDeletionWarningMessage": "أنت على وشك حذف التعديل: {0}\n\nهل انت متأكد انك تريد المتابعة؟",
"DialogModManagerDeletionAllWarningMessage": "أنت على وشك حذف كافة التعديلات لهذا العنوان.\n\nهل انت متأكد انك تريد المتابعة؟", "DialogModManagerDeletionAllWarningMessage": "أنت على وشك حذف كافة التعديلات لهذا العنوان.\n\nهل انت متأكد انك تريد المتابعة؟",
"SettingsTabGraphicsFeaturesOptions": "المميزات", "SettingsTabGraphicsFeaturesOptions": "المميزات",
"SettingsTabGraphicsBackendMultithreading": "تعدد المسارات لخلفية الرسومات:", "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:",
"CommonAuto": "تلقائي", "CommonAuto": "تلقائي",
"CommonOff": "معطل", "CommonOff": "إيقاف",
"CommonOn": "تشغيل", "CommonOn": "تشغيل",
"InputDialogYes": "نعم", "InputDialogYes": "نعم",
"InputDialogNo": "لا", "InputDialogNo": "لا",
"DialogProfileInvalidProfileNameErrorMessage": "يحتوي اسم الملف على أحرف غير صالحة. يرجى المحاولة مرة أخرى.", "DialogProfileInvalidProfileNameErrorMessage": "يحتوي اسم الملف على أحرف غير صالحة. يرجى المحاولة مرة أخرى.",
"MenuBarOptionsPauseEmulation": "إيقاف مؤقت", "MenuBarOptionsPauseEmulation": "إيقاف مؤقت",
"MenuBarOptionsResumeEmulation": "استئناف", "MenuBarOptionsResumeEmulation": "استئناف",
"AboutUrlTooltipMessage": "انقر لفتح موقع ريوجينكس في متصفحك الافتراضي.", "AboutUrlTooltipMessage": "انقر لفتح موقع Ryujinx في متصفحك الافتراضي.",
"AboutDisclaimerMessage": "ريوجينكس لا ينتمي إلى نينتندو™،\nأو أي من شركائها بأي شكل من الأشكال.", "AboutDisclaimerMessage": "ريوجينكس لا ينتمي إلى نينتندو™،\nأو أي من شركائها بأي شكل من الأشكال.",
"AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) يتم \nاستخدامه في محاكاة أمبيو لدينا.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) يتم \nاستخدامه في محاكاة أمبيو لدينا.",
"AboutPatreonUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في باتريون في متصفحك الافتراضي.", "AboutPatreonUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في باتريون في متصفحك الافتراضي.",
@@ -525,18 +419,18 @@
"AboutTwitterUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في تويتر في متصفحك الافتراضي.", "AboutTwitterUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في تويتر في متصفحك الافتراضي.",
"AboutRyujinxAboutTitle": "حول:", "AboutRyujinxAboutTitle": "حول:",
"AboutRyujinxAboutContent": "ريوجينكس هو محاكي لجهاز نينتندو سويتش™.\nمن فضلك ادعمنا على باتريون.\nاحصل على آخر الأخبار على تويتر أو ديسكورد.\nيمكن للمطورين المهتمين بالمساهمة معرفة المزيد على غيت هاب أو ديسكورد.", "AboutRyujinxAboutContent": "ريوجينكس هو محاكي لجهاز نينتندو سويتش™.\nمن فضلك ادعمنا على باتريون.\nاحصل على آخر الأخبار على تويتر أو ديسكورد.\nيمكن للمطورين المهتمين بالمساهمة معرفة المزيد على غيت هاب أو ديسكورد.",
"AboutRyujinxMaintainersTitle": تم صيانته بواسطة:", "AboutRyujinxMaintainersTitle": "تم إصلاحها بواسطة:",
"AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.", "AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.",
"AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:", "AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:",
"AmiiboSeriesLabel": "مجموعة أميبو", "AmiiboSeriesLabel": "مجموعة أميبو",
"AmiiboCharacterLabel": "شخصية", "AmiiboCharacterLabel": "شخصية",
"AmiiboScanButtonLabel": "فحصه", "AmiiboScanButtonLabel": "فحصه",
"AmiiboOptionsShowAllLabel": "إظهار كل أميبو", "AmiiboOptionsShowAllLabel": "إظهار كل أميبو",
"AmiiboOptionsUsRandomTagLabel": "هاك: استخدم علامة Uuid عشوائية ", "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid",
"DlcManagerTableHeadingEnabledLabel": "مفعل", "DlcManagerTableHeadingEnabledLabel": "مفعل",
"DlcManagerTableHeadingTitleIdLabel": "معرف العنوان", "DlcManagerTableHeadingTitleIdLabel": "معرف العنوان",
"DlcManagerTableHeadingContainerPathLabel": "مسار الحاوية", "DlcManagerTableHeadingContainerPathLabel": "مسار الحاوية",
"DlcManagerTableHeadingFullPathLabel": "المسار كاملا", "DlcManagerTableHeadingFullPathLabel": "المسار كاملاً",
"DlcManagerRemoveAllButton": "حذف الكل", "DlcManagerRemoveAllButton": "حذف الكل",
"DlcManagerEnableAllButton": "تشغيل الكل", "DlcManagerEnableAllButton": "تشغيل الكل",
"DlcManagerDisableAllButton": "تعطيل الكل", "DlcManagerDisableAllButton": "تعطيل الكل",
@@ -546,100 +440,100 @@
"CommonSort": "فرز", "CommonSort": "فرز",
"CommonShowNames": "عرض الأسماء", "CommonShowNames": "عرض الأسماء",
"CommonFavorite": "المفضلة", "CommonFavorite": "المفضلة",
"OrderAscending": "تصاعدي", "OrderAscending": رتيب تصاعدي",
"OrderDescending": "تنازلي", "OrderDescending": رتيب تنازلي",
"SettingsTabGraphicsFeatures": "الميزات والتحسينات", "SettingsTabGraphicsFeatures": "الميزات والتحسينات",
"ErrorWindowTitle": "نافذة الخطأ", "ErrorWindowTitle": "نافذة الخطأ",
"ToggleDiscordTooltip": "اختر ما إذا كنت تريد عرض ريوجينكس في نشاط ديسكورد \"يتم تشغيله حاليا\" أم لا", "ToggleDiscordTooltip": "اختر ما إذا كنت تريد عرض ريوجينكس في نشاط ديسكورد \"يتم تشغيله حاليًا\" أم لا",
"AddGameDirBoxTooltip": "أدخل مجلد اللعبة لإضافته إلى القائمة", "AddGameDirBoxTooltip": "أدخل دليل اللعبة لإضافته إلى القائمة",
"AddGameDirTooltip": "إضافة مجلد اللعبة إلى القائمة", "AddGameDirTooltip": "إضافة دليل اللعبة إلى القائمة",
"RemoveGameDirTooltip": "إزالة مجلد اللعبة المحدد", "RemoveGameDirTooltip": "إزالة دليل اللعبة المحدد",
"CustomThemeCheckTooltip": "استخدم سمة أفالونيا المخصصة لواجهة المستخدم الرسومية لتغيير مظهر قوائم المحاكي", "CustomThemeCheckTooltip": "استخدم سمة أفالونيا المخصصة لواجهة المستخدم الرسومية لتغيير مظهر قوائم المحاكي",
"CustomThemePathTooltip": "مسار سمة واجهة المستخدم المخصصة", "CustomThemePathTooltip": "مسار سمة واجهة المستخدم المخصصة",
"CustomThemeBrowseTooltip": "تصفح للحصول على سمة واجهة المستخدم المخصصة", "CustomThemeBrowseTooltip": "تصفح للحصول على سمة واجهة المستخدم المخصصة",
"DockModeToggleTooltip": "يجعل وضع تركيب بالمنصة النظام الذي تمت محاكاته بمثابة جهاز نينتندو سويتش الذي تم تركيبه بالمنصة. يؤدي هذا إلى تحسين الدقة الرسومية في معظم الألعاب. على العكس من ذلك، سيؤدي تعطيل هذا إلى جعل النظام الذي تمت محاكاته يعمل كجهاز نينتندو سويتش محمول، مما يقلل من جودة الرسومات.\n\nقم بتكوين عناصر تحكم اللاعب 1 إذا كنت تخطط لاستخدام وضع تركيب بالمنصة؛ قم بتكوين عناصر التحكم المحمولة إذا كنت تخطط لاستخدام الوضع المحمول.\n\nاتركه مشغل إذا لم تكن متأكدا.", "DockModeToggleTooltip": "يجعل وضع الإرساء النظام الذي تمت محاكاته بمثابة جهاز نينتندو سويتش الذي تم إرساءه. يؤدي هذا إلى تحسين الدقة الرسومية في معظم الألعاب. على العكس من ذلك، سيؤدي تعطيل هذا إلى جعل النظام الذي تمت محاكاته يعمل كجهاز نينتندو سويتش محمول، مما يقلل من جودة الرسومات.\n\nقم بتكوين عناصر تحكم اللاعب 1 إذا كنت تخطط لاستخدام وضع الإرساء؛ قم بتكوين عناصر التحكم المحمولة إذا كنت تخطط لاستخدام الوضع المحمول.\n\nاتركه مشغل إذا لم تكن متأكدًا.",
"DirectKeyboardTooltip": "دعم الوصول المباشر للوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", "DirectKeyboardTooltip": "دعم الوصول المباشر إلى لوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.",
"DirectMouseTooltip": "دعم الوصول المباشر للوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", "DirectMouseTooltip": "دعم الوصول المباشر إلى لوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.",
"RegionTooltip": "تغيير منطقة النظام", "RegionTooltip": "تغيير منطقة النظام",
"LanguageTooltip": "تغيير لغة النظام", "LanguageTooltip": "تغيير لغة النظام",
"TimezoneTooltip": "تغيير النطاق الزمني للنظام", "TimezoneTooltip": "تغيير المنطقة الزمنية للنظام",
"TimeTooltip": "تغيير وقت النظام", "TimeTooltip": "تغيير وقت النظام",
"VSyncToggleTooltip": "محاكاة المزامنة العمودية للجهاز. في الأساس محدد الإطار لغالبية الألعاب؛ قد يؤدي تعطيله إلى تشغيل الألعاب بسرعة أعلى أو جعل شاشات التحميل تستغرق وقتا أطول أو تتعطل.\n\nيمكن تبديله داخل اللعبة باستخدام مفتاح التشغيل السريع الذي تفضله (F1 افتراضيا). نوصي بالقيام بذلك إذا كنت تخطط لتعطيله.\n\nاتركه ممكنا إذا لم تكن متأكدا.", "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"PptcToggleTooltip": "يحفظ وظائف JIT المترجمة بحيث لا تحتاج إلى ترجمتها في كل مرة يتم فيها تحميل اللعبة.\n\nيقلل من التقطيع ويسرع بشكل ملحوظ أوقات التشغيل بعد التشغيل الأول للعبة.\n\nاتركه ممكنا إذا لم تكن متأكدا.", "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.",
"FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.", "FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.",
"AudioBackendTooltip": "يغير الواجهة الخلفية المستخدمة لتقديم الصوت.\n\nSDL2 هو الخيار المفضل، بينما يتم استخدام OpenAL وSoundIO كبديلين. زائف لن يكون لها صوت.\n\nاضبط على SDL2 إذا لم تكن متأكدا.", "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.",
"MemoryManagerTooltip": "تغيير كيفية تعيين ذاكرة الضيف والوصول إليها. يؤثر بشكل كبير على أداء وحدة المعالجة المركزية التي تمت محاكاتها.\n\nاضبط على المضيف غير محدد إذا لم تكن متأكدا.", "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.",
"MemoryManagerSoftwareTooltip": "استخدام جدول الصفحات البرمجي لترجمة العناوين. أعلى دقة ولكن أبطأ أداء.", "MemoryManagerSoftwareTooltip": "استخدام جدول الصفحات البرمجي لترجمة العناوين. أعلى دقة ولكن أبطأ أداء.",
"MemoryManagerHostTooltip": "تعيين الذاكرة مباشرة في مساحة عنوان المضيف. تجميع وتنفيذ JIT أسرع بكثير.", "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.",
"MemoryManagerUnsafeTooltip": "تعيين الذاكرة مباشرة، ولكن لا تخفي العنوان داخل مساحة عنوان الضيف قبل الوصول. أسرع، ولكن على حساب السلامة. يمكن لتطبيق الضيف الوصول إلى الذاكرة من أي مكان في ريوجينكس، لذا قم بتشغيل البرامج التي تثق بها فقط مع هذا الوضع.", "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.",
"UseHypervisorTooltip": "استخدم هايبرڤايزور بدلا من JIT. يعمل على تحسين الأداء بشكل كبير عند توفره، ولكنه قد يكون غير مستقر في حالته الحالية.", "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.",
"DRamTooltip": "يستخدم تخطيط وضع الذاكرة البديل لتقليد نموذج سويتش المطورين.\n\nيعد هذا مفيدا فقط لحزم النسيج عالية الدقة أو تعديلات دقة 4K. لا يحسن الأداء.\n\nاتركه معطلا إذا لم تكن متأكدا.", "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.",
"IgnoreMissingServicesTooltip": "يتجاهل خدمات نظام هوريزون غير المنفذة. قد يساعد هذا في تجاوز الأعطال عند تشغيل ألعاب معينة.\n\nاتركه معطلا إذا كنت غير متأكد.", "IgnoreMissingServicesTooltip": "يتجاهل خدمات نظام هوريزون غير المنفذة. قد يساعد هذا في تجاوز الأعطال عند تشغيل ألعاب معينة.\n\nاتركه معطلا إذا كنت غير متأكد.",
"GraphicsBackendThreadingTooltip": "ينفذ أوامر الواجهة الخلفية للرسومات على مسار ثاني.\n\nيعمل على تسريع عملية تجميع المظللات وتقليل التقطيع وتحسين الأداء على برامج تشغيل وحدة الرسوميات دون دعم المسارات المتعددة الخاصة بهم. أداء أفضل قليلا على برامج التشغيل ذات المسارات المتعددة.\n\nاضبط على تلقائي إذا لم تكن متأكدا.", "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
"GalThreadingTooltip": "ينفذ أوامر الواجهة الخلفية للرسومات على مسار ثاني.\n\nيعمل على تسريع عملية تجميع المظللات وتقليل التقطيع وتحسين الأداء على برامج تشغيل وحدة الرسوميات دون دعم المسارات المتعددة الخاصة بهم. أداء أفضل قليلا على برامج التشغيل ذات المسارات المتعددة.\n\nاضبط على تلقائي إذا لم تكن متأكدا.", "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
"ShaderCacheToggleTooltip": "يحفظ ذاكرة المظللات المؤقتة على القرص مما يقلل من التقطيع في عمليات التشغيل اللاحقة.\n\nاتركه مفعلا إذا لم تكن متأكدا.", "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.",
"ResolutionScaleTooltip": "يضاعف دقة عرض اللعبة.\n\nقد لا تعمل بعض الألعاب مع هذا وتبدو منقطة حتى عند زيادة الدقة؛ بالنسبة لهذه الألعاب، قد تحتاج إلى العثور على تعديلات تزيل تنعيم الحواف أو تزيد من دقة العرض الداخلي. لاستخدام الأخير، من المحتمل أن ترغب في تحديد أصلي.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبًا والتجربة حتى تجد المظهر المفضل للعبة.\n\nضع في اعتبارك أن 4x مبالغة في أي إعداد تقريبًا.", "ResolutionScaleTooltip": "يضاعف دقة عرض اللعبة.\n\nقد لا تعمل بعض الألعاب مع هذا وتبدو منقطة حتى عند زيادة الدقة؛ بالنسبة لهذه الألعاب، قد تحتاج إلى العثور على تعديلات تزيل الصقل أو تزيد من دقة العرض الداخلي. لاستخدام الأخير، من المحتمل أن ترغب في تحديد أصلي.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبًا والتجربة حتى تجد المظهر المفضل للعبة.\n\nضع في اعتبارك أن 4x مبالغة في أي إعداد تقريبًا.",
"ResolutionScaleEntryTooltip": "مقياس دقة النقطة العائمة، مثل 1.5. من المرجح أن تتسبب المقاييس غير المتكاملة في حدوث مشكلات أو تعطل.", "ResolutionScaleEntryTooltip": "مقياس دقة النقطة العائمة، مثل 1.5. من المرجح أن تتسبب المقاييس غير المتكاملة في حدوث مشكلات أو تعطل.",
"AnisotropyTooltip": "مستوى تصفية. اضبط على تلقائي لاستخدام القيمة التي تطلبها اللعبة.", "AnisotropyTooltip": "مستوى تصفية متباين الخواص. اضبط على تلقائي لاستخدام القيمة التي تطلبها اللعبة.",
"AspectRatioTooltip": "يتم تطبيق نسبة العرض إلى الارتفاع على نافذة العارض.\n\nقم بتغيير هذا فقط إذا كنت تستخدم تعديل نسبة العرض إلى الارتفاع للعبتك، وإلا سيتم تمديد الرسومات.\n\nاتركه16:9 إذا لم تكن متأكدا.", "AspectRatioTooltip": "يتم تطبيق نسبة العرض إلى الارتفاع على نافذة العارض.\n\nقم بتغيير هذا فقط إذا كنت تستخدم تعديل نسبة العرض إلى الارتفاع للعبتك، وإلا سيتم تمديد الرسومات.\n\nاتركه على 16:9 إذا لم تكن متأكدًا.",
"ShaderDumpPathTooltip": "مسار تفريغ المظللات", "ShaderDumpPathTooltip": "Graphics Shaders Dump Path",
"FileLogTooltip": "حفظ تسجيل وحدة التحكم إلى ملف سجل على القرص. لا يؤثر على الأداء.", "FileLogTooltip": "حفظ تسجيل وحدة التحكم إلى ملف سجل على القرص. لا يؤثر على الأداء.",
"StubLogTooltip": "طباعة رسائل سجل stub في وحدة التحكم. لا يؤثر على الأداء.", "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.",
"InfoLogTooltip": "طباعة رسائل سجل المعلومات في وحدة التحكم. لا يؤثر على الأداء.", "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.",
"WarnLogTooltip": "طباعة رسائل سجل التحذير في وحدة التحكم. لا يؤثر على الأداء.", "WarnLogTooltip": "طباعة رسائل سجل التحذير في وحدة التحكم. لا يؤثر على الأداء.",
"ErrorLogTooltip": "طباعة رسائل سجل الأخطاء في وحدة التحكم. لا يؤثر على الأداء.", "ErrorLogTooltip": "طباعة رسائل سجل الأخطاء في وحدة التحكم. لا يؤثر على الأداء.",
"TraceLogTooltip": "طباعة رسائل سجل التتبع في وحدة التحكم. لا يؤثر على الأداء.", "TraceLogTooltip": "طباعة رسائل سجل التتبع في وحدة التحكم. لا يؤثر على الأداء.",
"GuestLogTooltip": "طباعة رسائل سجل الضيف في وحدة التحكم. لا يؤثر على الأداء.", "GuestLogTooltip": "طباعة رسائل سجل الضيف في وحدة التحكم. لا يؤثر على الأداء.",
"FileAccessLogTooltip": "طباعة رسائل سجل الوصول إلى الملفات في وحدة التحكم.", "FileAccessLogTooltip": "طباعة رسائل سجل الوصول إلى الملفات في وحدة التحكم.",
"FSAccessLogModeTooltip": "تمكين إخراج سجل الوصول إلى نظام الملفات إلى وحدة التحكم. الأوضاع الممكنة هي 0-3", "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3",
"DeveloperOptionTooltip": "استخدمه بعناية", "DeveloperOptionTooltip": "استخدمه بعناية",
"OpenGlLogLevel": "يتطلب تمكين مستويات السجل المناسبة", "OpenGlLogLevel": "يتطلب تمكين مستويات السجل المناسبة",
"DebugLogTooltip": "طباعة رسائل سجل التصحيح في وحدة التحكم.\n\nاستخدم هذا فقط إذا طلب منك أحد الموظفين تحديدًا ذلك، لأنه سيجعل من الصعب قراءة السجلات وسيؤدي إلى تدهور أداء المحاكي.", "DebugLogTooltip": "طباعة رسائل سجل التصحيح في وحدة التحكم.\n\nاستخدم هذا فقط إذا طلب منك أحد الموظفين تحديدًا ذلك، لأنه سيجعل من الصعب قراءة السجلات وسيؤدي إلى تدهور أداء المحاكي.",
"LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع سويتش لتحميله", "LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع Switch لتحميله",
"LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع سويتش للتحميل", "LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع Switch للتحميل",
"OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات ريوجينكس", "OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات Ryujinx",
"OpenRyujinxLogsTooltip": "يفتح المجلد الذي تتم كتابة السجلات إليه", "OpenRyujinxLogsTooltip": "يفتح المجلد الذي تتم كتابة السجلات إليه",
"ExitTooltip": "الخروج من ريوجينكس", "ExitTooltip": "الخروج من Ryujinx",
"OpenSettingsTooltip": "فتح نافذة الإعدادات", "OpenSettingsTooltip": "فتح نافذة الإعدادات",
"OpenProfileManagerTooltip": "فتح نافذة إدارة الملفات الشخصية للمستخدمين", "OpenProfileManagerTooltip": "فتح نافذة إدارة ملفات تعريف المستخدمين",
"StopEmulationTooltip": "إيقاف محاكاة اللعبة الحالية والعودة إلى اختيار اللعبة", "StopEmulationTooltip": "إيقاف محاكاة اللعبة الحالية والعودة إلى اختيار اللعبة",
"CheckUpdatesTooltip": "التحقق من وجود تحديثات لريوجينكس", "CheckUpdatesTooltip": "التحقق من وجود تحديثات لـ Ryujinx",
"OpenAboutTooltip": "فتح حول النافذة", "OpenAboutTooltip": "فتح حول النافذة",
"GridSize": "حجم الشبكة", "GridSize": "حجم الشبكة",
"GridSizeTooltip": "تغيير حجم عناصر الشبكة", "GridSizeTooltip": "تغيير حجم عناصر الشبكة",
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "البرتغالية البرازيلية", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "البرتغالية البرازيلية",
"AboutRyujinxContributorsButtonHeader": "رؤية جميع المساهمين", "AboutRyujinxContributorsButtonHeader": "رؤية جميع المساهمين",
"SettingsTabSystemAudioVolume": "مستوى الصوت:", "SettingsTabSystemAudioVolume": "الحجم:",
"AudioVolumeTooltip": "تغيير مستوى الصوت", "AudioVolumeTooltip": "تغيير مستوى الصوت",
"SettingsTabSystemEnableInternetAccess": "الوصول إلى إنترنت كضيف/وضع LAN", "SettingsTabSystemEnableInternetAccess": "الوصول إلى إنترنت كضيف/وضع LAN",
"EnableInternetAccessTooltip": "للسماح للتطبيق الذي تمت محاكاته بالاتصال بالإنترنت.\n\nيمكن للألعاب التي تحتوي على وضع LAN الاتصال ببعضها البعض عند تمكين ذلك وتوصيل الأنظمة بنفس نقطة الوصول. وهذا يشمل الأجهزة الحقيقية أيضا.\n\nلا يسمح بالاتصال بخوادم نينتندو. قد يتسبب في حدوث عطل في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه معطلا إذا لم تكن متأكدا.", "EnableInternetAccessTooltip": "للسماح للتطبيق المحاكي بالاتصال بالإنترنت.\n\nالألعاب ذات وضع الشبكة المحلية يمكن الاتصال ببعضها البعض عندما يتم تمكين هذا النظام وتكون الأنظمة متصلة بنفس نقطة الوصول. هذا يشمل وحدات التحكم الحقيقية أيضًا.\n\nلا يسمح بالاتصال بخوادم Nintendo. قد يسبب الانهيار في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه متوقفا إن لم يكن مؤكدا.",
"GameListContextMenuManageCheatToolTip": "إدارة الغش", "GameListContextMenuManageCheatToolTip": "إدارة الغش",
"GameListContextMenuManageCheat": "إدارة الغش", "GameListContextMenuManageCheat": "إدارة الغش",
"GameListContextMenuManageModToolTip": "إدارة التعديلات", "GameListContextMenuManageModToolTip": "إدارة التعديلات",
"GameListContextMenuManageMod": "إدارة التعديلات", "GameListContextMenuManageMod": "إدارة التعديلات",
"ControllerSettingsStickRange": "نطاق:", "ControllerSettingsStickRange": "نطاق:",
"DialogStopEmulationTitle": "ريوجينكس - إيقاف المحاكاة", "DialogStopEmulationTitle": "Ryujinx - إيقاف المحاكاة",
"DialogStopEmulationMessage": "هل أنت متأكد أنك تريد إيقاف المحاكاة؟", "DialogStopEmulationMessage": "هل أنت متأكد أنك تريد إيقاف المحاكاة؟",
"SettingsTabCpu": "المعالج", "SettingsTabCpu": "CPU",
"SettingsTabAudio": "الصوت", "SettingsTabAudio": "الصوت",
"SettingsTabNetwork": "الشبكة", "SettingsTabNetwork": "الشبكة",
"SettingsTabNetworkConnection": "اتصال الشبكة", "SettingsTabNetworkConnection": "اتصال الشبكة",
"SettingsTabCpuCache": "ذاكرة المعالج المؤقت", "SettingsTabCpuCache": "ذاكرة المعالج المؤقت",
"SettingsTabCpuMemory": "وضع المعالج", "SettingsTabCpuMemory": "وضع المعالج",
"DialogUpdaterFlatpakNotSupportedMessage": "الرجاء تحديث ريوجينكس عبر فلات هاب.", "DialogUpdaterFlatpakNotSupportedMessage": "الرجاء تحديث Ryujinx عبر FlatHub.",
"UpdaterDisabledWarningTitle": "المحدث معطل!", "UpdaterDisabledWarningTitle": "التحديث معطل!",
"ControllerSettingsRotate90": "تدوير 90 درجة في اتجاه عقارب الساعة", "ControllerSettingsRotate90": "تدوير 90 درجة في اتجاه عقارب الساعة",
"IconSize": "حجم الأيقونة", "IconSize": "حجم الأيقونة",
"IconSizeTooltip": "تغيير حجم أيقونات اللعبة", "IconSizeTooltip": "تغيير حجم أيقونات اللعبة",
"MenuBarOptionsShowConsole": "عرض وحدة التحكم", "MenuBarOptionsShowConsole": "عرض وحدة التحكم",
"ShaderCachePurgeError": "حدث خطأ أثناء تنظيف ذاكرة المظللات المؤقتة في {0}: {1}", "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}",
"UserErrorNoKeys": "المفاتيح غير موجودة", "UserErrorNoKeys": "المفاتيح غير موجودة",
"UserErrorNoFirmware": "لم يتم العثور على البرنامج الثابت", "UserErrorNoFirmware": "لم يتم العثور على البرنامج الثابت",
"UserErrorFirmwareParsingFailed": "خطأ في تحليل البرنامج الثابت", "UserErrorFirmwareParsingFailed": "خطأ في تحليل البرنامج الثابت",
"UserErrorApplicationNotFound": "التطبيق غير موجود", "UserErrorApplicationNotFound": "التطبيق غير موجود",
"UserErrorUnknown": "خطأ غير معروف", "UserErrorUnknown": "خطأ غير معروف",
"UserErrorUndefined": "خطأ غير محدد", "UserErrorUndefined": "خطأ غير محدد",
"UserErrorNoKeysDescription": "لم يتمكن ريوجينكس من العثور على ملف 'prod.keys' الخاص بك", "UserErrorNoKeysDescription": "لم يتمكن Ryujinx من العثور على ملف 'prod.keys' الخاص بك",
"UserErrorNoFirmwareDescription": "لم يتمكن ريوجينكس من العثور على أية برامج ثابتة مثبتة", "UserErrorNoFirmwareDescription": "لم يتمكن ريوجينكس من العثور على أية برامج ثابتة مثبتة",
"UserErrorFirmwareParsingFailedDescription": "لم يتمكن ريوجينكس من تحليل البرامج الثابتة المتوفرة. يحدث هذا عادة بسبب المفاتيح القديمة.", "UserErrorFirmwareParsingFailedDescription": "لم يتمكن ريوجينكس من تحليل البرامج الثابتة المتوفرة. يحدث هذا عادة بسبب المفاتيح القديمة.",
"UserErrorApplicationNotFoundDescription": "تعذر على ريوجينكس العثور على تطبيق صالح في المسار المحدد.", "UserErrorApplicationNotFoundDescription": "تعذر على ريوجينكس العثور على تطبيق صالح في المسار المحدد.",
@@ -648,73 +542,72 @@
"OpenSetupGuideMessage": "فتح دليل الإعداد", "OpenSetupGuideMessage": "فتح دليل الإعداد",
"NoUpdate": "لا يوجد تحديث", "NoUpdate": "لا يوجد تحديث",
"TitleUpdateVersionLabel": "الإصدار: {0}", "TitleUpdateVersionLabel": "الإصدار: {0}",
"RyujinxInfo": "ريوجينكس - معلومات", "RyujinxInfo": "Ryujinx - معلومات",
"RyujinxConfirm": "ريوجينكس - تأكيد", "RyujinxConfirm": "Ryujinx - تأكيد",
"FileDialogAllTypes": "كل الأنواع", "FileDialogAllTypes": "كل الأنواع",
"Never": "مطلقا", "Never": "مطلقاً",
"SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفا على الأقل", "SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفًا على الأقل",
"SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفا", "SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفًا",
"SoftwareKeyboard": "لوحة المفاتيح البرمجية", "SoftwareKeyboard": "لوحة المفاتيح البرمجية",
"SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط", "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط",
"SoftwareKeyboardModeAlphabet": "يجب أن تكون الأحرف غير CJK فقط", "SoftwareKeyboardModeAlphabet": "يجب أن تكون الأحرف غير CJK فقط",
"SoftwareKeyboardModeASCII": "يجب أن يكون نص ASCII فقط", "SoftwareKeyboardModeASCII": "يجب أن يكون نص ASCII فقط",
"ControllerAppletControllers": "وحدات التحكم المدعومة:", "ControllerAppletControllers": "ذراع التحكم المدعومة:",
"ControllerAppletPlayers": "اللاعبين:", "ControllerAppletPlayers": "اللاعبين:",
"ControllerAppletDescription": "الإعدادات الحالية غير صالحة. افتح الإعدادات وأعد تكوين المدخلات الخاصة بك.", "ControllerAppletDescription": "الإعدادات الحالية غير صالحة. افتح الإعدادات وأعد تكوين المدخلات الخاصة بك.",
"ControllerAppletDocked": "تم ضبط وضع تركيب بالمنصة. يجب تعطيل التحكم المحمول.", "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.",
"UpdaterRenaming": "إعادة تسمية الملفات القديمة...", "UpdaterRenaming": "إعادة تسمية الملفات القديمة...",
"UpdaterRenameFailed": "المحدث غير قادر على إعادة تسمية الملف: {0}", "UpdaterRenameFailed": "التحديث غير قادر على إعادة تسمية الملف: {0}",
"UpdaterAddingFiles": "إضافة ملفات جديدة...", "UpdaterAddingFiles": "إضافة ملفات جديدة...",
"UpdaterExtracting": "استخراج التحديث...", "UpdaterExtracting": "استخراج التحديث...",
"UpdaterDownloading": "تحميل التحديث...", "UpdaterDownloading": "تحميل التحديث...",
"Game": "لعبة", "Game": "لعبة",
"Docked": "تركيب بالمنصة", "Docked": "مركب بالمنصة",
"Handheld": "محمول", "Handheld": "محمول",
"ConnectionError": "خطأ في الاتصال", "ConnectionError": "خطأ في الاتصال",
"AboutPageDeveloperListMore": "{0} والمزيد...", "AboutPageDeveloperListMore": "{0} والمزيد...",
"ApiError": "خطأ في API.", "ApiError": "خطأ في API.",
"LoadingHeading": "جاري تحميل {0}", "LoadingHeading": "جارٍ تحميل {0}",
"CompilingPPTC": "تجميع الـ(PPTC)", "CompilingPPTC": "تجميع الـ PTC",
"CompilingShaders": "تجميع المظللات", "CompilingShaders": "تجميع الظلال",
"AllKeyboards": "كل لوحات المفاتيح", "AllKeyboards": "كل لوحات المفاتيح",
"OpenFileDialogTitle": "حدد ملف مدعوم لفتحه", "OpenFileDialogTitle": "حدد ملف مدعوم لفتحه",
"OpenFolderDialogTitle": "حدد مجلدا يحتوي على لعبة غير مضغوطة", "OpenFolderDialogTitle": "حدد مجلدًا يحتوي على لعبة غير مضغوطة",
"AllSupportedFormats": "كل التنسيقات المدعومة", "AllSupportedFormats": "كل التنسيقات المدعومة",
"RyujinxUpdater": "محدث ريوجينكس", "RyujinxUpdater": "تحديث Ryujinx",
"SettingsTabHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", "SettingsTabHotkeys": "مفاتيح الاختصار في لوحة المفاتيح",
"SettingsTabHotkeysHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", "SettingsTabHotkeysHotkeys": "مفاتيح الاختصار في لوحة المفاتيح",
"SettingsTabHotkeysToggleVsyncHotkey": "تبديل المزامنة العمودية:", "SettingsTabHotkeysToggleVsyncHotkey": "تبديل VSync:",
"SettingsTabHotkeysScreenshotHotkey": "لقطة الشاشة:", "SettingsTabHotkeysScreenshotHotkey": "لقطة الشاشة:",
"SettingsTabHotkeysShowUiHotkey": "عرض واجهة المستخدم:", "SettingsTabHotkeysShowUiHotkey": "عرض واجهة المستخدم:",
"SettingsTabHotkeysPauseHotkey": "إيقاف مؤقت:", "SettingsTabHotkeysPauseHotkey": "إيقاف مؤقت:",
"SettingsTabHotkeysToggleMuteHotkey": "كتم:", "SettingsTabHotkeysToggleMuteHotkey": "كتم الصوت:",
"ControllerMotionTitle": "إعدادات التحكم بالحركة", "ControllerMotionTitle": "إعدادات التحكم بالحركة",
"ControllerRumbleTitle": "إعدادات الهزاز", "ControllerRumbleTitle": "إعدادات الهزاز",
"SettingsSelectThemeFileDialogTitle": "حدد ملف السمة", "SettingsSelectThemeFileDialogTitle": "حدد ملف السمة",
"SettingsXamlThemeFile": "ملف سمة Xaml", "SettingsXamlThemeFile": "Xaml Theme File",
"AvatarWindowTitle": "إدارة الحسابات - الصورة الرمزية", "AvatarWindowTitle": "إدارة الحسابات - الصورة الرمزية",
"Amiibo": "أميبو", "Amiibo": "أميبو",
"Unknown": "غير معروف", "Unknown": "غير معروف",
"Usage": "الاستخدام", "Usage": "الاستخدام",
"Writable": "قابل للكتابة", "Writable": "قابل للكتابة",
"SelectDlcDialogTitle": "حدد ملفات المحتوي الإضافي", "SelectDlcDialogTitle": "حدد ملفات DLC",
"SelectUpdateDialogTitle": "حدد ملفات التحديث", "SelectUpdateDialogTitle": "حدد ملفات التحديث",
"SelectModDialogTitle": "حدد مجلد التعديل", "SelectModDialogTitle": "حدد مجلد التعديل",
"UserProfileWindowTitle": "مدير الملفات الشخصية للمستخدمين", "UserProfileWindowTitle": "مدير ملفات تعريف المستخدمين",
"CheatWindowTitle": "مدير الغش", "CheatWindowTitle": "مدير الغش",
"DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})", "DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})",
"ModWindowTitle": "إدارة التعديلات لـ {0} ({1})",
"UpdateWindowTitle": "مدير تحديث العنوان", "UpdateWindowTitle": "مدير تحديث العنوان",
"CheatWindowHeading": "الغش متوفر لـ {0} [{1}]", "CheatWindowHeading": "الغش متوفر لـ {0} [{1}]",
"BuildId": "معرف البناء:", "BuildId": "معرف البناء:",
"DlcWindowHeading": "المحتويات القابلة للتنزيل {0}", "DlcWindowHeading": "المحتويات القابلة للتنزيل {0}",
"ModWindowHeading": "{0} تعديل", "ModWindowHeading": "{0} تعديل",
"UserProfilesEditProfile": "تعديل المحدد", "UserProfilesEditProfile": "تعديل المحددة",
"Cancel": "إلغاء", "Cancel": "إلغاء",
"Save": "حفظ", "Save": "حفظ",
"Discard": "تجاهل", "Discard": "تجاهل",
"Paused": "متوقف مؤقتا", "Paused": "متوقف مؤقتا",
"UserProfilesSetProfileImage": "تعيين صورة الملف الشخصي", "UserProfilesSetProfileImage": "تعيين صورة ملف التعريف",
"UserProfileEmptyNameError": "الاسم مطلوب", "UserProfileEmptyNameError": "الاسم مطلوب",
"UserProfileNoImageError": "يجب تعيين صورة الملف الشخصي", "UserProfileNoImageError": "يجب تعيين صورة الملف الشخصي",
"GameUpdateWindowHeading": "إدارة التحديثات لـ {0} ({1})", "GameUpdateWindowHeading": "إدارة التحديثات لـ {0} ({1})",
@@ -723,22 +616,22 @@
"UserProfilesName": "الاسم:", "UserProfilesName": "الاسم:",
"UserProfilesUserId": "معرف المستخدم:", "UserProfilesUserId": "معرف المستخدم:",
"SettingsTabGraphicsBackend": "خلفية الرسومات", "SettingsTabGraphicsBackend": "خلفية الرسومات",
"SettingsTabGraphicsBackendTooltip": "حدد الواجهة الخلفية للرسومات التي سيتم استخدامها في المحاكي.\n\nيعد برنامج فولكان أفضل بشكل عام لجميع بطاقات الرسومات الحديثة، طالما أن برامج التشغيل الخاصة بها محدثة. يتميز فولكان أيضا بتجميع مظللات أسرع (أقل تقطيعا) على جميع بائعي وحدات معالجة الرسومات.\n\nقد يحقق أوبن جي أل نتائج أفضل على وحدات معالجة الرسومات إنفيديا القديمة، أو على وحدات معالجة الرسومات إي إم دي القديمة على لينكس، أو على وحدات معالجة الرسومات ذات ذاكرة الوصول العشوائي للفيديوالأقل، على الرغم من أن تعثرات تجميع المظللات ستكون أكبر.\n\nاضبط على فولكان إذا لم تكن متأكدا. اضبط على أوبن جي أل إذا كانت وحدة معالجة الرسومات الخاصة بك لا تدعم فولكان حتى مع أحدث برامج تشغيل الرسومات.", "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.",
"SettingsEnableTextureRecompression": "تمكين إعادة ضغط التكستر", "SettingsEnableTextureRecompression": "تمكين إعادة ضغط التكستر",
"SettingsEnableTextureRecompressionTooltip": "يضغط تكستر ASTC من أجل تقليل استخدام ذاكرة الوصول العشوائي للفيديو.\n\nتتضمن الألعاب التي تستخدم تنسيق النسيج هذا Astral Chain وBayonetta 3 وFire Emblem Engage وMetroid Prime Remastered وSuper Mario Bros. Wonder وThe Legend of Zelda: Tears of the Kingdom.\n\nمن المحتمل أن تتعطل بطاقات الرسومات التي تحتوي على 4 جيجا بايت من ذاكرة الوصول العشوائي للفيديو أو أقل في مرحلة ما أثناء تشغيل هذه الألعاب.\n\nقم بالتمكين فقط في حالة نفاد ذاكرة الوصول العشوائي للفيديو في الألعاب المذكورة أعلاه. اتركه معطلا إذا لم تكن متأكدا.", "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.",
"SettingsTabGraphicsPreferredGpu": "وحدة معالجة الرسوميات المفضلة", "SettingsTabGraphicsPreferredGpu": "GPU المفضل",
"SettingsTabGraphicsPreferredGpuTooltip": "حدد بطاقة الرسومات التي سيتم استخدامها مع الواجهة الخلفية لرسومات فولكان.\n\nلا يؤثر على وحدة معالجة الرسومات التي سيستخدمها أوبن جي أل.\n\nاضبط على وحدة معالجة الرسومات التي تم وضع علامة عليها كـ \"dGPU\" إذا لم تكن متأكدًا. إذا لم يكن هناك واحد، اتركه.", "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.",
"SettingsAppRequiredRestartMessage": "مطلوب إعادة تشغيل ريوجينكس", "SettingsAppRequiredRestartMessage": "مطلوب إعادة تشغيل Ryujinx",
"SettingsGpuBackendRestartMessage": "تم تعديل إعدادات الواجهة الخلفية للرسومات أو وحدة معالجة الرسومات. سيتطلب هذا إعادة التشغيل ليتم تطبيقه", "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied",
"SettingsGpuBackendRestartSubMessage": "\n\nهل تريد إعادة التشغيل الآن؟", "SettingsGpuBackendRestartSubMessage": "\n\nهل تريد إعادة التشغيل الآن؟",
"RyujinxUpdaterMessage": "هل تريد تحديث ريوجينكس إلى أحدث إصدار؟", "RyujinxUpdaterMessage": "هل تريد تحديث Ryujinx إلى أحدث إصدار؟",
"SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:", "SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:",
"SettingsTabHotkeysVolumeDownHotkey": "خفض مستوى الصوت:", "SettingsTabHotkeysVolumeDownHotkey": "خفض مستوى الصوت:",
"SettingsEnableMacroHLE": "تمكين Maro HLE", "SettingsEnableMacroHLE": "Enable Macro HLE",
"SettingsEnableMacroHLETooltip": "محاكاة عالية المستوى لكود مايكرو وحدة معالجة الرسوميات.\n\nيعمل على تحسين الأداء، ولكنه قد يسبب خللا رسوميا في بعض الألعاب.\n\nاتركه مفعلا إذا لم تكن متأكدا.", "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.",
"SettingsEnableColorSpacePassthrough": "عبور مساحة اللون", "SettingsEnableColorSpacePassthrough": "Color Space Passthrough",
"SettingsEnableColorSpacePassthroughTooltip": "يوجه واجهة فولكان الخلفية لتمرير معلومات الألوان دون تحديد مساحة اللون. بالنسبة للمستخدمين الذين لديهم شاشات ذات نطاق واسع، قد يؤدي ذلك إلى الحصول على ألوان أكثر حيوية، على حساب صحة الألوان.", "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.",
"VolumeShort": "مستوى", "VolumeShort": "الحجم",
"UserProfilesManageSaves": "إدارة الحفظ", "UserProfilesManageSaves": "إدارة الحفظ",
"DeleteUserSave": "هل تريد حذف حفظ المستخدم لهذه اللعبة؟", "DeleteUserSave": "هل تريد حذف حفظ المستخدم لهذه اللعبة؟",
"IrreversibleActionNote": "هذا الإجراء لا يمكن التراجع عنه.", "IrreversibleActionNote": "هذا الإجراء لا يمكن التراجع عنه.",
@@ -749,12 +642,12 @@
"Search": "بحث", "Search": "بحث",
"UserProfilesRecoverLostAccounts": "استعادة الحسابات المفقودة", "UserProfilesRecoverLostAccounts": "استعادة الحسابات المفقودة",
"Recover": "استعادة", "Recover": "استعادة",
"UserProfilesRecoverHeading": "تم العثور على حفظ للحسابات التالية", "UserProfilesRecoverHeading": "تم العثور على الحفظ للحسابات التالية",
"UserProfilesRecoverEmptyList": "لا توجد ملفات شخصية لاستردادها", "UserProfilesRecoverEmptyList": "لا توجد ملفات تعريف لاستردادها",
"GraphicsAATooltip": "يتم تطبيق تنعيم الحواف على عرض اللعبة.\n\nسوف يقوم FXAA بتعتيم معظم الصورة، بينما سيحاول SMAA العثور على حواف خشنة وتنعيمها.\n\nلا ينصح باستخدامه مع فلتر FSR لتكبير.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبا والتجربة حتى تجد المظهر المفضل للعبة.\n\nاتركه على لا شيء إذا لم تكن متأكدا.", "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.",
"GraphicsAALabel": "تنعيم الحواف:", "GraphicsAALabel": "تنعيم الحواف:",
"GraphicsScalingFilterLabel": "فلتر التكبير:", "GraphicsScalingFilterLabel": "فلتر التكبير:",
"GraphicsScalingFilterTooltip": "اختر فلتر التكبير الذي سيتم تطبيقه عند استخدام مقياس الدقة.\n\nيعمل Bilinear بشكل جيد مع الألعاب ثلاثية الأبعاد وهو خيار افتراضي آمن.\n\nيوصى باستخدام Nearest لألعاب البكسل الفنية.\n\nFSR 1.0 هو مجرد مرشح توضيحي، ولا ينصح باستخدامه مع FXAA أو SMAA.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبا والتجربة حتى تجد المظهر المفضل للعبة.\n\nاتركه على Bilinear إذا لم تكن متأكدا.", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.",
"GraphicsScalingFilterBilinear": "Bilinear", "GraphicsScalingFilterBilinear": "Bilinear",
"GraphicsScalingFilterNearest": "Nearest", "GraphicsScalingFilterNearest": "Nearest",
"GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterFsr": "FSR",
@@ -767,14 +660,14 @@
"UserEditorTitle": "تعديل المستخدم", "UserEditorTitle": "تعديل المستخدم",
"UserEditorTitleCreate": "إنشاء مستخدم", "UserEditorTitleCreate": "إنشاء مستخدم",
"SettingsTabNetworkInterface": "واجهة الشبكة:", "SettingsTabNetworkInterface": "واجهة الشبكة:",
"NetworkInterfaceTooltip": "واجهة الشبكة مستخدمة لميزات LAN/LDN.\n\nبالاشتراك مع VPN أو XLink Kai ولعبة تدعم LAN، يمكن استخدامها لتزييف اتصال الشبكة نفسها عبر الإنترنت.\n\nاتركه على الافتراضي إذا لم تكن متأكدا.", "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.",
"NetworkInterfaceDefault": "افتراضي", "NetworkInterfaceDefault": "افتراضي",
"PackagingShaders": "تعبئة المظللات", "PackagingShaders": "Packaging Shaders",
"AboutChangelogButton": "عرض سجل التغييرات على غيت هاب", "AboutChangelogButton": "عرض سجل التغييرات على GitHub",
"AboutChangelogButtonTooltipMessage": "انقر لفتح سجل التغيير لهذا الإصدار في متصفحك الافتراضي.", "AboutChangelogButtonTooltipMessage": "انقر لفتح سجل التغيير لهذا الإصدار في متصفحك الافتراضي.",
"SettingsTabNetworkMultiplayer": "لعب جماعي", "SettingsTabNetworkMultiplayer": "لعب جماعي",
"MultiplayerMode": "الوضع:", "MultiplayerMode": "النمط:",
"MultiplayerModeTooltip": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم LdnMitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.", "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
"MultiplayerModeDisabled": "معطل", "MultiplayerModeDisabled": "معطل",
"MultiplayerModeLdnMitm": "ldn_mitm" "MultiplayerModeLdnMitm": "ldn_mitm"
} }

View File

@@ -30,10 +30,6 @@
"MenuBarToolsManageFileTypes": "Dateitypen verwalten", "MenuBarToolsManageFileTypes": "Dateitypen verwalten",
"MenuBarToolsInstallFileTypes": "Dateitypen installieren", "MenuBarToolsInstallFileTypes": "Dateitypen installieren",
"MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren",
"MenuBarView": "_Ansicht",
"MenuBarViewWindow": "Fenstergröße",
"MenuBarViewWindow720": "720p",
"MenuBarViewWindow1080": "1080p",
"MenuBarHelp": "_Hilfe", "MenuBarHelp": "_Hilfe",
"MenuBarHelpCheckForUpdates": "Nach Updates suchen", "MenuBarHelpCheckForUpdates": "Nach Updates suchen",
"MenuBarHelpAbout": "Über Ryujinx", "MenuBarHelpAbout": "Über Ryujinx",
@@ -96,7 +92,6 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere die Statusanzeige für Discord", "SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere die Statusanzeige für Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen", "SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen",
"SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\"-Dialog", "SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\"-Dialog",
"SettingsTabGeneralRememberWindowState": "Fenstergröße/-position merken",
"SettingsTabGeneralHideCursor": "Mauszeiger ausblenden", "SettingsTabGeneralHideCursor": "Mauszeiger ausblenden",
"SettingsTabGeneralHideCursorNever": "Niemals", "SettingsTabGeneralHideCursorNever": "Niemals",
"SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden", "SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden",
@@ -271,107 +266,6 @@
"ControllerSettingsMotionGyroDeadzone": "Gyro-Deadzone:", "ControllerSettingsMotionGyroDeadzone": "Gyro-Deadzone:",
"ControllerSettingsSave": "Speichern", "ControllerSettingsSave": "Speichern",
"ControllerSettingsClose": "Schließen", "ControllerSettingsClose": "Schließen",
"KeyUnknown": "Unbekannt",
"KeyShiftLeft": "Shift Left",
"KeyShiftRight": "Shift Right",
"KeyControlLeft": "Ctrl Left",
"KeyMacControlLeft": "⌃ Left",
"KeyControlRight": "Ctrl Right",
"KeyMacControlRight": "⌃ Right",
"KeyAltLeft": "Alt Left",
"KeyMacAltLeft": "⌥ Left",
"KeyAltRight": "Alt Right",
"KeyMacAltRight": "⌥ Right",
"KeyWinLeft": "⊞ Left",
"KeyMacWinLeft": "⌘ Left",
"KeyWinRight": "⊞ Right",
"KeyMacWinRight": "⌘ Right",
"KeyMenu": "Menu",
"KeyUp": "Up",
"KeyDown": "Down",
"KeyLeft": "Left",
"KeyRight": "Right",
"KeyEnter": "Enter",
"KeyEscape": "Escape",
"KeySpace": "Space",
"KeyTab": "Tab",
"KeyBackSpace": "Backspace",
"KeyInsert": "Insert",
"KeyDelete": "Delete",
"KeyPageUp": "Page Up",
"KeyPageDown": "Page Down",
"KeyHome": "Home",
"KeyEnd": "End",
"KeyCapsLock": "Caps Lock",
"KeyScrollLock": "Scroll Lock",
"KeyPrintScreen": "Print Screen",
"KeyPause": "Pause",
"KeyNumLock": "Num Lock",
"KeyClear": "Clear",
"KeyKeypad0": "Keypad 0",
"KeyKeypad1": "Keypad 1",
"KeyKeypad2": "Keypad 2",
"KeyKeypad3": "Keypad 3",
"KeyKeypad4": "Keypad 4",
"KeyKeypad5": "Keypad 5",
"KeyKeypad6": "Keypad 6",
"KeyKeypad7": "Keypad 7",
"KeyKeypad8": "Keypad 8",
"KeyKeypad9": "Keypad 9",
"KeyKeypadDivide": "Keypad Divide",
"KeyKeypadMultiply": "Keypad Multiply",
"KeyKeypadSubtract": "Keypad Subtract",
"KeyKeypadAdd": "Keypad Add",
"KeyKeypadDecimal": "Keypad Decimal",
"KeyKeypadEnter": "Keypad Enter",
"KeyNumber0": "0",
"KeyNumber1": "1",
"KeyNumber2": "2",
"KeyNumber3": "3",
"KeyNumber4": "4",
"KeyNumber5": "5",
"KeyNumber6": "6",
"KeyNumber7": "7",
"KeyNumber8": "8",
"KeyNumber9": "9",
"KeyTilde": "~",
"KeyGrave": "`",
"KeyMinus": "-",
"KeyPlus": "+",
"KeyBracketLeft": "[",
"KeyBracketRight": "]",
"KeySemicolon": ";",
"KeyQuote": "\"",
"KeyComma": ",",
"KeyPeriod": ".",
"KeySlash": "/",
"KeyBackSlash": "\\",
"KeyUnbound": "Unbound",
"GamepadLeftStick": "L Stick Button",
"GamepadRightStick": "R Stick Button",
"GamepadLeftShoulder": "Left Shoulder",
"GamepadRightShoulder": "Right Shoulder",
"GamepadLeftTrigger": "Left Trigger",
"GamepadRightTrigger": "Right Trigger",
"GamepadDpadUp": "Up",
"GamepadDpadDown": "Down",
"GamepadDpadLeft": "Left",
"GamepadDpadRight": "Right",
"GamepadMinus": "-",
"GamepadPlus": "+",
"GamepadGuide": "Guide",
"GamepadMisc1": "Misc",
"GamepadPaddle1": "Paddle 1",
"GamepadPaddle2": "Paddle 2",
"GamepadPaddle3": "Paddle 3",
"GamepadPaddle4": "Paddle 4",
"GamepadTouchpad": "Touchpad",
"GamepadSingleLeftTrigger0": "Left Trigger 0",
"GamepadSingleRightTrigger0": "Right Trigger 0",
"GamepadSingleLeftTrigger1": "Left Trigger 1",
"GamepadSingleRightTrigger1": "Right Trigger 1",
"StickLeft": "Left Stick",
"StickRight": "Right Stick",
"UserProfilesSelectedUserProfile": "Ausgewähltes Profil:", "UserProfilesSelectedUserProfile": "Ausgewähltes Profil:",
"UserProfilesSaveProfileName": "Profilname speichern", "UserProfilesSaveProfileName": "Profilname speichern",
"UserProfilesChangeProfileImage": "Profilbild ändern", "UserProfilesChangeProfileImage": "Profilbild ändern",
@@ -703,7 +597,6 @@
"UserProfileWindowTitle": "Benutzerprofile verwalten", "UserProfileWindowTitle": "Benutzerprofile verwalten",
"CheatWindowTitle": "Spiel-Cheats verwalten", "CheatWindowTitle": "Spiel-Cheats verwalten",
"DlcWindowTitle": "Spiel-DLC verwalten", "DlcWindowTitle": "Spiel-DLC verwalten",
"ModWindowTitle": "Manage Mods for {0} ({1})",
"UpdateWindowTitle": "Spiel-Updates verwalten", "UpdateWindowTitle": "Spiel-Updates verwalten",
"CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]",
"BuildId": "BuildId:", "BuildId": "BuildId:",

View File

@@ -30,10 +30,6 @@
"MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων",
"MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.",
"MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων",
"MenuBarView": "_View",
"MenuBarViewWindow": "Window Size",
"MenuBarViewWindow720": "720p",
"MenuBarViewWindow1080": "1080p",
"MenuBarHelp": "_Βοήθεια", "MenuBarHelp": "_Βοήθεια",
"MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις",
"MenuBarHelpAbout": "Σχετικά με", "MenuBarHelpAbout": "Σχετικά με",
@@ -96,7 +92,6 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord", "SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", "SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση",
"SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".", "SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Απόκρυψη Κέρσορα:", "SettingsTabGeneralHideCursor": "Απόκρυψη Κέρσορα:",
"SettingsTabGeneralHideCursorNever": "Ποτέ", "SettingsTabGeneralHideCursorNever": "Ποτέ",
"SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια", "SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια",
@@ -271,107 +266,6 @@
"ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:", "ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:",
"ControllerSettingsSave": "Αποθήκευση", "ControllerSettingsSave": "Αποθήκευση",
"ControllerSettingsClose": "Κλείσιμο", "ControllerSettingsClose": "Κλείσιμο",
"KeyUnknown": "Unknown",
"KeyShiftLeft": "Shift Left",
"KeyShiftRight": "Shift Right",
"KeyControlLeft": "Ctrl Left",
"KeyMacControlLeft": "⌃ Left",
"KeyControlRight": "Ctrl Right",
"KeyMacControlRight": "⌃ Right",
"KeyAltLeft": "Alt Left",
"KeyMacAltLeft": "⌥ Left",
"KeyAltRight": "Alt Right",
"KeyMacAltRight": "⌥ Right",
"KeyWinLeft": "⊞ Left",
"KeyMacWinLeft": "⌘ Left",
"KeyWinRight": "⊞ Right",
"KeyMacWinRight": "⌘ Right",
"KeyMenu": "Menu",
"KeyUp": "Up",
"KeyDown": "Down",
"KeyLeft": "Left",
"KeyRight": "Right",
"KeyEnter": "Enter",
"KeyEscape": "Escape",
"KeySpace": "Space",
"KeyTab": "Tab",
"KeyBackSpace": "Backspace",
"KeyInsert": "Insert",
"KeyDelete": "Delete",
"KeyPageUp": "Page Up",
"KeyPageDown": "Page Down",
"KeyHome": "Home",
"KeyEnd": "End",
"KeyCapsLock": "Caps Lock",
"KeyScrollLock": "Scroll Lock",
"KeyPrintScreen": "Print Screen",
"KeyPause": "Pause",
"KeyNumLock": "Num Lock",
"KeyClear": "Clear",
"KeyKeypad0": "Keypad 0",
"KeyKeypad1": "Keypad 1",
"KeyKeypad2": "Keypad 2",
"KeyKeypad3": "Keypad 3",
"KeyKeypad4": "Keypad 4",
"KeyKeypad5": "Keypad 5",
"KeyKeypad6": "Keypad 6",
"KeyKeypad7": "Keypad 7",
"KeyKeypad8": "Keypad 8",
"KeyKeypad9": "Keypad 9",
"KeyKeypadDivide": "Keypad Divide",
"KeyKeypadMultiply": "Keypad Multiply",
"KeyKeypadSubtract": "Keypad Subtract",
"KeyKeypadAdd": "Keypad Add",
"KeyKeypadDecimal": "Keypad Decimal",
"KeyKeypadEnter": "Keypad Enter",
"KeyNumber0": "0",
"KeyNumber1": "1",
"KeyNumber2": "2",
"KeyNumber3": "3",
"KeyNumber4": "4",
"KeyNumber5": "5",
"KeyNumber6": "6",
"KeyNumber7": "7",
"KeyNumber8": "8",
"KeyNumber9": "9",
"KeyTilde": "~",
"KeyGrave": "`",
"KeyMinus": "-",
"KeyPlus": "+",
"KeyBracketLeft": "[",
"KeyBracketRight": "]",
"KeySemicolon": ";",
"KeyQuote": "\"",
"KeyComma": ",",
"KeyPeriod": ".",
"KeySlash": "/",
"KeyBackSlash": "\\",
"KeyUnbound": "Unbound",
"GamepadLeftStick": "L Stick Button",
"GamepadRightStick": "R Stick Button",
"GamepadLeftShoulder": "Left Shoulder",
"GamepadRightShoulder": "Right Shoulder",
"GamepadLeftTrigger": "Left Trigger",
"GamepadRightTrigger": "Right Trigger",
"GamepadDpadUp": "Up",
"GamepadDpadDown": "Down",
"GamepadDpadLeft": "Left",
"GamepadDpadRight": "Right",
"GamepadMinus": "-",
"GamepadPlus": "+",
"GamepadGuide": "Guide",
"GamepadMisc1": "Misc",
"GamepadPaddle1": "Paddle 1",
"GamepadPaddle2": "Paddle 2",
"GamepadPaddle3": "Paddle 3",
"GamepadPaddle4": "Paddle 4",
"GamepadTouchpad": "Touchpad",
"GamepadSingleLeftTrigger0": "Left Trigger 0",
"GamepadSingleRightTrigger0": "Right Trigger 0",
"GamepadSingleLeftTrigger1": "Left Trigger 1",
"GamepadSingleRightTrigger1": "Right Trigger 1",
"StickLeft": "Left Stick",
"StickRight": "Right Stick",
"UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:", "UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:",
"UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ", "UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ",
"UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ", "UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ",
@@ -703,7 +597,6 @@
"UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη",
"CheatWindowTitle": "Διαχειριστής των Cheats", "CheatWindowTitle": "Διαχειριστής των Cheats",
"DlcWindowTitle": "Downloadable Content Manager", "DlcWindowTitle": "Downloadable Content Manager",
"ModWindowTitle": "Manage Mods for {0} ({1})",
"UpdateWindowTitle": "Διαχειριστής Ενημερώσεων Τίτλου", "UpdateWindowTitle": "Διαχειριστής Ενημερώσεων Τίτλου",
"CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]",
"BuildId": "BuildId:", "BuildId": "BuildId:",

View File

@@ -30,10 +30,6 @@
"MenuBarToolsManageFileTypes": "Manage file types", "MenuBarToolsManageFileTypes": "Manage file types",
"MenuBarToolsInstallFileTypes": "Install file types", "MenuBarToolsInstallFileTypes": "Install file types",
"MenuBarToolsUninstallFileTypes": "Uninstall file types", "MenuBarToolsUninstallFileTypes": "Uninstall file types",
"MenuBarView": "_View",
"MenuBarViewWindow": "Window Size",
"MenuBarViewWindow720": "720p",
"MenuBarViewWindow1080": "1080p",
"MenuBarHelp": "_Help", "MenuBarHelp": "_Help",
"MenuBarHelpCheckForUpdates": "Check for Updates", "MenuBarHelpCheckForUpdates": "Check for Updates",
"MenuBarHelpAbout": "About", "MenuBarHelpAbout": "About",
@@ -96,7 +92,6 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence", "SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch",
"SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Hide Cursor:", "SettingsTabGeneralHideCursor": "Hide Cursor:",
"SettingsTabGeneralHideCursorNever": "Never", "SettingsTabGeneralHideCursorNever": "Never",
"SettingsTabGeneralHideCursorOnIdle": "On Idle", "SettingsTabGeneralHideCursorOnIdle": "On Idle",
@@ -404,7 +399,6 @@
"GameListContextMenuToggleFavorite": "Toggle Favorite", "GameListContextMenuToggleFavorite": "Toggle Favorite",
"GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game",
"SettingsTabGeneralTheme": "Theme:", "SettingsTabGeneralTheme": "Theme:",
"SettingsTabGeneralThemeAuto": "Auto",
"SettingsTabGeneralThemeDark": "Dark", "SettingsTabGeneralThemeDark": "Dark",
"SettingsTabGeneralThemeLight": "Light", "SettingsTabGeneralThemeLight": "Light",
"ControllerSettingsConfigureGeneral": "Configure", "ControllerSettingsConfigureGeneral": "Configure",

Some files were not shown because too many files have changed in this diff Show More