Compare commits

..

10 Commits

Author SHA1 Message Date
riperiperi
e211c3f00a UI: Add Metal surface creation for MoltenVK (#3980)
* Initial implementation of metal surface across UIs

* Fix SDL2 on windows

* Update Ryujinx/Ryujinx.csproj

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Address Feedback

Co-authored-by: Mary-nyan <thog@protonmail.com>
2022-12-06 19:00:25 -03:00
dependabot[bot]
d3709a753f nuget: bump XamlNameReferenceGenerator from 1.4.2 to 1.5.1 (#4026)
Bumps [XamlNameReferenceGenerator](https://github.com/avaloniaui/Avalonia.NameGenerator) from 1.4.2 to 1.5.1.
- [Release notes](https://github.com/avaloniaui/Avalonia.NameGenerator/releases)
- [Commits](https://github.com/avaloniaui/Avalonia.NameGenerator/compare/1.4.2...1.5.1)

---
updated-dependencies:
- dependency-name: XamlNameReferenceGenerator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 19:00:08 +01:00
dependabot[bot]
ab676d58ea nuget: bump System.IdentityModel.Tokens.Jwt from 6.25.0 to 6.25.1 (#4043)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.25.0 to 6.25.1.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.25.0...6.25.1)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 18:01:21 +01:00
Ac_K
2372c194f1 ava: Cleanup Input classes (#4042)
* ava: Cleanup Input classes

This PR just cleanup all Input classes for consistencies.

* Addresses TSRBerry's feedback
2022-12-06 15:32:14 +00:00
Mary-nyan
40311310d1 amadeus: Add missing compressor effect from REV11 (#4010)
* amadeus: Add missing compressor effect from REV11

This was in my reversing notes but seems I completely forgot to
implement it

Also took the opportunity to simplify the Limiter effect a bit.

* Remove some outdated comment

* Address gdkchan's comments
2022-12-06 15:04:25 +01:00
gdkchan
dde9bb5c69 Fix storage buffer access when match fails (#4037)
* Fix storage buffer access when match fails

* Shader cache version bump
2022-12-06 03:36:54 +00:00
MetrosexualGarbodor
266338a7c9 Change default Vsync toggle hotkey to F1 instead of Tab (#3995) 2022-12-06 02:09:26 +00:00
dependabot[bot]
90156eea4c nuget: bump Microsoft.CodeAnalysis.CSharp from 4.2.0 to 4.4.0 (#4025)
Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.2.0 to 4.4.0.
- [Release notes](https://github.com/dotnet/roslyn/releases)
- [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md)
- [Commits](https://github.com/dotnet/roslyn/compare/v4.2.0...Visual-Studio-2019-Version-16.0-Preview-4.4)

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.CSharp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 01:51:33 +00:00
Isaac Marovitz
071c01c235 Fix Sorting Regression (#4032)
* Fix sorting regression + Remove unsued sort

* Fix GTK

* Attempt 2 to fix GTK

* Whoopsie

* Fix whitspace
2022-12-06 01:40:06 +00:00
gdkchan
de06ffb0f7 Fix shaders with global memory access from unknown locations (#4029)
* Fix shaders with global memory access from unknown locations

* Shader cache version bump
2022-12-06 01:09:24 +00:00
74 changed files with 1371 additions and 536 deletions

View File

@@ -48,6 +48,11 @@ namespace Ryujinx.Audio.Renderer.Common
/// <summary>
/// Effect to capture mixes (via auxiliary buffers).
/// </summary>
CaptureBuffer
CaptureBuffer,
/// <summary>
/// Effect applying a compressor filter (DRC).
/// </summary>
Compressor,
}
}

View File

@@ -14,6 +14,7 @@ namespace Ryujinx.Audio.Renderer.Common
Reverb3d,
PcmFloat,
Limiter,
CaptureBuffer
CaptureBuffer,
Compressor
}
}

View File

@@ -31,6 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
LimiterVersion1,
LimiterVersion2,
GroupedBiquadFilter,
CaptureBuffer
CaptureBuffer,
Compressor
}
}

View File

@@ -0,0 +1,173 @@
using System;
using System.Diagnostics;
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
public class CompressorCommand : ICommand
{
private const int FixedPointPrecision = 15;
public bool Enabled { get; set; }
public int NodeId { get; }
public CommandType CommandType => CommandType.Compressor;
public uint EstimatedProcessingTime { get; set; }
public CompressorParameter Parameter => _parameter;
public Memory<CompressorState> State { get; }
public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; }
private CompressorParameter _parameter;
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
{
Enabled = true;
NodeId = nodeId;
_parameter = parameter;
State = state;
IsEffectEnabled = isEnabled;
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
for (int i = 0; i < _parameter.ChannelCount; i++)
{
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
}
}
public void Process(CommandList context)
{
ref CompressorState state = ref State.Span[0];
if (IsEffectEnabled)
{
if (_parameter.Status == Server.Effect.UsageState.Invalid)
{
state = new CompressorState(ref _parameter);
}
else if (_parameter.Status == Server.Effect.UsageState.New)
{
state.UpdateParameter(ref _parameter);
}
}
ProcessCompressor(context, ref state);
}
private unsafe void ProcessCompressor(CommandList context, ref CompressorState state)
{
Debug.Assert(_parameter.IsChannelCountValid());
if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
float unknown4 = state.Unknown4;
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha;
for (int i = 0; i < _parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
}
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
}
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 0.0f;
bool unknown10OutOfRange = false;
if (newMean < 1.0e-10f)
{
z = 1.0f;
unknown10OutOfRange = state.Unknown10 < -100.0f;
}
if (y >= state.Unknown10 || unknown10OutOfRange)
{
float tmpGain;
if (y >= state.Unknown14)
{
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
}
else
{
tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
}
z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
}
float unknown4New = z;
float compressionEmaAlpha;
if ((unknown4 - z) <= 0.08f)
{
compressionEmaAlpha = Parameter.ReleaseCoefficient;
if ((unknown4 - z) >= -0.08f)
{
if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f)
{
unknown4New = unknown4;
}
compressionEmaAlpha = previousCompressionEmaAlpha;
}
}
else
{
compressionEmaAlpha = Parameter.AttackCoefficient;
}
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
{
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
}
unknown4 = unknown4New;
previousCompressionEmaAlpha = compressionEmaAlpha;
}
state.InputMovingAverage = inputMovingAverage;
state.Unknown4 = unknown4;
state.CompressionGainAverage = compressionGainAverage;
state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha;
}
else
{
for (int i = 0; i < Parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
}
}
}
}
}
}

View File

@@ -90,32 +90,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float inputCoefficient = Parameter.ReleaseCoefficient;
if (sampleInputMax > state.DectectorAverage[channelIndex])
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
}
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
if (detectorValue > Parameter.Threshold)
{
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
attenuation = Parameter.Threshold / detectorValue;
}
float outputCoefficient = Parameter.ReleaseCoefficient;
if (state.CompressionGain[channelIndex] > attenuation)
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;

View File

@@ -101,32 +101,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float inputCoefficient = Parameter.ReleaseCoefficient;
if (sampleInputMax > state.DectectorAverage[channelIndex])
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
}
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
if (detectorValue > Parameter.Threshold)
{
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
attenuation = Parameter.Threshold / detectorValue;
}
float outputCoefficient = Parameter.ReleaseCoefficient;
if (state.CompressionGain[channelIndex] > attenuation)
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@@ -144,7 +143,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax);
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], state.CompressionGain[channelIndex]);
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain);
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.Effect
{
public struct ExponentialMovingAverage
{
private float _mean;
public ExponentialMovingAverage(float mean)
{
_mean = mean;
}
public float Read()
{
return _mean;
}
public float Update(float value, float alpha)
{
_mean += alpha * (value - _mean);
return _mean;
}
}
}

View File

@@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Renderer.Dsp
return (float)value / (1 << qBits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ConvertFloat(float value, int qBits)
{
return value / (1 << qBits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToFixed(float value, int qBits)
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp
@@ -46,6 +47,53 @@ namespace Ryujinx.Audio.Renderer.Dsp
return MathF.Pow(10, x);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Log10(float x)
{
// NOTE: Nintendo uses an approximation of log10, we don't.
// As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
return MathF.Pow(10, MathF.Max(x, 1.0e-10f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float MeanSquare(ReadOnlySpan<float> inputs)
{
float res = 0.0f;
foreach (float input in inputs)
{
res += (input * input);
}
res /= inputs.Length;
return res;
}
/// <summary>
/// Map decibel to linear.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value/returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinear(float db)
{
return MathF.Pow(10.0f, db / 20.0f);
}
/// <summary>
/// Map decibel to linear in [0, 2] range.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value in [0, 2] range</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinearExtended(float db)
{
float tmp = MathF.Log2(DecibelToLinear(db));
return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{

View File

@@ -0,0 +1,51 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class CompressorState
{
public ExponentialMovingAverage InputMovingAverage;
public float Unknown4;
public ExponentialMovingAverage CompressionGainAverage;
public float CompressorGainReduction;
public float Unknown10;
public float Unknown14;
public float PreviousCompressionEmaAlpha;
public float MakeupGain;
public float OutputGain;
public CompressorState(ref CompressorParameter parameter)
{
InputMovingAverage = new ExponentialMovingAverage(0.0f);
Unknown4 = 1.0f;
CompressionGainAverage = new ExponentialMovingAverage(1.0f);
UpdateParameter(ref parameter);
}
public void UpdateParameter(ref CompressorParameter parameter)
{
float threshold = parameter.Threshold;
float ratio = 1.0f / parameter.Ratio;
float attackCoefficient = parameter.AttackCoefficient;
float makeupGain;
if (parameter.MakeupGainEnabled)
{
makeupGain = (threshold * 0.5f * (ratio - 1.0f)) - 3.0f;
}
else
{
makeupGain = 0.0f;
}
PreviousCompressionEmaAlpha = attackCoefficient;
MakeupGain = makeupGain;
CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax;
Unknown10 = threshold - 1.5f;
Unknown14 = threshold + 1.5f;
OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain);
}
}
}

View File

@@ -1,3 +1,4 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
@@ -5,20 +6,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class LimiterState
{
public float[] DectectorAverage;
public float[] CompressionGain;
public ExponentialMovingAverage[] DetectorAverage;
public ExponentialMovingAverage[] CompressionGainAverage;
public float[] DelayedSampleBuffer;
public int[] DelayedSampleBufferPosition;
public LimiterState(ref LimiterParameter parameter, ulong workBuffer)
{
DectectorAverage = new float[parameter.ChannelCount];
CompressionGain = new float[parameter.ChannelCount];
DetectorAverage = new ExponentialMovingAverage[parameter.ChannelCount];
CompressionGainAverage = new ExponentialMovingAverage[parameter.ChannelCount];
DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax];
DelayedSampleBufferPosition = new int[parameter.ChannelCount];
DectectorAverage.AsSpan().Fill(0.0f);
CompressionGain.AsSpan().Fill(1.0f);
DetectorAverage.AsSpan().Fill(new ExponentialMovingAverage(0.0f));
CompressionGainAverage.AsSpan().Fill(new ExponentialMovingAverage(1.0f));
DelayedSampleBufferPosition.AsSpan().Fill(0);
DelayedSampleBuffer.AsSpan().Fill(0.0f);

View File

@@ -0,0 +1,115 @@
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Compressor"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CompressorParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// The maximum number of channels supported.
/// </summary>
public ushort ChannelCountMax;
/// <summary>
/// The total channel count used.
/// </summary>
public ushort ChannelCount;
/// <summary>
/// The target sample rate.
/// </summary>
/// <remarks>This is in kHz.</remarks>
public int SampleRate;
/// <summary>
/// The threshold.
/// </summary>
public float Threshold;
/// <summary>
/// The compressor ratio.
/// </summary>
public float Ratio;
/// <summary>
/// The attack time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int AttackTime;
/// <summary>
/// The release time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int ReleaseTime;
/// <summary>
/// The input gain.
/// </summary>
public float InputGain;
/// <summary>
/// The attack coefficient.
/// </summary>
public float AttackCoefficient;
/// <summary>
/// The release coefficient.
/// </summary>
public float ReleaseCoefficient;
/// <summary>
/// The output gain.
/// </summary>
public float OutputGain;
/// <summary>
/// The current usage status of the effect on the client side.
/// </summary>
public UsageState Status;
/// <summary>
/// Indicate if the makeup gain should be used.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool MakeupGainEnabled;
/// <summary>
/// Reserved/padding.
/// </summary>
private Array2<byte> _reserved;
/// <summary>
/// Check if the <see cref="ChannelCount"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
/// <summary>
/// Check if the <see cref="ChannelCountMax"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
}
}
}

View File

@@ -93,6 +93,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary>
/// REV11:
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
/// A new effect was added: Compressor. This effect is effectively implemented with a DRC.
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
/// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
/// </summary>

View File

@@ -469,6 +469,18 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
{
if (parameter.IsChannelCountValid())
{
CompressorCommand command = new CompressorCommand(bufferOffset, parameter, state, isEnabled, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
}
/// <summary>
/// Generate a new <see cref="VolumeCommand"/>.
/// </summary>

View File

@@ -606,6 +606,17 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId)
{
Debug.Assert(effect.Type == EffectType.Compressor);
_commandBuffer.GenerateCompressorEffect(bufferOffset,
effect.Parameter,
effect.State,
effect.IsEnabled,
nodeId);
}
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
{
int nodeId = mix.NodeId;
@@ -650,6 +661,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.CaptureBuffer:
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
break;
case EffectType.Compressor:
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
break;
default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
}

View File

@@ -179,5 +179,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@@ -543,5 +543,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@@ -747,5 +747,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public virtual uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@@ -232,5 +232,79 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
}
public override uint Estimate(CompressorCommand command)
{
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
if (_sampleCount == 160)
{
if (command.Enabled)
{
switch (command.Parameter.ChannelCount)
{
case 1:
return 34431;
case 2:
return 44253;
case 4:
return 63827;
case 6:
return 83361;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
else
{
switch (command.Parameter.ChannelCount)
{
case 1:
return (uint)630.12f;
case 2:
return (uint)638.27f;
case 4:
return (uint)705.86f;
case 6:
return (uint)782.02f;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
}
if (command.Enabled)
{
switch (command.Parameter.ChannelCount)
{
case 1:
return 51095;
case 2:
return 65693;
case 4:
return 95383;
case 6:
return 124510;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
else
{
switch (command.Parameter.ChannelCount)
{
case 1:
return (uint)840.14f;
case 2:
return (uint)826.1f;
case 4:
return (uint)901.88f;
case 6:
return (uint)965.29f;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
}
}
}

View File

@@ -262,6 +262,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return PerformanceDetailType.Limiter;
case EffectType.CaptureBuffer:
return PerformanceDetailType.CaptureBuffer;
case EffectType.Compressor:
return PerformanceDetailType.Compressor;
default:
throw new NotImplementedException($"{Type}");
}

View File

@@ -0,0 +1,67 @@
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Effect
{
/// <summary>
/// Server state for a compressor effect.
/// </summary>
public class CompressorEffect : BaseEffect
{
/// <summary>
/// The compressor parameter.
/// </summary>
public CompressorParameter Parameter;
/// <summary>
/// The compressor state.
/// </summary>
public Memory<CompressorState> State { get; }
/// <summary>
/// Create a new <see cref="CompressorEffect"/>.
/// </summary>
public CompressorEffect()
{
State = new CompressorState[1];
}
public override EffectType TargetEffectType => EffectType.Compressor;
public override ulong GetWorkBuffer(int index)
{
return GetSingleBuffer();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
// Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
updateErrorInfo = new BehaviourParameter.ErrorInfo();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Debug.Assert(IsTypeValid(ref parameter));
UpdateParameterBase(ref parameter);
Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled;
updateErrorInfo = new BehaviourParameter.ErrorInfo();
}
public override void UpdateForCommandGeneration()
{
UpdateUsageStateForCommandGeneration();
Parameter.Status = UsageState.Enabled;
}
}
}

View File

@@ -35,5 +35,6 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(LimiterCommandVersion2 command);
uint Estimate(GroupedBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command);
uint Estimate(CompressorCommand command);
}
}

View File

@@ -240,6 +240,10 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.CaptureBuffer:
effect = new CaptureBufferEffect();
break;
case EffectType.Compressor:
effect = new CompressorEffect();
break;
default:
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
}

View File

@@ -125,7 +125,7 @@ namespace Ryujinx.Ava
_inputManager = inputManager;
_accountManager = accountManager;
_userChannelPersistence = userChannelPersistence;
_renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" };
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
_lastCursorMoveTime = Stopwatch.GetTimestamp();
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;

View File

@@ -0,0 +1,127 @@
using System;
using System.Runtime.Versioning;
using System.Runtime.InteropServices;
using Avalonia;
namespace Ryujinx.Ava.Ui.Helper
{
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
[SupportedOSPlatform("macos")]
static class MetalHelper
{
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
private struct Selector
{
public readonly IntPtr NativePtr;
public unsafe Selector(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
NativePtr = sel_registerName(data);
}
public static implicit operator Selector(string value) => new Selector(value);
}
private static unsafe IntPtr GetClass(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
return objc_getClass(data);
}
private struct NSPoint
{
public double X;
public double Y;
public NSPoint(double x, double y)
{
X = x;
Y = y;
}
}
private struct NSRect
{
public NSPoint Pos;
public NSPoint Size;
public NSRect(double x, double y, double width, double height)
{
Pos = new NSPoint(x, y);
Size = new NSPoint(width, height);
}
}
public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
{
// Create a new CAMetalLayer.
IntPtr layerClass = GetClass("CAMetalLayer");
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
objc_msgSend(metalLayer, "init");
// Create a child NSView to render into.
IntPtr nsViewClass = GetClass("NSView");
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
// Make its renderer our metal layer.
objc_msgSend(child, "setWantsLayer:", (byte)1);
objc_msgSend(child, "setLayer:", metalLayer);
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
// Ensure the scale factor is up to date.
updateBounds = (Rect rect) => {
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
};
nsView = child;
return metalLayer;
}
public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
{
// TODO
}
[DllImport(LibObjCImport)]
private static unsafe extern IntPtr sel_registerName(byte* data);
[DllImport(LibObjCImport)]
private static unsafe extern IntPtr objc_getClass(byte* data);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, byte value);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, double value);
[DllImport(LibObjCImport, EntryPoint = "objc_msgSend")]
private static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
}
}

View File

@@ -4,7 +4,6 @@ using Ryujinx.Input;
using System;
using System.Collections.Generic;
using System.Numerics;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
using Key = Ryujinx.Input.Key;
@@ -13,30 +12,37 @@ namespace Ryujinx.Ava.Input
internal class AvaloniaKeyboard : IKeyboard
{
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly AvaloniaKeyboardDriver _driver;
private readonly AvaloniaKeyboardDriver _driver;
private StandardKeyboardInputConfig _configuration;
private readonly object _userMappingLock = new();
private StandardKeyboardInputConfig _configuration;
private bool HasConfiguration => _configuration != null;
public string Id { get; }
public string Id { get; }
public string Name { get; }
public bool IsConnected => true;
public bool IsConnected => true;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
private class ButtonMappingEntry
{
public readonly Key From;
public readonly GamepadButtonInputId To;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
}
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
{
_driver = driver;
Id = id;
Name = name;
_buttonsUserMapping = new List<ButtonMappingEntry>();
}
public void Dispose() { }
_driver = driver;
Id = id;
Name = name;
}
public KeyboardStateSnapshot GetKeyboardStateSnapshot()
{
@@ -46,11 +52,11 @@ namespace Ryujinx.Ava.Input
public GamepadStateSnapshot GetMappedStateSnapshot()
{
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
GamepadStateSnapshot result = default;
GamepadStateSnapshot result = default;
lock (_userMappingLock)
{
if (!HasConfiguration)
if (_configuration == null)
{
return result;
}
@@ -62,17 +68,17 @@ namespace Ryujinx.Ava.Input
continue;
}
// Do not touch state of the button already pressed
// NOTE: Do not touch state of the button already pressed.
if (!result.IsPressed(entry.To))
{
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
}
}
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
}
@@ -114,29 +120,29 @@ namespace Ryujinx.Ava.Input
_buttonsUserMapping.Clear();
// Left joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
// Left JoyCon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
// Finally right joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
// Right JoyCon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
}
}
@@ -190,16 +196,6 @@ namespace Ryujinx.Ava.Input
_driver?.ResetKeys();
}
private class ButtonMappingEntry
{
public readonly Key From;
public readonly GamepadButtonInputId To;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
}
public void Dispose() { }
}
}

View File

@@ -4,7 +4,6 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using AvaKey = Avalonia.Input.Key;
using Key = Ryujinx.Input.Key;
@@ -13,24 +12,23 @@ namespace Ryujinx.Ava.Input
internal class AvaloniaKeyboardDriver : IGamepadDriver
{
private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
private readonly Control _control;
private readonly Control _control;
private readonly HashSet<AvaKey> _pressedKeys;
public event EventHandler<KeyEventArgs> KeyPressed;
public event EventHandler<KeyEventArgs> KeyRelease;
public event EventHandler<string> TextInput;
public string DriverName => "Avalonia";
public event EventHandler<string> TextInput;
public string DriverName => "AvaloniaKeyboardDriver";
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
public AvaloniaKeyboardDriver(Control control)
{
_control = control;
_control = control;
_pressedKeys = new HashSet<AvaKey>();
_control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease;
_control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease;
_control.TextInput += Control_TextInput;
}
@@ -41,21 +39,16 @@ namespace Ryujinx.Ava.Input
public event Action<string> OnGamepadConnected
{
add { }
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
add { }
remove { }
}
public void Dispose()
{
Dispose(true);
}
public IGamepad GetGamepad(string id)
{
if (!_keyboardIdentifers[0].Equals(id))
@@ -70,15 +63,13 @@ namespace Ryujinx.Ava.Input
{
if (disposing)
{
_control.KeyUp -= OnKeyPress;
_control.KeyUp -= OnKeyPress;
_control.KeyDown -= OnKeyRelease;
}
}
protected void OnKeyPress(object sender, KeyEventArgs args)
{
AvaKey key = args.Key;
_pressedKeys.Add(args.Key);
KeyPressed?.Invoke(this, args);
@@ -98,7 +89,7 @@ namespace Ryujinx.Ava.Input
return false;
}
AvaloniaMappingHelper.TryGetAvaKey(key, out var nativeKey);
AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out var nativeKey);
return _pressedKeys.Contains(nativeKey);
}
@@ -107,5 +98,10 @@ namespace Ryujinx.Ava.Input
{
_pressedKeys.Clear();
}
public void Dispose()
{
Dispose(true);
}
}
}

View File

@@ -5,7 +5,7 @@ using AvaKey = Avalonia.Input.Key;
namespace Ryujinx.Ava.Input
{
internal static class AvaloniaMappingHelper
internal static class AvaloniaKeyboardMappingHelper
{
private static readonly AvaKey[] _keyMapping = new AvaKey[(int)Key.Count]
{
@@ -149,11 +149,11 @@ namespace Ryujinx.Ava.Input
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
static AvaloniaMappingHelper()
static AvaloniaKeyboardMappingHelper()
{
var inputKeys = Enum.GetValues(typeof(Key));
// Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
_avaKeyMapping = new Dictionary<AvaKey, Key>();
foreach (var key in inputKeys)
@@ -167,15 +167,13 @@ namespace Ryujinx.Ava.Input
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
{
var keyExist = (int)key < _keyMapping.Length;
avaKey = AvaKey.None;
bool keyExist = (int)key < _keyMapping.Length;
if (keyExist)
{
avaKey = _keyMapping[(int)key];
}
else
{
avaKey = AvaKey.None;
}
return keyExist;
}

View File

@@ -10,15 +10,12 @@ namespace Ryujinx.Ava.Input
{
private AvaloniaMouseDriver _driver;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public string Id => "0";
public string Id => "0";
public string Name => "AvaloniaMouse";
public bool IsConnected => true;
public bool[] Buttons => _driver.PressedButtons;
public bool IsConnected => true;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public bool[] Buttons => _driver.PressedButtons;
public AvaloniaMouse(AvaloniaMouseDriver driver)
{

View File

@@ -11,35 +11,50 @@ namespace Ryujinx.Ava.Input
{
internal class AvaloniaMouseDriver : IGamepadDriver
{
private Control _widget;
private bool _isDisposed;
private Size _size;
private Control _widget;
private bool _isDisposed;
private Size _size;
private readonly Window _window;
public bool[] PressedButtons { get; }
public bool[] PressedButtons { get; }
public Vector2 CurrentPosition { get; private set; }
public Vector2 Scroll { get; private set; }
public Vector2 Scroll { get; private set; }
public string DriverName => "AvaloniaMouseDriver";
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public AvaloniaMouseDriver(Window window, Control parent)
{
_widget = parent;
_window = window;
_widget.PointerMoved += Parent_PointerMovedEvent;
_widget.PointerPressed += Parent_PointerPressEvent;
_widget.PointerReleased += Parent_PointerReleaseEvent;
_widget.PointerMoved += Parent_PointerMovedEvent;
_widget.PointerPressed += Parent_PointerPressEvent;
_widget.PointerReleased += Parent_PointerReleaseEvent;
_widget.PointerWheelChanged += Parent_ScrollEvent;
_window.PointerMoved += Parent_PointerMovedEvent;
_window.PointerPressed += Parent_PointerPressEvent;
_window.PointerReleased += Parent_PointerReleaseEvent;
_window.PointerMoved += Parent_PointerMovedEvent;
_window.PointerPressed += Parent_PointerPressEvent;
_window.PointerReleased += Parent_PointerReleaseEvent;
_window.PointerWheelChanged += Parent_ScrollEvent;
PressedButtons = new bool[(int)MouseButton.Count];
_size = new Size((int)parent.Bounds.Width, (int)parent.Bounds.Height);
parent.GetObservable(Control.BoundsProperty).Subscribe(Resized);
parent.GetObservable(Visual.BoundsProperty).Subscribe(Resized);
}
public event Action<string> OnGamepadConnected
{
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
remove { }
}
private void Resized(Rect rect)
@@ -59,14 +74,12 @@ namespace Ryujinx.Ava.Input
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
{
var pointerProperties = args.GetCurrentPoint(_widget).Properties;
PressedButtons[(int)pointerProperties.PointerUpdateKind] = true;
PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true;
}
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
{
var position = args.GetPosition(_widget);
Point position = args.GetPosition(_widget);
CurrentPosition = new Vector2((float)position.X, (float)position.Y);
}
@@ -96,22 +109,6 @@ namespace Ryujinx.Ava.Input
return _size;
}
public string DriverName => "Avalonia";
public event Action<string> OnGamepadConnected
{
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
remove { }
}
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public IGamepad GetGamepad(string id)
{
return new AvaloniaMouse(this);
@@ -126,14 +123,14 @@ namespace Ryujinx.Ava.Input
_isDisposed = true;
_widget.PointerMoved -= Parent_PointerMovedEvent;
_widget.PointerPressed -= Parent_PointerPressEvent;
_widget.PointerReleased -= Parent_PointerReleaseEvent;
_widget.PointerMoved -= Parent_PointerMovedEvent;
_widget.PointerPressed -= Parent_PointerPressEvent;
_widget.PointerReleased -= Parent_PointerReleaseEvent;
_widget.PointerWheelChanged -= Parent_ScrollEvent;
_window.PointerMoved -= Parent_PointerMovedEvent;
_window.PointerPressed -= Parent_PointerPressEvent;
_window.PointerReleased -= Parent_PointerReleaseEvent;
_window.PointerMoved -= Parent_PointerMovedEvent;
_window.PointerPressed -= Parent_PointerPressEvent;
_window.PointerReleased -= Parent_PointerReleaseEvent;
_window.PointerWheelChanged -= Parent_ScrollEvent;
_widget = null;

View File

@@ -22,10 +22,11 @@ namespace Ryujinx.Ava
{
internal class Program
{
public static double WindowScaleFactor { get; set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);

View File

@@ -28,11 +28,13 @@
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.12.8" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />

View File

@@ -68,7 +68,7 @@ namespace Ryujinx.Ava.Ui.Applet
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, Avalonia.Input.KeyEventArgs e)
{
var key = (HidKey)AvaloniaMappingHelper.ToInputKey(e.Key);
var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
{
@@ -88,7 +88,7 @@ namespace Ryujinx.Ava.Ui.Applet
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
{
var key = (HidKey)AvaloniaMappingHelper.ToInputKey(e.Key);
var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
{

View File

@@ -2,11 +2,10 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Ryujinx.Ava.Ui.Helper;
using SPB.Graphics;
using SPB.Platform;
using SPB.Platform.GLX;
using SPB.Platform.X11;
using SPB.Windowing;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@@ -23,6 +22,10 @@ namespace Ryujinx.Ava.Ui.Controls
protected GLXWindow X11Window { get; set; }
protected IntPtr WindowHandle { get; set; }
protected IntPtr X11Display { get; set; }
protected IntPtr NsView { get; set; }
protected IntPtr MetalLayer { get; set; }
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
public event EventHandler<IntPtr> WindowCreated;
public event EventHandler<Size> SizeChanged;
@@ -58,6 +61,7 @@ namespace Ryujinx.Ava.Ui.Controls
private void StateChanged(Rect rect)
{
SizeChanged?.Invoke(this, rect.Size);
_updateBoundsCallback?.Invoke(rect);
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
@@ -70,6 +74,11 @@ namespace Ryujinx.Ava.Ui.Controls
{
return CreateWin32(parent);
}
else if (OperatingSystem.IsMacOS())
{
return CreateMacOs(parent);
}
return base.CreateNativeControlCore(parent);
}
@@ -85,6 +94,10 @@ namespace Ryujinx.Ava.Ui.Controls
{
DestroyWin32(control);
}
else if (OperatingSystem.IsMacOS())
{
DestroyMacOS();
}
else
{
base.DestroyNativeControlCore(control);
@@ -187,6 +200,16 @@ namespace Ryujinx.Ava.Ui.Controls
return DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam);
}
[SupportedOSPlatform("macos")]
IPlatformHandle CreateMacOs(IPlatformHandle parent)
{
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
NsView = nsView;
return new PlatformHandle(nsView, "NSView");
}
void DestroyLinux()
{
X11Window?.Dispose();
@@ -198,5 +221,11 @@ namespace Ryujinx.Ava.Ui.Controls
DestroyWindow(handle.Handle);
UnregisterClass(_className, GetModuleHandle(null));
}
[SupportedOSPlatform("macos")]
void DestroyMacOS()
{
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
}
}
}

View File

@@ -3,6 +3,7 @@ using Ryujinx.Ava.Ui.Controls;
using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan;
using SPB.Platform.GLX;
using SPB.Platform.Metal;
using SPB.Platform.Win32;
using SPB.Platform.X11;
using SPB.Windowing;
@@ -37,6 +38,10 @@ namespace Ryujinx.Ava.Ui
{
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
}
else if (OperatingSystem.IsMacOS())
{
_window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
}
else
{
throw new PlatformNotSupportedException();

View File

@@ -1,45 +0,0 @@
using Ryujinx.Ui.App.Common;
using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
internal class FileSizeSortComparer : IComparer
{
public int Compare(object x, object y)
{
string aValue = (x as ApplicationData).TimePlayed;
string bValue = (y as ApplicationData).TimePlayed;
if (aValue[^3..] == "GiB")
{
aValue = (float.Parse(aValue[0..^3]) * 1024).ToString();
}
else
{
aValue = aValue[0..^3];
}
if (bValue[^3..] == "GiB")
{
bValue = (float.Parse(bValue[0..^3]) * 1024).ToString();
}
else
{
bValue = bValue[0..^3];
}
if (float.Parse(aValue) > float.Parse(bValue))
{
return -1;
}
else if (float.Parse(bValue) > float.Parse(aValue))
{
return 1;
}
else
{
return 0;
}
}
}
}

View File

@@ -1,50 +0,0 @@
using Ryujinx.Ui.App.Common;
using System.Collections.Generic;
namespace Ryujinx.Ava.Ui.Models.Generic
{
internal class FileSizeSortComparer : IComparer<ApplicationData>
{
public FileSizeSortComparer() { }
public FileSizeSortComparer(bool isAscending) { _order = isAscending ? 1 : -1; }
private int _order;
public int Compare(ApplicationData x, ApplicationData y)
{
string aValue = x.FileSize;
string bValue = y.FileSize;
if (aValue[^3..] == "GiB")
{
aValue = (float.Parse(aValue[0..^3]) * 1024).ToString();
}
else
{
aValue = aValue[0..^3];
}
if (bValue[^3..] == "GiB")
{
bValue = (float.Parse(bValue[0..^3]) * 1024).ToString();
}
else
{
bValue = bValue[0..^3];
}
if (float.Parse(aValue) > float.Parse(bValue))
{
return -1 * _order;
}
else if (float.Parse(bValue) > float.Parse(aValue))
{
return 1 * _order;
}
else
{
return 0;
}
}
}
}

View File

@@ -1,66 +0,0 @@
using Ryujinx.Ui.App.Common;
using System.Collections.Generic;
namespace Ryujinx.Ava.Ui.Models.Generic
{
internal class TimePlayedSortComparer : IComparer<ApplicationData>
{
public TimePlayedSortComparer() { }
public TimePlayedSortComparer(bool isAscending) { _order = isAscending ? 1 : -1; }
private int _order;
public int Compare(ApplicationData x, ApplicationData y)
{
string aValue = x.TimePlayed;
string bValue = y.TimePlayed;
if (aValue.Length > 4 && aValue[^4..] == "mins")
{
aValue = (float.Parse(aValue[0..^5]) * 60).ToString();
}
else if (aValue.Length > 3 && aValue[^3..] == "hrs")
{
aValue = (float.Parse(aValue[0..^4]) * 3600).ToString();
}
else if (aValue.Length > 4 && aValue[^4..] == "days")
{
aValue = (float.Parse(aValue[0..^5]) * 86400).ToString();
}
else
{
aValue = aValue[0..^1];
}
if (bValue.Length > 4 && bValue[^4..] == "mins")
{
bValue = (float.Parse(bValue[0..^5]) * 60).ToString();
}
else if (bValue.Length > 3 && bValue[^3..] == "hrs")
{
bValue = (float.Parse(bValue[0..^4]) * 3600).ToString();
}
else if (bValue.Length > 4 && bValue[^4..] == "days")
{
bValue = (float.Parse(bValue[0..^5]) * 86400).ToString();
}
else
{
bValue = bValue[0..^1];
}
if (float.Parse(aValue) > float.Parse(bValue))
{
return -1 * _order;
}
else if (float.Parse(bValue) > float.Parse(aValue))
{
return 1 * _order;
}
else
{
return 0;
}
}
}
}

View File

@@ -1,27 +0,0 @@
using Ryujinx.Ui.App.Common;
using System;
using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
internal class LastPlayedSortComparer : IComparer
{
public int Compare(object x, object y)
{
string aValue = (x as ApplicationData).LastPlayed;
string bValue = (y as ApplicationData).LastPlayed;
if (aValue == "Never")
{
aValue = DateTime.UnixEpoch.ToString();
}
if (bValue == "Never")
{
bValue = DateTime.UnixEpoch.ToString();
}
return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
}
}
}

View File

@@ -1,61 +0,0 @@
using Ryujinx.Ui.App.Common;
using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
internal class TimePlayedSortComparer : IComparer
{
public int Compare(object x, object y)
{
string aValue = (x as ApplicationData).TimePlayed;
string bValue = (y as ApplicationData).TimePlayed;
if (aValue.Length > 4 && aValue[^4..] == "mins")
{
aValue = (float.Parse(aValue[0..^5]) * 60).ToString();
}
else if (aValue.Length > 3 && aValue[^3..] == "hrs")
{
aValue = (float.Parse(aValue[0..^4]) * 3600).ToString();
}
else if (aValue.Length > 4 && aValue[^4..] == "days")
{
aValue = (float.Parse(aValue[0..^5]) * 86400).ToString();
}
else
{
aValue = aValue[0..^1];
}
if (bValue.Length > 4 && bValue[^4..] == "mins")
{
bValue = (float.Parse(bValue[0..^5]) * 60).ToString();
}
else if (bValue.Length > 3 && bValue[^3..] == "hrs")
{
bValue = (float.Parse(bValue[0..^4]) * 3600).ToString();
}
else if (bValue.Length > 4 && bValue[^4..] == "days")
{
bValue = (float.Parse(bValue[0..^5]) * 86400).ToString();
}
else
{
bValue = bValue[0..^1];
}
if (float.Parse(aValue) > float.Parse(bValue))
{
return -1;
}
else if (float.Parse(bValue) > float.Parse(aValue))
{
return 1;
}
else
{
return 0;
}
}
}
}

View File

@@ -502,8 +502,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
return SortMode switch
{
ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending),
ApplicationSort.FileSize => new Models.Generic.FileSizeSortComparer(IsAscending),
ApplicationSort.TotalTimePlayed => new Models.Generic.TimePlayedSortComparer(IsAscending),
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSizeBytes)
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSizeBytes),
ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TimePlayedNum)
: SortExpressionComparer<ApplicationData>.Descending(app => app.TimePlayedNum),
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
@@ -881,17 +883,17 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void LoadConfigurableHotKeys()
{
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
{
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
}
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
{
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
}
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
{
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
}

View File

@@ -108,6 +108,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
}
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
public bool DirectoryChanged
{
get => _directoryChanged;

View File

@@ -154,6 +154,12 @@ namespace Ryujinx.Ava.Ui.Windows
}
}
protected override void HandleScalingChanged(double scale)
{
Program.DesktopScaleFactor = scale;
base.HandleScalingChanged(scale);
}
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
{
if (args.Application != null)

View File

@@ -540,7 +540,7 @@
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
<TextBlock Text="Vulkan" />
</ComboBoxItem>
<ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
<TextBlock Text="OpenGL" />
</ComboBoxItem>
</ComboBox>

View File

@@ -45,7 +45,14 @@ namespace Ryujinx.Common.Configuration
public static void Initialize(string baseDirPath)
{
string userProfilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (appDataPath.Length == 0)
{
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
}
string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
if (Directory.Exists(portablePath))

View File

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

View File

@@ -21,10 +21,11 @@ namespace Ryujinx.Graphics.Shader.Translation
{
BasicBlock block = blocks[blkIndex];
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
for (LinkedListNode<INode> node = block.Operations.First; node != null;)
{
if (node.Value is not Operation operation)
{
node = node.Next;
continue;
}
@@ -43,10 +44,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
if (UsesGlobalMemory(operation.Inst))
{
node = RewriteGlobalAccess(node, config);
}
LinkedListNode<INode> nextNode = node.Next;
if (operation is TextureOperation texOp)
{
@@ -59,7 +57,15 @@ namespace Ryujinx.Graphics.Shader.Translation
node = InsertSnormNormalization(node, config);
}
}
nextNode = node.Next;
}
else if (UsesGlobalMemory(operation.Inst))
{
nextNode = RewriteGlobalAccess(node, config)?.Next ?? nextNode;
}
node = nextNode;
}
}
}
@@ -72,7 +78,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool isStg16Or8 = operation.Inst == Instruction.StoreGlobal16 || operation.Inst == Instruction.StoreGlobal8;
bool isWrite = isAtomic || operation.Inst == Instruction.StoreGlobal || isStg16Or8;
Operation storageOp;
Operation storageOp = null;
Operand PrependOperation(Instruction inst, params Operand[] sources)
{
@@ -120,49 +126,56 @@ namespace Ryujinx.Graphics.Shader.Translation
sbSlot = PrependOperation(Instruction.ConditionalSelect, inRange, Const(slot), sbSlot);
}
Operand alignMask = Const(-config.GpuAccessor.QueryHostStorageBufferOffsetAlignment());
Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd, sbBaseAddrLow, alignMask);
Operand byteOffset = PrependOperation(Instruction.Subtract, addrLow, baseAddrTrunc);
Operand[] sources = new Operand[operation.SourcesCount];
sources[0] = sbSlot;
if (isStg16Or8)
if (config.AccessibleStorageBuffersMask != 0)
{
sources[1] = byteOffset;
}
else
{
sources[1] = PrependOperation(Instruction.ShiftRightU32, byteOffset, Const(2));
}
Operand alignMask = Const(-config.GpuAccessor.QueryHostStorageBufferOffsetAlignment());
for (int index = 2; index < operation.SourcesCount; index++)
{
sources[index] = operation.GetSource(index);
}
Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd, sbBaseAddrLow, alignMask);
Operand byteOffset = PrependOperation(Instruction.Subtract, addrLow, baseAddrTrunc);
if (isAtomic)
{
Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage;
Operand[] sources = new Operand[operation.SourcesCount];
storageOp = new Operation(inst, operation.Dest, sources);
}
else if (operation.Inst == Instruction.LoadGlobal)
{
storageOp = new Operation(Instruction.LoadStorage, operation.Dest, sources);
}
else
{
Instruction storeInst = operation.Inst switch
sources[0] = sbSlot;
if (isStg16Or8)
{
Instruction.StoreGlobal16 => Instruction.StoreStorage16,
Instruction.StoreGlobal8 => Instruction.StoreStorage8,
_ => Instruction.StoreStorage
};
sources[1] = byteOffset;
}
else
{
sources[1] = PrependOperation(Instruction.ShiftRightU32, byteOffset, Const(2));
}
storageOp = new Operation(storeInst, null, sources);
for (int index = 2; index < operation.SourcesCount; index++)
{
sources[index] = operation.GetSource(index);
}
if (isAtomic)
{
Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage;
storageOp = new Operation(inst, operation.Dest, sources);
}
else if (operation.Inst == Instruction.LoadGlobal)
{
storageOp = new Operation(Instruction.LoadStorage, operation.Dest, sources);
}
else
{
Instruction storeInst = operation.Inst switch
{
Instruction.StoreGlobal16 => Instruction.StoreStorage16,
Instruction.StoreGlobal8 => Instruction.StoreStorage8,
_ => Instruction.StoreStorage
};
storageOp = new Operation(storeInst, null, sources);
}
}
else if (operation.Dest != null)
{
storageOp = new Operation(Instruction.Copy, operation.Dest, Const(0));
}
for (int index = 0; index < operation.SourcesCount; index++)
@@ -171,10 +184,18 @@ namespace Ryujinx.Graphics.Shader.Translation
}
LinkedListNode<INode> oldNode = node;
LinkedList<INode> oldNodeList = oldNode.List;
node = node.List.AddBefore(node, storageOp);
if (storageOp != null)
{
node = node.List.AddBefore(node, storageOp);
}
else
{
node = null;
}
node.List.Remove(oldNode);
oldNodeList.Remove(oldNode);
return node;
}

View File

@@ -1,4 +1,4 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
@@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
{
ExtConditionalRendering.ExtensionName,
ExtExtendedDynamicState.ExtensionName,
ExtTransformFeedback.ExtensionName,
KhrDrawIndirectCount.ExtensionName,
KhrPushDescriptor.ExtensionName,
"VK_EXT_custom_border_color",
@@ -36,8 +37,7 @@ namespace Ryujinx.Graphics.Vulkan
public static string[] RequiredExtensions { get; } = new string[]
{
KhrSwapchain.ExtensionName,
ExtTransformFeedback.ExtensionName
KhrSwapchain.ExtensionName
};
private static string[] _excludedMessages = new string[]
@@ -382,12 +382,12 @@ namespace Ryujinx.Graphics.Vulkan
DepthClamp = true,
DualSrcBlend = true,
FragmentStoresAndAtomics = true,
GeometryShader = true,
GeometryShader = supportedFeatures.GeometryShader,
ImageCubeArray = true,
IndependentBlend = true,
LogicOp = true,
LogicOp = supportedFeatures.LogicOp,
MultiViewport = true,
PipelineStatisticsQuery = true,
PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery,
SamplerAnisotropy = true,
ShaderClipDistance = true,
ShaderFloat64 = supportedFeatures.ShaderFloat64,

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -11,7 +11,6 @@ using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
using Ryujinx.Common;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
@@ -68,8 +67,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
int ryujinxLogoSize = 32;
Stream logoStream = EmbeddedResources.GetStream("Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png");
_ryujinxLogo = LoadResource(logoStream, ryujinxLogoSize, ryujinxLogoSize);
string ryujinxIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Logo_Ryujinx.png";
_ryujinxLogo = LoadResource(Assembly.GetExecutingAssembly(), ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize);
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
@@ -117,7 +116,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
uiThemeFontFamily,
"Liberation Sans",
"FreeSans",
"DejaVu Sans"
"DejaVu Sans",
"Lucida Grande"
};
foreach (string fontFamily in availableFonts)

View File

@@ -26,7 +26,7 @@
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
</ItemGroup>
<!-- Due to Concentus. -->
@@ -36,6 +36,7 @@
<ItemGroup>
<None Remove="Homebrew.npdm" />
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
@@ -44,6 +45,7 @@
<ItemGroup>
<EmbeddedResource Include="Homebrew.npdm" />
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />

View File

@@ -77,6 +77,26 @@ namespace Ryujinx.Headless.SDL2
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
_userChannelPersistence = new UserChannelPersistence();
if (OperatingSystem.IsMacOS())
{
AutoResetEvent invoked = new AutoResetEvent(false);
// MacOS must perform SDL polls from the main thread.
Ryujinx.SDL2.Common.SDL2Driver.MainThreadDispatcher = (Action action) =>
{
invoked.Reset();
WindowBase.QueueMainThreadAction(() =>
{
action();
invoked.Set();
});
invoked.WaitOne();
};
}
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = true;

View File

@@ -12,7 +12,8 @@
<ItemGroup>
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,7 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Input.HLE;
using Ryujinx.SDL2.Common;
using System;
using System.Runtime.InteropServices;
using static SDL2.SDL;
@@ -26,15 +27,34 @@ namespace Ryujinx.Headless.SDL2.Vulkan
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
}
private void BasicInvoke(Action action)
{
action();
}
public unsafe IntPtr CreateWindowSurface(IntPtr instance)
{
if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out ulong surfaceHandle) == SDL_bool.SDL_FALSE)
ulong surfaceHandle = 0;
Action createSurface = () =>
{
string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\"";
if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out surfaceHandle) == SDL_bool.SDL_FALSE)
{
string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\"";
Logger.Error?.Print(LogClass.Application, errorMessage);
Logger.Error?.Print(LogClass.Application, errorMessage);
throw new Exception(errorMessage);
throw new Exception(errorMessage);
}
};
if (SDL2Driver.MainThreadDispatcher != null)
{
SDL2Driver.MainThreadDispatcher(createSurface);
}
else
{
createSurface();
}
return (IntPtr)surfaceHandle;

View File

@@ -11,6 +11,7 @@ using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
@@ -26,6 +27,13 @@ namespace Ryujinx.Headless.SDL2
private const SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN;
private const int TargetFps = 60;
private static ConcurrentQueue<Action> MainThreadActions = new ConcurrentQueue<Action>();
public static void QueueMainThreadAction(Action action)
{
MainThreadActions.Enqueue(action);
}
public NpadManager NpadManager { get; }
public TouchScreenManager TouchScreenManager { get; }
public Switch Device { get; private set; }
@@ -168,6 +176,14 @@ namespace Ryujinx.Headless.SDL2
public void Render()
{
InitializeWindowRenderer();
Device.Gpu.Renderer.Initialize(_glLogLevel);
InitializeRenderer();
_gpuVendorName = GetGpuVendorName();
Device.Gpu.Renderer.RunLoop(() =>
{
Device.Gpu.SetGpuThread();
@@ -241,6 +257,14 @@ namespace Ryujinx.Headless.SDL2
_exitEvent.Dispose();
}
public void ProcessMainThreadQueue()
{
while (MainThreadActions.TryDequeue(out Action action))
{
action();
}
}
public void MainLoop()
{
while (_isActive)
@@ -249,6 +273,8 @@ namespace Ryujinx.Headless.SDL2
SDL_PumpEvents();
ProcessMainThreadQueue();
// Polling becomes expensive if it's not slept
Thread.Sleep(1);
}
@@ -315,14 +341,6 @@ namespace Ryujinx.Headless.SDL2
InitializeWindow();
InitializeWindowRenderer();
Device.Gpu.Renderer.Initialize(_glLogLevel);
InitializeRenderer();
_gpuVendorName = GetGpuVendorName();
Thread renderLoopThread = new Thread(Render)
{
Name = "GUI.RenderLoop"

View File

@@ -9,7 +9,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
</ItemGroup>
</Project>

View File

@@ -153,7 +153,8 @@ namespace Ryujinx.Memory
if (OperatingSystem.IsMacOSVersionAtLeast(10, 14))
{
result |= MAP_JIT_DARWIN;
// Only to be used with the Hardened Runtime.
// result |= MAP_JIT_DARWIN;
}
return result;

View File

@@ -0,0 +1,16 @@
using NUnit.Framework;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System.Runtime.CompilerServices;
namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
{
class CompressorParameterTests
{
[Test]
public void EnsureTypeSize()
{
Assert.AreEqual(0x38, Unsafe.SizeOf<CompressorParameter>());
}
}
}

View File

@@ -12,9 +12,11 @@ namespace Ryujinx.Ui.App.Common
public string Developer { get; set; }
public string Version { get; set; }
public string TimePlayed { get; set; }
public double TimePlayedNum { get; set; }
public string LastPlayed { get; set; }
public string FileExtension { get; set; }
public string FileSize { get; set; }
public double FileSizeBytes { get; set; }
public string Path { get; set; }
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
}

View File

@@ -465,9 +465,11 @@ namespace Ryujinx.Ui.App.Common
Developer = developer,
Version = version,
TimePlayed = ConvertSecondsToReadableString(appMetadata.TimePlayed),
TimePlayedNum = appMetadata.TimePlayed,
LastPlayed = appMetadata.LastPlayed,
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1),
FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + " MiB" : fileSize.ToString("0.##") + " GiB",
FileSizeBytes = fileSize,
Path = applicationPath,
ControlHolder = controlHolder
};

View File

@@ -680,7 +680,7 @@ namespace Ryujinx.Ui.Common.Configuration
Hid.EnableMouse.Value = false;
Hid.Hotkeys.Value = new KeyboardHotkeys
{
ToggleVsync = Key.Tab,
ToggleVsync = Key.F1,
ToggleMute = Key.F2,
Screenshot = Key.F8,
ShowUi = Key.F4,
@@ -818,7 +818,7 @@ namespace Ryujinx.Ui.Common.Configuration
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
ToggleVsync = Key.Tab
ToggleVsync = Key.F1
};
configurationFileUpdated = true;
@@ -999,7 +999,7 @@ namespace Ryujinx.Ui.Common.Configuration
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
ToggleVsync = Key.Tab,
ToggleVsync = Key.F1,
Screenshot = Key.F8
};
@@ -1012,7 +1012,7 @@ namespace Ryujinx.Ui.Common.Configuration
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
ToggleVsync = Key.Tab,
ToggleVsync = Key.F1,
Screenshot = Key.F8,
ShowUi = Key.F4
};

View File

@@ -25,7 +25,7 @@ namespace Ryujinx.Modules
public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { }
private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetObject("UpdateDialog").Handle)
private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog"))
{
builder.Autoconnect(this);

View File

@@ -16,6 +16,7 @@ using Ryujinx.Ui.Widgets;
using SixLabors.ImageSharp.Formats.Jpeg;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
@@ -40,6 +41,12 @@ namespace Ryujinx
[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
[DllImport("libc", SetLastError = true)]
static extern int setenv(string name, string value, int overwrite);
[DllImport("libc")]
static extern IntPtr getenv(string name);
private const uint MB_ICONWARNING = 0x30;
static Program()
@@ -97,6 +104,35 @@ namespace Ryujinx
XInitThreads();
}
if (OperatingSystem.IsMacOS())
{
string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
string resourcesDataDir;
if (Path.GetFileName(baseDirectory) == "MacOS")
{
resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources");
}
else
{
resourcesDataDir = baseDirectory;
}
void SetEnvironmentVariableNoCaching(string key, string value)
{
int res = setenv(key, value, 1);
Debug.Assert(res != -1);
}
// On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories.
SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
// On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories.
SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache"));
}
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");

View File

@@ -19,10 +19,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GtkSharp" Version="3.22.25.128" />
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="GtkSharp.Dependencies.osx" Version="0.0.5" Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
<PackageReference Include="OpenTK.Graphics" Version="4.7.5" />
<PackageReference Include="SPB" Version="0.0.4-build28" />
@@ -62,10 +65,6 @@
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
<DefineConstants>$(DefineConstants);MACOS_BUILD</DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Remove="Ui\MainWindow.glade" />
<None Remove="Ui\Widgets\ProfileDialog.glade" />

View File

@@ -0,0 +1,134 @@
using Gdk;
using System;
using System.Runtime.Versioning;
using System.Runtime.InteropServices;
namespace Ryujinx.Ui.Helper
{
public delegate void UpdateBoundsCallbackDelegate(Window window);
[SupportedOSPlatform("macos")]
static class MetalHelper
{
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
private struct Selector
{
public readonly IntPtr NativePtr;
public unsafe Selector(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
NativePtr = sel_registerName(data);
}
public static implicit operator Selector(string value) => new Selector(value);
}
private static unsafe IntPtr GetClass(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
return objc_getClass(data);
}
private struct NSPoint
{
public double X;
public double Y;
public NSPoint(double x, double y)
{
X = x;
Y = y;
}
}
private struct NSRect
{
public NSPoint Pos;
public NSPoint Size;
public NSRect(double x, double y, double width, double height)
{
Pos = new NSPoint(x, y);
Size = new NSPoint(width, height);
}
}
public static IntPtr GetMetalLayer(Display display, Window window, out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
{
nsView = gdk_quartz_window_get_nsview(window.Handle);
// Create a new CAMetalLayer.
IntPtr layerClass = GetClass("CAMetalLayer");
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
objc_msgSend(metalLayer, "init");
// Create a child NSView to render into.
IntPtr nsViewClass = GetClass("NSView");
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
objc_msgSend(child, "init", new NSRect());
// Add it as a child.
objc_msgSend(nsView, "addSubview:", child);
// Make its renderer our metal layer.
objc_msgSend(child, "setWantsLayer:", (byte)1);
objc_msgSend(child, "setLayer:", metalLayer);
objc_msgSend(metalLayer, "setContentsScale:", (double)display.GetMonitorAtWindow(window).ScaleFactor);
// Set the frame position/location.
updateBounds = (Window window) => {
window.GetPosition(out int x, out int y);
int width = window.Width;
int height = window.Height;
objc_msgSend(child, "setFrame:", new NSRect(x, y, width, height));
};
updateBounds(window);
return metalLayer;
}
[DllImport(LibObjCImport)]
private static unsafe extern IntPtr sel_registerName(byte* data);
[DllImport(LibObjCImport)]
private static unsafe extern IntPtr objc_getClass(byte* data);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, byte value);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, double value);
[DllImport(LibObjCImport, EntryPoint = "objc_msgSend")]
private static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
[DllImport("libgdk-3.0.dylib")]
private static extern IntPtr gdk_quartz_window_get_nsview(IntPtr gdkWindow);
}
}

View File

@@ -9,46 +9,56 @@ namespace Ryujinx.Ui.Helper
{
string aValue = model.GetValue(a, 5).ToString();
string bValue = model.GetValue(b, 5).ToString();
float aFloat;
float bFloat;
if (aValue.Length > 4 && aValue[^4..] == "mins")
if (aValue.Length > 7 && aValue[^7..] == "minutes")
{
aValue = (float.Parse(aValue[0..^5]) * 60).ToString();
aValue = aValue.Replace("minutes", "");
aFloat = (float.Parse(aValue) * 60);
}
else if (aValue.Length > 3 && aValue[^3..] == "hrs")
else if (aValue.Length > 5 && aValue[^5..] == "hours")
{
aValue = (float.Parse(aValue[0..^4]) * 3600).ToString();
aValue = aValue.Replace("hours", "");
aFloat = (float.Parse(aValue) * 3600);
}
else if (aValue.Length > 4 && aValue[^4..] == "days")
{
aValue = (float.Parse(aValue[0..^5]) * 86400).ToString();
aValue = aValue.Replace("days", "");
aFloat = (float.Parse(aValue) * 86400);
}
else
{
aValue = aValue[0..^1];
aValue = aValue.Replace("seconds", "");
aFloat = float.Parse(aValue);
}
if (bValue.Length > 4 && bValue[^4..] == "mins")
if (bValue.Length > 7 && bValue[^7..] == "minutes")
{
bValue = (float.Parse(bValue[0..^5]) * 60).ToString();
bValue = bValue.Replace("minutes", "");
bFloat = (float.Parse(bValue) * 60);
}
else if (bValue.Length > 3 && bValue[^3..] == "hrs")
else if (bValue.Length > 5 && bValue[^5..] == "hours")
{
bValue = (float.Parse(bValue[0..^4]) * 3600).ToString();
bValue = bValue.Replace("hours", "");
bFloat = (float.Parse(bValue) * 3600);
}
else if (bValue.Length > 4 && bValue[^4..] == "days")
{
bValue = (float.Parse(bValue[0..^5]) * 86400).ToString();
bValue = bValue.Replace("days", "");
bFloat = (float.Parse(bValue) * 86400);
}
else
{
bValue = bValue[0..^1];
bValue = bValue[0..^8];
bFloat = float.Parse(bValue);
}
if (float.Parse(aValue) > float.Parse(bValue))
if (aFloat > bFloat)
{
return -1;
}
else if (float.Parse(bValue) > float.Parse(aValue))
else if (bFloat > aFloat)
{
return 1;
}

View File

@@ -142,7 +142,7 @@ namespace Ryujinx.Ui
public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { }
private MainWindow(Builder builder) : base(builder.GetObject("_mainWin").Handle)
private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin"))
{
builder.Autoconnect(this);
@@ -846,9 +846,7 @@ namespace Ryujinx.Ui
_deviceExitStatus.Reset();
Translator.IsReadyForTranslation.Reset();
#if MACOS_BUILD
CreateGameWindow();
#else
Thread windowThread = new Thread(() =>
{
CreateGameWindow();
@@ -858,7 +856,6 @@ namespace Ryujinx.Ui
};
windowThread.Start();
#endif
_gameLoaded = true;
_actionMenu.Sensitive = true;

View File

@@ -1,9 +1,11 @@
using Gdk;
using Ryujinx.Common.Configuration;
using Ryujinx.Input.HLE;
using Ryujinx.Ui.Helper;
using SPB.Graphics.Vulkan;
using SPB.Platform.Win32;
using SPB.Platform.X11;
using SPB.Platform.Metal;
using SPB.Windowing;
using System;
using System.Runtime.InteropServices;
@@ -13,6 +15,7 @@ namespace Ryujinx.Ui
public class VKRenderer : RendererWidgetBase
{
public NativeWindowBase NativeWindow { get; private set; }
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
public VKRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) { }
@@ -31,6 +34,12 @@ namespace Ryujinx.Ui
return new SimpleX11Window(new NativeHandle(displayHandle), new NativeHandle(windowHandle));
}
else if (OperatingSystem.IsMacOS())
{
IntPtr metalLayer = MetalHelper.GetMetalLayer(Display, Window, out IntPtr nsView, out _updateBoundsCallback);
return new SimpleMetalWindow(new NativeHandle(nsView), new NativeHandle(metalLayer));
}
throw new NotImplementedException();
}
@@ -53,7 +62,11 @@ namespace Ryujinx.Ui
WaitEvent.Set();
}
return base.OnConfigureEvent(evnt);
bool result = base.OnConfigureEvent(evnt);
_updateBoundsCallback?.Invoke(Window);
return result;
}
public unsafe IntPtr CreateWindowSurface(IntPtr instance)

View File

@@ -18,7 +18,7 @@ namespace Ryujinx.Ui.Widgets
public ProfileDialog() : this(new Builder("Ryujinx.Ui.Widgets.ProfileDialog.glade")) { }
private ProfileDialog(Builder builder) : base(builder.GetObject("_profileDialog").Handle)
private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog"))
{
builder.Autoconnect(this);
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");

View File

@@ -23,7 +23,7 @@ namespace Ryujinx.Ui.Windows
public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName) { }
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : base(builder.GetObject("_cheatWindow").Handle)
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : base(builder.GetRawOwnedObject("_cheatWindow"))
{
builder.Autoconnect(this);
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";

View File

@@ -119,7 +119,7 @@ namespace Ryujinx.Ui.Windows
public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { }
private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetObject("_controllerWin").Handle)
private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin"))
{
_mainWindow = mainWindow;
_selectedGamepad = null;
@@ -379,13 +379,16 @@ namespace Ryujinx.Ui.Windows
break;
}
_controllerImage.Pixbuf = _controllerType.ActiveId switch
if (!OperatingSystem.IsMacOS())
{
"ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_ProCon.svg", 400, 400),
"JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConLeft.svg", 400, 500),
"JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConRight.svg", 400, 500),
_ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConPair.svg", 400, 500),
};
_controllerImage.Pixbuf = _controllerType.ActiveId switch
{
"ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_ProCon.svg", 400, 400),
"JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConLeft.svg", 400, 500),
"JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConRight.svg", 400, 500),
_ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConPair.svg", 400, 500),
};
}
}
private void ClearValues()

View File

@@ -34,7 +34,7 @@ namespace Ryujinx.Ui.Windows
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_dlcWindow").Handle)
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
{
builder.Autoconnect(this);

View File

@@ -113,7 +113,7 @@ namespace Ryujinx.Ui.Windows
public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Ui.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle)
private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin"))
{
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
@@ -422,7 +422,7 @@ namespace Ryujinx.Ui.Windows
Task.Run(() =>
{
openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported;
soundIoIsSupported = SoundIoHardwareDeviceDriver.IsSupported;
soundIoIsSupported = !OperatingSystem.IsMacOS() && SoundIoHardwareDeviceDriver.IsSupported;
sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported;
});
@@ -438,6 +438,15 @@ namespace Ryujinx.Ui.Windows
_ => throw new ArgumentOutOfRangeException()
};
});
if (OperatingSystem.IsMacOS())
{
var store = (_graphicsBackend.Model as ListStore);
store.GetIter(out TreeIter openglIter, new TreePath(new int[] {1}));
store.Remove(ref openglIter);
_graphicsBackend.Model = store;
}
}
private void UpdatePreferredGpuComboBox()

View File

@@ -40,7 +40,7 @@ namespace Ryujinx.Ui.Windows
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_titleUpdateWindow").Handle)
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
{
_parent = parent;