Compare commits

...

13 Commits

Author SHA1 Message Date
ac4f2c1e70 Avalonia: Show aspect ratio popup options in status bar (#5780)
* Show aspect ratio selection popup in status bar

* Add aspect ratio tooltip

* Fix typo
2023-10-08 11:04:41 +02:00
e40470bbe1 Fix return value of Get function when a result does not yet exist for the address. (#5768) 2023-10-07 17:42:10 +02:00
f460ecc182 GPU: Add HLE macros for popular NVN macros (#5761)
* GPU: Add HLE macros for popular NVN macros

* Remove non-vector equality check

The case where it's not hardware accelerated will do the check integer-wise anyways.

* Whitespace 😔

* Address Feedback
2023-10-06 19:55:07 -03:00
086564c3c8 HLE: Fix Mii crc generation and minor issues (#5766)
* HLE: Fix Mii crc generation

Validating CRCs for data and device involves calculating the crc of all data including the crc being checked, which should then be 0.

The crc should be _generated_ on all data _before_ the crc in the struct. It shouldn't include the crcs themselves.

This fixes all generated miis (eg. default) having invalid crcs. This does not affect mii maker, as that generates its own charinfo.

Does not fix MK8D crash.

* Fix other mii issues

* Fully define all fields for Nickname and Ver3StoreData

Fixes an issue where the nickname for a mii would only have the first character on some method calls.

* Add Array96 type
2023-10-06 19:23:39 -03:00
b6ac45d36d Fix SPIR-V call out arguments regression (#5767)
* Fix SPIR-V call out arguments regression

* Shader cache version bump
2023-10-06 00:18:30 -03:00
7afae8c699 nuget: bump Microsoft.CodeAnalysis.CSharp from 4.6.0 to 4.7.0 (#5608)
Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.6.0 to 4.7.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/commits)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-05 13:40:03 +02:00
7835968214 Strings should not be concatenated using '+' in a loop (#5664)
* Strings should not be concatenated using '+' in a loop

* fix IDE0090

* undo GenerateLoadOrStore

* prefer string interpolation

* Update src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs

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

---------

Co-authored-by: Mary <thog@protonmail.com>
2023-10-05 12:41:00 +02:00
0aceb534cb Fix SPIR-V function calls (#5764)
* Fix SPIR-V function calls

* Shader cache version bump
2023-10-04 21:35:26 -03:00
a0af6e4d07 Use unique temporary variables for function call parameters on SPIR-V (#5757)
* Use unique temporary variables for function call parameters on SPIR-V

* Shader cache version bump
2023-10-04 19:46:11 -03:00
jcm
f61b7818c3 Avalonia: Add macOS check for Color Space Passthrough (#5754)
* add macOS check for color passthrough

* use existing IsMacOS property

---------

Co-authored-by: jcm <butt@butts.com>
2023-10-04 19:15:37 +02:00
a2a97e1b11 Implement textureSamples texture query shader instruction (#5750)
* Implement textureSamples texture query shader instruction

* Shader cache version bump
2023-10-03 22:43:11 +00:00
8b2625b0be Decrement nvmap reference count on surface flinger prealloc (#5753) 2023-10-02 22:13:29 +00:00
651e24fed9 Signal friends completion event and stub CheckBlockedUserListAvailability (#5743) 2023-09-29 13:24:44 +02:00
50 changed files with 873 additions and 201 deletions

View File

@ -20,7 +20,7 @@
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
<PackageVersion Include="LibHac" Version="0.18.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />

View File

@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace ARMeilleure.Diagnostics
{
@ -33,7 +34,6 @@ namespace ARMeilleure.Diagnostics
public static string Get(ulong address)
{
if (_symbols.TryGetValue(address, out string result))
{
return result;
@ -48,13 +48,15 @@ namespace ARMeilleure.Diagnostics
ulong diff = address - symbol.Start;
ulong rem = diff % symbol.ElementSize;
result = symbol.Name + "_" + diff / symbol.ElementSize;
StringBuilder resultBuilder = new();
resultBuilder.Append($"{symbol.Name}_{diff / symbol.ElementSize}");
if (rem != 0)
{
result += "+" + rem;
resultBuilder.Append($"+{rem}");
}
result = resultBuilder.ToString();
_symbols.TryAdd(address, result);
return result;

View File

@ -327,7 +327,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image;
string usageString = "";
StringBuilder usageStringBuilder = new();
for (int i = 0; i < _amiiboList.Count; i++)
{
@ -341,20 +341,19 @@ namespace Ryujinx.Ava.UI.ViewModels
{
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
{
usageString += Environment.NewLine +
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
writable = usageItem.Write;
}
}
}
if (usageString.Length == 0)
if (usageStringBuilder.Length == 0)
{
usageString = LocaleManager.Instance[LocaleKeys.Unknown] + ".";
usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}.");
}
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageString}";
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}";
}
}

View File

@ -1278,6 +1278,11 @@ namespace Ryujinx.Ava.UI.ViewModels
Glyph = Glyph.Grid;
}
public void SetAspectRatio(AspectRatio aspectRatio)
{
ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
}
public async Task InstallFirmwareFromFile()
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions

View File

@ -147,6 +147,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableTextureRecompression { get; set; }
public bool EnableMacroHLE { get; set; }
public bool EnableColorSpacePassthrough { get; set; }
public bool ColorSpacePassthroughAvailable => IsMacOS;
public bool EnableFileLog { get; set; }
public bool EnableStub { get; set; }
public bool EnableInfo { get; set; }

View File

@ -6,6 +6,7 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:config="clr-namespace:Ryujinx.Common.Configuration;assembly=Ryujinx.Common"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
x:DataType="viewModels:MainWindowViewModel">
@ -112,15 +113,52 @@
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
<SplitButton
Name="AspectRatioStatus"
Margin="5,0,5,0"
Padding="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="Transparent"
BorderThickness="0"
CornerRadius="0"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="AspectRatioStatus_PointerReleased"
Text="{Binding AspectRatioStatusText}"
TextAlignment="Left" />
Content="{Binding AspectRatioStatusText}"
Click="AspectRatioStatus_OnClick"
ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
<SplitButton.Styles>
<Style Selector="Border#SeparatorBorder">
<Setter Property="Opacity" Value="0" />
</Style>
</SplitButton.Styles>
<SplitButton.Flyout>
<MenuFlyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio4x3}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed4x3}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio16x9}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed16x9}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio16x10}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed16x10}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio21x9}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed21x9}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio32x9}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed32x9}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatioStretch}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Stretched}"/>
</MenuFlyout>
</SplitButton.Flyout>
</SplitButton>
<Border
Width="2"
Height="12"

View File

@ -43,10 +43,9 @@ namespace Ryujinx.Ava.UI.Views.Main
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
}
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
private void AspectRatioStatus_OnClick(object sender, RoutedEventArgs e)
{
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
}

View File

@ -73,6 +73,7 @@
<TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableColorSpacePassthrough}"
IsVisible="{Binding ColorSpacePassthroughAvailable}"
ToolTip.Tip="{locale:Locale SettingsEnableColorSpacePassthroughTooltip}">
<TextBlock Text="{locale:Locale SettingsEnableColorSpacePassthrough}" />
</CheckBox>
@ -296,4 +297,4 @@
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>
</UserControl>

View File

@ -756,6 +756,18 @@ namespace Ryujinx.Common.Memory
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array96<T> : IArray<T> where T : unmanaged
{
T _e0;
Array64<T> _other;
Array31<T> _other2;
public readonly int Length => 96;
public ref T this[int index] => ref AsSpan()[index];
[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array127<T> : IArray<T> where T : unmanaged
{
T _e0;

View File

@ -32,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// </summary>
public ref TState State => ref _state.State;
/// <summary>
/// Current shadow state.
/// </summary>
public ref TState ShadowState => ref _shadowState.State;
/// <summary>
/// Creates a new instance of the device state, with shadow state.
/// </summary>

View File

@ -1,7 +1,10 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Types;
using System;
using System.Collections.Generic;
@ -15,9 +18,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
private const int ColorLayerCountOffset = 0x818;
private const int ColorStructSize = 0x40;
private const int ZetaLayerCountOffset = 0x1230;
private const int UniformBufferBindVertexOffset = 0x2410;
private const int FirstVertexOffset = 0x1434;
private const int IndirectIndexedDataEntrySize = 0x14;
private const int LogicOpOffset = 0x19c4;
private const int ShaderIdScratchOffset = 0x3470;
private const int ShaderAddressScratchOffset = 0x3488;
private const int UpdateConstantBufferAddressesBase = 0x34a8;
private const int UpdateConstantBufferSizesBase = 0x34bc;
private const int UpdateConstantBufferAddressCbu = 0x3460;
private readonly GPFifoProcessor _processor;
private readonly MacroHLEFunctionName _functionName;
@ -49,6 +61,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
{
switch (_functionName)
{
case MacroHLEFunctionName.BindShaderProgram:
BindShaderProgram(state, arg0);
break;
case MacroHLEFunctionName.ClearColor:
ClearColor(state, arg0);
break;
@ -58,6 +73,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
case MacroHLEFunctionName.DrawArraysInstanced:
DrawArraysInstanced(state, arg0);
break;
case MacroHLEFunctionName.DrawElements:
DrawElements(state, arg0);
break;
case MacroHLEFunctionName.DrawElementsInstanced:
DrawElementsInstanced(state, arg0);
break;
@ -67,6 +85,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
case MacroHLEFunctionName.MultiDrawElementsIndirectCount:
MultiDrawElementsIndirectCount(state, arg0);
break;
case MacroHLEFunctionName.UpdateBlendState:
UpdateBlendState(state, arg0);
break;
case MacroHLEFunctionName.UpdateColorMasks:
UpdateColorMasks(state, arg0);
break;
case MacroHLEFunctionName.UpdateUniformBufferState:
UpdateUniformBufferState(state, arg0);
break;
case MacroHLEFunctionName.UpdateUniformBufferStateCbu:
UpdateUniformBufferStateCbu(state, arg0);
break;
case MacroHLEFunctionName.UpdateUniformBufferStateCbuV2:
UpdateUniformBufferStateCbuV2(state, arg0);
break;
default:
throw new NotImplementedException(_functionName.ToString());
}
@ -75,6 +108,149 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
Fifo.Clear();
}
/// <summary>
/// Binds a shader program with the index in arg0.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void BindShaderProgram(IDeviceState state, int arg0)
{
int scratchOffset = ShaderIdScratchOffset + arg0 * 4;
int lastId = state.Read(scratchOffset);
int id = FetchParam().Word;
int offset = FetchParam().Word;
if (lastId == id)
{
FetchParam();
FetchParam();
return;
}
_processor.ThreedClass.SetShaderOffset(arg0, (uint)offset);
// Removes overflow on the method address into the increment portion.
// Present in the original macro.
int addrMask = unchecked((int)0xfffc0fff) << 2;
state.Write(scratchOffset & addrMask, id);
state.Write((ShaderAddressScratchOffset + arg0 * 4) & addrMask, offset);
int stage = FetchParam().Word;
uint cbAddress = (uint)FetchParam().Word;
_processor.ThreedClass.UpdateUniformBufferState(65536, cbAddress >> 24, cbAddress << 8);
int stageOffset = (stage & 0x7f) << 3;
state.Write((UniformBufferBindVertexOffset + stageOffset * 4) & addrMask, 17);
}
/// <summary>
/// Updates uniform buffer state for update or bind.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void UpdateUniformBufferState(IDeviceState state, int arg0)
{
uint address = (uint)state.Read(UpdateConstantBufferAddressesBase + arg0 * 4);
int size = state.Read(UpdateConstantBufferSizesBase + arg0 * 4);
_processor.ThreedClass.UpdateUniformBufferState(size, address >> 24, address << 8);
}
/// <summary>
/// Updates uniform buffer state for update.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void UpdateUniformBufferStateCbu(IDeviceState state, int arg0)
{
uint address = (uint)state.Read(UpdateConstantBufferAddressCbu);
UniformBufferState ubState = new()
{
Address = new()
{
High = address >> 24,
Low = address << 8
},
Size = 24320,
Offset = arg0 << 2
};
_processor.ThreedClass.UpdateUniformBufferState(ubState);
}
/// <summary>
/// Updates uniform buffer state for update.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void UpdateUniformBufferStateCbuV2(IDeviceState state, int arg0)
{
uint address = (uint)state.Read(UpdateConstantBufferAddressCbu);
UniformBufferState ubState = new()
{
Address = new()
{
High = address >> 24,
Low = address << 8
},
Size = 28672,
Offset = arg0 << 2
};
_processor.ThreedClass.UpdateUniformBufferState(ubState);
}
/// <summary>
/// Updates blend enable using the given argument.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void UpdateBlendState(IDeviceState state, int arg0)
{
state.Write(LogicOpOffset, 0);
Array8<Boolean32> enable = new();
for (int i = 0; i < 8; i++)
{
enable[i] = new Boolean32((uint)(arg0 >> (i + 8)) & 1);
}
_processor.ThreedClass.UpdateBlendEnable(ref enable);
}
/// <summary>
/// Updates color masks using the given argument and three pushed arguments.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void UpdateColorMasks(IDeviceState state, int arg0)
{
Array8<RtColorMask> masks = new();
int index = 0;
for (int i = 0; i < 4; i++)
{
masks[index++] = new RtColorMask((uint)arg0 & 0x1fff);
masks[index++] = new RtColorMask(((uint)arg0 >> 16) & 0x1fff);
if (i != 3)
{
arg0 = FetchParam().Word;
}
}
_processor.ThreedClass.UpdateColorMasks(ref masks);
}
/// <summary>
/// Clears one bound color target.
/// </summary>
@ -129,6 +305,36 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
indexed: false);
}
/// <summary>
/// Performs a indexed draw.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void DrawElements(IDeviceState state, int arg0)
{
var topology = (PrimitiveTopology)arg0;
var indexAddressHigh = FetchParam();
var indexAddressLow = FetchParam();
var indexType = FetchParam();
var firstIndex = 0;
var indexCount = FetchParam();
_processor.ThreedClass.UpdateIndexBuffer(
(uint)indexAddressHigh.Word,
(uint)indexAddressLow.Word,
(IndexType)indexType.Word);
_processor.ThreedClass.Draw(
topology,
indexCount.Word,
1,
firstIndex,
state.Read(FirstVertexOffset),
0,
indexed: true);
}
/// <summary>
/// Performs a indexed draw.
/// </summary>

View File

@ -6,11 +6,19 @@
enum MacroHLEFunctionName
{
None,
BindShaderProgram,
ClearColor,
ClearDepthStencil,
DrawArraysInstanced,
DrawElements,
DrawElementsInstanced,
DrawElementsIndirect,
MultiDrawElementsIndirectCount,
UpdateBlendState,
UpdateColorMasks,
UpdateUniformBufferState,
UpdateUniformBufferStateCbu,
UpdateUniformBufferStateCbuV2
}
}

View File

@ -46,12 +46,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
private static readonly TableEntry[] _table = new TableEntry[]
{
new(MacroHLEFunctionName.BindShaderProgram, new Hash128(0x5d5efb912369f60b, 0x69131ed5019f08ef), 0x68),
new(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28),
new(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24),
new(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48),
new(MacroHLEFunctionName.DrawElements, new Hash128(0x3D7F32AE6C2702A7, 0x9353C9F41C1A244D), 0x20),
new(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c),
new(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c),
new(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C),
new(MacroHLEFunctionName.UpdateBlendState, new Hash128(0x40F6D4E7B08D7640, 0x82167BEEAECB959F), 0x28),
new(MacroHLEFunctionName.UpdateColorMasks, new Hash128(0x9EE32420B8441DFD, 0x6E7724759A57333E), 0x24),
new(MacroHLEFunctionName.UpdateUniformBufferState, new Hash128(0x8EE66706049CB0B0, 0x51C1CF906EC86F7C), 0x20),
new(MacroHLEFunctionName.UpdateUniformBufferStateCbu, new Hash128(0xA4592676A3E581A0, 0xA39E77FE19FE04AC), 0x18),
new(MacroHLEFunctionName.UpdateUniformBufferStateCbuV2, new Hash128(0x392FA750489983D4, 0x35BACE455155D2C3), 0x18)
};
/// <summary>
@ -62,18 +69,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
/// <returns>True if the host supports the HLE macro, false otherwise</returns>
private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name)
{
if (name == MacroHLEFunctionName.ClearColor ||
name == MacroHLEFunctionName.ClearDepthStencil ||
name == MacroHLEFunctionName.DrawArraysInstanced ||
name == MacroHLEFunctionName.DrawElementsInstanced ||
name == MacroHLEFunctionName.DrawElementsIndirect)
{
return true;
}
else if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
{
return caps.SupportsIndirectParameters;
}
else if (name != MacroHLEFunctionName.None)
{
return true;
}
return false;
}

View File

@ -10,4 +10,22 @@ namespace Ryujinx.Graphics.Gpu.Engine
MethodPassthrough = 2,
MethodReplay = 3,
}
static class SetMmeShadowRamControlModeExtensions
{
public static bool IsTrack(this SetMmeShadowRamControlMode mode)
{
return mode == SetMmeShadowRamControlMode.MethodTrack || mode == SetMmeShadowRamControlMode.MethodTrackWithFilter;
}
public static bool IsPassthrough(this SetMmeShadowRamControlMode mode)
{
return mode == SetMmeShadowRamControlMode.MethodPassthrough;
}
public static bool IsReplay(this SetMmeShadowRamControlMode mode)
{
return mode == SetMmeShadowRamControlMode.MethodReplay;
}
}
}

View File

@ -17,9 +17,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
class StateUpdater
{
public const int ShaderStateIndex = 26;
public const int RtColorMaskIndex = 14;
public const int RasterizerStateIndex = 15;
public const int ScissorStateIndex = 16;
public const int VertexBufferStateIndex = 0;
public const int BlendStateIndex = 2;
public const int IndexBufferStateIndex = 23;
public const int PrimitiveRestartStateIndex = 12;
public const int RenderTargetStateIndex = 27;

View File

@ -1,12 +1,15 @@
using Ryujinx.Graphics.Device;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Synchronization;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
@ -26,6 +29,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private readonly ConstantBufferUpdater _cbUpdater;
private readonly StateUpdater _stateUpdater;
private SetMmeShadowRamControlMode ShadowMode => _state.State.SetMmeShadowRamControlMode;
/// <summary>
/// Creates a new instance of the 3D engine class.
/// </summary>
@ -228,6 +233,206 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_cbUpdater.Update(data);
}
/// <summary>
/// Test if two 32 byte structs are equal.
/// </summary>
/// <typeparam name="T">Type of the 32-byte struct</typeparam>
/// <param name="lhs">First struct</param>
/// <param name="rhs">Second struct</param>
/// <returns>True if equal, false otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool UnsafeEquals32Byte<T>(ref T lhs, ref T rhs) where T : unmanaged
{
if (Vector256.IsHardwareAccelerated)
{
return Vector256.EqualsAll(
Unsafe.As<T, Vector256<uint>>(ref lhs),
Unsafe.As<T, Vector256<uint>>(ref rhs)
);
}
else
{
ref var lhsVec = ref Unsafe.As<T, Vector128<uint>>(ref lhs);
ref var rhsVec = ref Unsafe.As<T, Vector128<uint>>(ref rhs);
return Vector128.EqualsAll(lhsVec, rhsVec) &&
Vector128.EqualsAll(Unsafe.Add(ref lhsVec, 1), Unsafe.Add(ref rhsVec, 1));
}
}
/// <summary>
/// Updates blend enable. Respects current shadow mode.
/// </summary>
/// <param name="masks">Blend enable</param>
public void UpdateBlendEnable(ref Array8<Boolean32> enable)
{
var shadow = ShadowMode;
ref var state = ref _state.State.BlendEnable;
if (shadow.IsReplay())
{
enable = _state.ShadowState.BlendEnable;
}
if (!UnsafeEquals32Byte(ref enable, ref state))
{
state = enable;
_stateUpdater.ForceDirty(StateUpdater.BlendStateIndex);
}
if (shadow.IsTrack())
{
_state.ShadowState.BlendEnable = enable;
}
}
/// <summary>
/// Updates color masks. Respects current shadow mode.
/// </summary>
/// <param name="masks">Color masks</param>
public void UpdateColorMasks(ref Array8<RtColorMask> masks)
{
var shadow = ShadowMode;
ref var state = ref _state.State.RtColorMask;
if (shadow.IsReplay())
{
masks = _state.ShadowState.RtColorMask;
}
if (!UnsafeEquals32Byte(ref masks, ref state))
{
state = masks;
_stateUpdater.ForceDirty(StateUpdater.RtColorMaskIndex);
}
if (shadow.IsTrack())
{
_state.ShadowState.RtColorMask = masks;
}
}
/// <summary>
/// Updates index buffer state for an indexed draw. Respects current shadow mode.
/// </summary>
/// <param name="addrHigh">High part of the address</param>
/// <param name="addrLow">Low part of the address</param>
/// <param name="type">Type of the binding</param>
public void UpdateIndexBuffer(uint addrHigh, uint addrLow, IndexType type)
{
var shadow = ShadowMode;
ref var state = ref _state.State.IndexBufferState;
if (shadow.IsReplay())
{
ref var shadowState = ref _state.ShadowState.IndexBufferState;
addrHigh = shadowState.Address.High;
addrLow = shadowState.Address.Low;
type = shadowState.Type;
}
if (state.Address.High != addrHigh || state.Address.Low != addrLow || state.Type != type)
{
state.Address.High = addrHigh;
state.Address.Low = addrLow;
state.Type = type;
_stateUpdater.ForceDirty(StateUpdater.IndexBufferStateIndex);
}
if (shadow.IsTrack())
{
ref var shadowState = ref _state.ShadowState.IndexBufferState;
shadowState.Address.High = addrHigh;
shadowState.Address.Low = addrLow;
shadowState.Type = type;
}
}
/// <summary>
/// Updates uniform buffer state for update or bind. Respects current shadow mode.
/// </summary>
/// <param name="size">Size of the binding</param>
/// <param name="addrHigh">High part of the addrsss</param>
/// <param name="addrLow">Low part of the address</param>
public void UpdateUniformBufferState(int size, uint addrHigh, uint addrLow)
{
var shadow = ShadowMode;
ref var state = ref _state.State.UniformBufferState;
if (shadow.IsReplay())
{
ref var shadowState = ref _state.ShadowState.UniformBufferState;
size = shadowState.Size;
addrHigh = shadowState.Address.High;
addrLow = shadowState.Address.Low;
}
state.Size = size;
state.Address.High = addrHigh;
state.Address.Low = addrLow;
if (shadow.IsTrack())
{
ref var shadowState = ref _state.ShadowState.UniformBufferState;
shadowState.Size = size;
shadowState.Address.High = addrHigh;
shadowState.Address.Low = addrLow;
}
}
/// <summary>
/// Updates a shader offset. Respects current shadow mode.
/// </summary>
/// <param name="index">Index of the shader to update</param>
/// <param name="offset">Offset to update with</param>
public void SetShaderOffset(int index, uint offset)
{
var shadow = ShadowMode;
ref var shaderState = ref _state.State.ShaderState[index];
if (shadow.IsReplay())
{
offset = _state.ShadowState.ShaderState[index].Offset;
}
if (shaderState.Offset != offset)
{
shaderState.Offset = offset;
_stateUpdater.ForceDirty(StateUpdater.ShaderStateIndex);
}
if (shadow.IsTrack())
{
_state.ShadowState.ShaderState[index].Offset = offset;
}
}
/// <summary>
/// Updates uniform buffer state for update. Respects current shadow mode.
/// </summary>
/// <param name="ubState">Uniform buffer state</param>
public void UpdateUniformBufferState(UniformBufferState ubState)
{
var shadow = ShadowMode;
ref var state = ref _state.State.UniformBufferState;
if (shadow.IsReplay())
{
ubState = _state.ShadowState.UniformBufferState;
}
state = ubState;
if (shadow.IsTrack())
{
_state.ShadowState.UniformBufferState = ubState;
}
}
/// <summary>
/// Launches the Inline-to-Memory DMA copy operation.
/// </summary>

View File

@ -590,9 +590,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
struct RtColorMask
{
#pragma warning disable CS0649 // Field is never assigned to
public uint Packed;
#pragma warning restore CS0649
public RtColorMask(uint packed)
{
Packed = packed;
}
/// <summary>
/// Unpacks red channel enable.

View File

@ -5,9 +5,12 @@
/// </summary>
readonly struct Boolean32
{
#pragma warning disable CS0649 // Field is never assigned to
private readonly uint _value;
#pragma warning restore CS0649
public Boolean32(uint value)
{
_value = value;
}
public static implicit operator bool(Boolean32 value)
{

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 = 5682;
private const uint CodeGenVersion = 5767;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@ -92,14 +92,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static string GetIndentation(int level)
{
string indentation = string.Empty;
StringBuilder indentationBuilder = new();
for (int index = 0; index < level; index++)
{
indentation += Tab;
indentationBuilder.Append(Tab);
}
return indentation;
return indentationBuilder.ToString();
}
}
}

View File

@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Text;
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenBallot;
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenCall;
@ -67,11 +68,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
int arity = (int)(info.Type & InstType.ArityMask);
string args = string.Empty;
StringBuilder builder = new();
if (atomic && (operation.StorageKind == StorageKind.StorageBuffer || operation.StorageKind == StorageKind.SharedMemory))
{
args = GenerateLoadOrStore(context, operation, isStore: false);
builder.Append(GenerateLoadOrStore(context, operation, isStore: false));
AggregateType dstType = operation.Inst == Instruction.AtomicMaxS32 || operation.Inst == Instruction.AtomicMinS32
? AggregateType.S32
@ -79,7 +80,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
{
args += ", " + GetSoureExpr(context, operation.GetSource(argIndex), dstType);
builder.Append($", {GetSoureExpr(context, operation.GetSource(argIndex), dstType)}");
}
}
else
@ -88,16 +89,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
if (argIndex != 0)
{
args += ", ";
builder.Append(", ");
}
AggregateType dstType = GetSrcVarType(inst, argIndex);
args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
builder.Append(GetSoureExpr(context, operation.GetSource(argIndex), dstType));
}
}
return info.OpName + '(' + args + ')';
return $"{info.OpName}({builder})";
}
else if ((info.Type & InstType.Op) != 0)
{
@ -184,8 +185,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.TextureSample:
return TextureSample(context, operation);
case Instruction.TextureSize:
return TextureSize(context, operation);
case Instruction.TextureQuerySamples:
return TextureQuerySamples(context, operation);
case Instruction.TextureQuerySize:
return TextureQuerySize(context, operation);
case Instruction.UnpackDouble2x32:
return UnpackDouble2x32(context, operation);

View File

@ -118,7 +118,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
Add(Instruction.TextureSample, InstType.Special);
Add(Instruction.TextureSize, InstType.Special);
Add(Instruction.TextureQuerySamples, InstType.Special);
Add(Instruction.TextureQuerySize, InstType.Special);
Add(Instruction.Truncate, InstType.CallUnary, "trunc");
Add(Instruction.UnpackDouble2x32, InstType.Special);
Add(Instruction.UnpackHalf2x16, InstType.Special);

View File

@ -517,7 +517,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return texCall;
}
public static string TextureSize(CodeGenContext context, AstOperation operation)
public static string TextureQuerySamples(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
string indexExpr = null;
if (isIndexed)
{
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
return $"textureSamples({samplerName})";
}
public static string TextureQuerySize(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
@ -753,17 +779,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetMaskMultiDest(int mask)
{
string swizzle = ".";
StringBuilder swizzleBuilder = new();
swizzleBuilder.Append('.');
for (int i = 0; i < 4; i++)
{
if ((mask & (1 << i)) != 0)
{
swizzle += "xyzw"[i];
swizzleBuilder.Append("xyzw"[i]);
}
}
return swizzle;
return swizzleBuilder.ToString();
}
}
}

View File

@ -44,7 +44,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public StructuredFunction CurrentFunction { get; set; }
private readonly Dictionary<AstOperand, Instruction> _locals = new();
private readonly Dictionary<int, Instruction[]> _localForArgs = new();
private readonly Dictionary<int, Instruction> _funcArgs = new();
private readonly Dictionary<int, (StructuredFunction, Instruction)> _functions = new();
@ -112,7 +111,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
IsMainFunction = isMainFunction;
MayHaveReturned = false;
_locals.Clear();
_localForArgs.Clear();
_funcArgs.Clear();
}
@ -169,11 +167,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
_locals.Add(local, spvLocal);
}
public void DeclareLocalForArgs(int funcIndex, Instruction[] spvLocals)
{
_localForArgs.Add(funcIndex, spvLocals);
}
public void DeclareArgument(int argIndex, Instruction spvLocal)
{
_funcArgs.Add(argIndex, spvLocal);
@ -278,11 +271,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return _locals[local];
}
public Instruction[] GetLocalForArgsPointers(int funcIndex)
{
return _localForArgs[funcIndex];
}
public Instruction GetArgumentPointer(AstOperand funcArg)
{
return _funcArgs[funcArg.Value];

View File

@ -41,28 +41,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
public static void DeclareLocalForArgs(CodeGenContext context, List<StructuredFunction> functions)
{
for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
{
StructuredFunction function = functions[funcIndex];
SpvInstruction[] locals = new SpvInstruction[function.InArguments.Length];
for (int i = 0; i < function.InArguments.Length; i++)
{
var type = function.GetArgumentType(i);
var localPointerType = context.TypePointer(StorageClass.Function, context.GetType(type));
var spvLocal = context.Variable(localPointerType, StorageClass.Function);
context.AddLocalVariable(spvLocal);
locals[i] = spvLocal;
}
context.DeclareLocalForArgs(funcIndex, locals);
}
}
public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
{
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);

View File

@ -134,7 +134,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.Subtract, GenerateSubtract);
Add(Instruction.SwizzleAdd, GenerateSwizzleAdd);
Add(Instruction.TextureSample, GenerateTextureSample);
Add(Instruction.TextureSize, GenerateTextureSize);
Add(Instruction.TextureQuerySamples, GenerateTextureQuerySamples);
Add(Instruction.TextureQuerySize, GenerateTextureQuerySize);
Add(Instruction.Truncate, GenerateTruncate);
Add(Instruction.UnpackDouble2x32, GenerateUnpackDouble2x32);
Add(Instruction.UnpackHalf2x16, GenerateUnpackHalf2x16);
@ -310,26 +311,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var (function, spvFunc) = context.GetFunction(funcId.Value);
var args = new SpvInstruction[operation.SourcesCount - 1];
var spvLocals = context.GetLocalForArgsPointers(funcId.Value);
for (int i = 0; i < args.Length; i++)
{
var operand = operation.GetSource(i + 1);
if (i >= function.InArguments.Length)
{
args[i] = context.GetLocalPointer((AstOperand)operand);
}
else
{
var type = function.GetArgumentType(i);
var value = context.Get(type, operand);
var spvLocal = spvLocals[i];
context.Store(spvLocal, value);
args[i] = spvLocal;
}
AstOperand local = (AstOperand)operand;
Debug.Assert(local.Type == OperandType.LocalVariable);
args[i] = context.GetLocalPointer(local);
}
var retType = function.ReturnType;
@ -1492,7 +1481,36 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(swizzledResultType, result);
}
private static OperationResult GenerateTextureSize(CodeGenContext context, AstOperation operation)
private static OperationResult GenerateTextureQuerySamples(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
if (isIndexed)
{
context.GetS32(texOp.GetSource(0));
}
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
image = context.Image(imageType, image);
SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image);
return new OperationResult(AggregateType.S32, result);
}
private static OperationResult GenerateTextureQuerySize(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;

View File

@ -161,7 +161,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.EnterBlock(function.MainBlock);
Declarations.DeclareLocals(context, function);
Declarations.DeclareLocalForArgs(context, info.Functions);
Generate(context, function.MainBlock);

View File

@ -1094,7 +1094,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (isBindless)
{
type = (componentMask & 4) != 0 ? SamplerType.Texture3D : SamplerType.Texture2D;
if (query == TexQuery.TexHeaderTextureType)
{
type = SamplerType.Texture2D | SamplerType.Multisample;
}
else
{
type = (componentMask & 4) != 0 ? SamplerType.Texture3D : SamplerType.Texture2D;
}
}
else
{
@ -1102,31 +1109,69 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
int binding;
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureSize,
type,
TextureFormat.Unknown,
flags,
TextureOperation.DefaultCbufSlot,
imm);
for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
switch (query)
{
if ((compMask & 1) != 0)
{
Operand d = GetDest();
case TexQuery.TexHeaderDimension:
binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureQuerySize,
type,
TextureFormat.Unknown,
flags,
TextureOperation.DefaultCbufSlot,
imm);
if (d == null)
for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
{
break;
if ((compMask & 1) != 0)
{
Operand d = GetDest();
if (d == null)
{
break;
}
context.Copy(d, context.TextureQuerySize(type, flags, binding, compIndex, sources));
}
}
break;
// TODO: Validate and use query parameter.
Operand res = context.TextureSize(type, flags, binding, compIndex, sources);
case TexQuery.TexHeaderTextureType:
binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureQuerySamples,
type,
TextureFormat.Unknown,
flags,
TextureOperation.DefaultCbufSlot,
imm);
context.Copy(d, res);
}
if ((componentMask & 4) != 0)
{
// Skip first 2 components if necessary.
if ((componentMask & 1) != 0)
{
GetDest();
}
if ((componentMask & 2) != 0)
{
GetDest();
}
Operand d = GetDest();
if (d != null)
{
context.Copy(d, context.TextureQuerySamples(type, flags, binding, sources));
}
}
break;
default:
context.TranslatorContext.GpuAccessor.Log($"Invalid or unsupported query type \"{query}\".");
break;
}
}

View File

@ -1,10 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{
[Flags]
[SuppressMessage("Design", "CA1069: Enums values should not be duplicated")]
enum Instruction
{
Absolute = 1,
@ -118,7 +116,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Subtract,
SwizzleAdd,
TextureSample,
TextureSize,
TextureQuerySamples,
TextureQuerySize,
Truncate,
UnpackDouble2x32,
UnpackHalf2x16,
@ -160,7 +159,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public static bool IsTextureQuery(this Instruction inst)
{
inst &= Instruction.Mask;
return inst == Instruction.Lod || inst == Instruction.TextureSize;
return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize;
}
}
}

View File

@ -124,7 +124,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Add(Instruction.Subtract, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.SwizzleAdd, AggregateType.FP32, AggregateType.FP32, AggregateType.FP32, AggregateType.S32);
Add(Instruction.TextureSample, AggregateType.FP32);
Add(Instruction.TextureSize, AggregateType.S32, AggregateType.S32, AggregateType.S32);
Add(Instruction.TextureQuerySamples, AggregateType.S32, AggregateType.S32);
Add(Instruction.TextureQuerySize, AggregateType.S32, AggregateType.S32, AggregateType.S32);
Add(Instruction.Truncate, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.UnpackDouble2x32, AggregateType.U32, AggregateType.FP64);
Add(Instruction.UnpackHalf2x16, AggregateType.FP32, AggregateType.U32);

View File

@ -2,17 +2,22 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class StructuredProgram
{
// TODO: Eventually it should be possible to specify the parameter types for the function instead of using S32 for everything.
private const AggregateType FuncParameterType = AggregateType.S32;
public static StructuredProgramInfo MakeStructuredProgram(
IReadOnlyList<Function> functions,
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ResourceManager resourceManager,
TargetLanguage targetLanguage,
bool debugMode)
{
StructuredProgramContext context = new(attributeUsage, definitions, resourceManager, debugMode);
@ -23,19 +28,19 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
BasicBlock[] blocks = function.Blocks;
AggregateType returnType = function.ReturnsValue ? AggregateType.S32 : AggregateType.Void;
AggregateType returnType = function.ReturnsValue ? FuncParameterType : AggregateType.Void;
AggregateType[] inArguments = new AggregateType[function.InArgumentsCount];
AggregateType[] outArguments = new AggregateType[function.OutArgumentsCount];
for (int i = 0; i < inArguments.Length; i++)
{
inArguments[i] = AggregateType.S32;
inArguments[i] = FuncParameterType;
}
for (int i = 0; i < outArguments.Length; i++)
{
outArguments[i] = AggregateType.S32;
outArguments[i] = FuncParameterType;
}
context.EnterFunction(blocks.Length, function.Name, returnType, inArguments, outArguments);
@ -58,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
AddOperation(context, operation);
AddOperation(context, operation, targetLanguage, functions);
}
}
}
@ -73,7 +78,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return context.Info;
}
private static void AddOperation(StructuredProgramContext context, Operation operation)
private static void AddOperation(StructuredProgramContext context, Operation operation, TargetLanguage targetLanguage, IReadOnlyList<Function> functions)
{
Instruction inst = operation.Inst;
StorageKind storageKind = operation.StorageKind;
@ -114,9 +119,43 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
IAstNode[] sources = new IAstNode[sourcesCount + outDestsCount];
for (int index = 0; index < operation.SourcesCount; index++)
if (inst == Instruction.Call && targetLanguage == TargetLanguage.Spirv)
{
sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index));
// SPIR-V requires that all function parameters are copied to a local variable before the call
// (or at least that's what the Khronos compiler does).
// First one is the function index.
Operand funcIndexOperand = operation.GetSource(0);
Debug.Assert(funcIndexOperand.Type == OperandType.Constant);
int funcIndex = funcIndexOperand.Value;
sources[0] = new AstOperand(OperandType.Constant, funcIndex);
int inArgsCount = functions[funcIndex].InArgumentsCount;
// Remaining ones are parameters, copy them to a temp local variable.
for (int index = 1; index < operation.SourcesCount; index++)
{
IAstNode source = context.GetOperandOrCbLoad(operation.GetSource(index));
if (index - 1 < inArgsCount)
{
AstOperand argTemp = context.NewTemp(FuncParameterType);
context.AddNode(new AstAssignment(argTemp, source));
sources[index] = argTemp;
}
else
{
sources[index] = source;
}
}
}
else
{
for (int index = 0; index < operation.SourcesCount; index++)
{
sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index));
}
}
for (int index = 0; index < outDestsCount; index++)

View File

@ -897,7 +897,21 @@ namespace Ryujinx.Graphics.Shader.Translation
context.Add(new TextureOperation(Instruction.TextureSample, type, TextureFormat.Unknown, flags, binding, compMask, dests, sources));
}
public static Operand TextureSize(
public static Operand TextureQuerySamples(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
int binding,
Operand[] sources)
{
Operand dest = Local();
context.Add(new TextureOperation(Instruction.TextureQuerySamples, type, TextureFormat.Unknown, flags, binding, 0, new[] { dest }, sources));
return dest;
}
public static Operand TextureQuerySize(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
@ -907,7 +921,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
Operand dest = Local();
context.Add(new TextureOperation(Instruction.TextureSize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
context.Add(new TextureOperation(Instruction.TextureQuerySize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
return dest;
}

View File

@ -27,9 +27,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
if (texOp.Inst == Instruction.Lod ||
texOp.Inst == Instruction.TextureSample ||
texOp.Inst == Instruction.TextureSize)
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())
{
Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
@ -40,7 +38,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// as long bindless elimination is successful and we know where the texture descriptor is located.
bool rewriteSamplerType =
texOp.Type == SamplerType.TextureBuffer ||
texOp.Inst == Instruction.TextureSize;
texOp.Inst == Instruction.TextureQuerySamples ||
texOp.Inst == Instruction.TextureQuerySize;
if (bindlessHandle.Type == OperandType.ConstantBuffer)
{

View File

@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
@ -785,30 +786,31 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static string GetFunctionName(Operation baseOp, bool isMultiTarget, IReadOnlyList<uint> targetCbs)
{
string name = baseOp.Inst.ToString();
StringBuilder nameBuilder = new();
nameBuilder.Append(baseOp.Inst.ToString());
name += baseOp.StorageKind switch
nameBuilder.Append(baseOp.StorageKind switch
{
StorageKind.GlobalMemoryS8 => "S8",
StorageKind.GlobalMemoryS16 => "S16",
StorageKind.GlobalMemoryU8 => "U8",
StorageKind.GlobalMemoryU16 => "U16",
_ => string.Empty,
};
});
if (isMultiTarget)
{
name += "Multi";
nameBuilder.Append("Multi");
}
foreach (uint targetCb in targetCbs)
{
(int sbCbSlot, int sbCbOffset) = UnpackCbSlotAndOffset(targetCb);
name += $"_c{sbCbSlot}o{sbCbOffset}";
nameBuilder.Append($"_c{sbCbSlot}o{sbCbOffset}");
}
return name;
return nameBuilder.ToString();
}
private static bool TryGenerateStorageOp(

View File

@ -232,8 +232,8 @@ namespace Ryujinx.Graphics.Shader.Translation
inst &= Instruction.Mask;
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureSize;
bool accurateType = !inst.IsTextureQuery();
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize;
bool coherent = flags.HasFlag(TextureFlags.Coherent);
if (!isImage)

View File

@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.GpuAccessor, context.Stage);
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
{
@ -99,7 +99,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
if (texOp.Inst == Instruction.TextureSize &&
if (texOp.Inst == Instruction.TextureQuerySize &&
texOp.Index < 2 &&
!isBindless &&
!isIndexed &&
@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize,
Instruction.TextureQuerySize,
texOp.Type,
texOp.Format,
texOp.Flags,
@ -259,7 +259,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize,
Instruction.TextureQuerySize,
texOp.Type,
texOp.Format,
texOp.Flags,
@ -287,7 +287,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node;
}
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, IGpuAccessor gpuAccessor, ShaderStage stage)
{
// Non-constant texture offsets are not allowed (according to the spec),
// however some GPUs does support that.
@ -440,7 +440,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
sources.CopyTo(newSources, 0);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
int destIndex = 0;
@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
else
{
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
for (int index = 0; index < coordsCount; index++)
{
@ -554,21 +554,31 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
TextureOperation texOp,
Operand[] lodSources,
Operand bindlessHandle,
int coordsCount)
int coordsCount,
ShaderStage stage)
{
Operand[] texSizes = new Operand[coordsCount];
Operand lod = Local();
Operand lod;
node.List.AddBefore(node, new TextureOperation(
Instruction.Lod,
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.Binding,
0,
new[] { lod },
lodSources));
if (stage == ShaderStage.Fragment)
{
lod = Local();
node.List.AddBefore(node, new TextureOperation(
Instruction.Lod,
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.Binding,
0,
new[] { lod },
lodSources));
}
else
{
lod = Const(0);
}
for (int index = 0; index < coordsCount; index++)
{
@ -586,7 +596,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize,
Instruction.TextureQuerySize,
texOp.Type,
texOp.Format,
texOp.Flags,

View File

@ -329,6 +329,7 @@ namespace Ryujinx.Graphics.Shader.Translation
attributeUsage,
definitions,
resourceManager,
Options.TargetLanguage,
Options.Flags.HasFlag(TranslationFlags.DebugMode));
int geometryVerticesPerPrimitive = Definitions.OutputTopology switch

View File

@ -19,6 +19,7 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using Path = System.IO.Path;
namespace Ryujinx.HLE.FileSystem
@ -817,13 +818,13 @@ namespace Ryujinx.HLE.FileSystem
if (updateNcas.Count > 0)
{
string extraNcas = string.Empty;
StringBuilder extraNcas = new();
foreach (var entry in updateNcas)
{
foreach (var (type, path) in entry.Value)
{
extraNcas += path + Environment.NewLine;
extraNcas.AppendLine(path);
}
}
@ -954,13 +955,13 @@ namespace Ryujinx.HLE.FileSystem
if (updateNcas.Count > 0)
{
string extraNcas = string.Empty;
StringBuilder extraNcas = new();
foreach (var entry in updateNcas)
{
foreach (var (type, path) in entry.Value)
{
extraNcas += path + Environment.NewLine;
extraNcas.AppendLine(path);
}
}

View File

@ -436,14 +436,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
uint nameIndex = sym.NameOffset;
string name = string.Empty;
StringBuilder nameBuilder = new();
for (int chr; (chr = memory.Read<byte>(strTblAddr + nameIndex++)) != 0;)
{
name += (char)chr;
nameBuilder.Append((char)chr);
}
return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
return new ElfSymbol(nameBuilder.ToString(), sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
}
private static ElfSymbol GetSymbol32(IVirtualMemoryManager memory, ulong address, ulong strTblAddr)
@ -452,14 +452,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
uint nameIndex = sym.NameOffset;
string name = string.Empty;
StringBuilder nameBuilder = new();
for (int chr; (chr = memory.Read<byte>(strTblAddr + nameIndex++)) != 0;)
{
name += (char)chr;
nameBuilder.Append((char)chr);
}
return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
return new ElfSymbol(nameBuilder.ToString(), sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
}
}
}

View File

@ -36,6 +36,8 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
throw new InvalidOperationException("Out of handles!");
}
_completionEvent.WritableEvent.Signal();
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(completionEventHandle);
return ResultCode.Success;
@ -187,6 +189,20 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success;
}
[CommandCmif(10420)]
// nn::friends::CheckBlockedUserListAvailability(nn::account::Uid userId) -> bool
public ResultCode CheckBlockedUserListAvailability(ServiceCtx context)
{
UserId userId = context.RequestData.ReadStruct<UserId>();
// Yes, it is available.
context.ResponseData.Write(true);
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
return ResultCode.Success;
}
[CommandCmif(10600)]
// nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid userId)
public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context)

View File

@ -290,7 +290,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
{
coreData = new CoreData();
if (charInfo.IsValid())
if (!charInfo.IsValid())
{
return ResultCode.InvalidCharInfo;
}

View File

@ -1,4 +1,5 @@
using System;
using Ryujinx.Common.Memory;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Ryujinx.HLE.HOS.Services.Mii.Types.RandomMiiConstants;
@ -10,9 +11,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
public const int Size = 0x30;
private byte _storage;
private Array48<byte> _storage;
public Span<byte> Storage => MemoryMarshal.CreateSpan(ref _storage, Size);
public Span<byte> Storage => _storage.AsSpan();
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x18)]
public struct ElementInfo

View File

@ -1,4 +1,5 @@
using System;
using Ryujinx.Common.Memory;
using System;
using System.Runtime.InteropServices;
using System.Text;
@ -10,12 +11,12 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
public const int CharCount = 10;
private const int SizeConst = (CharCount + 1) * 2;
private byte _storage;
private Array22<byte> _storage;
public static Nickname Default => FromString("no name");
public static Nickname Question => FromString("???");
public Span<byte> Raw => MemoryMarshal.CreateSpan(ref _storage, SizeConst);
public Span<byte> Raw => _storage.AsSpan();
private ReadOnlySpan<ushort> Characters => MemoryMarshal.Cast<byte, ushort>(Raw);

View File

@ -62,7 +62,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
private ushort CalculateDataCrc()
{
return Helper.CalculateCrc16(AsSpanWithoutDeviceCrc(), 0, true);
return Helper.CalculateCrc16(AsSpanWithoutCrcs(), 0, true);
}
private ushort CalculateDeviceCrc()
@ -71,7 +71,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
ushort deviceIdCrc16 = Helper.CalculateCrc16(SpanHelpers.AsByteSpan(ref deviceId), 0, false);
return Helper.CalculateCrc16(AsSpan(), deviceIdCrc16, true);
return Helper.CalculateCrc16(AsSpanWithoutDeviceCrc(), deviceIdCrc16, true);
}
private ReadOnlySpan<byte> AsSpan()
@ -84,6 +84,11 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
return AsSpan()[..(Size - 2)];
}
private ReadOnlySpan<byte> AsSpanWithoutCrcs()
{
return AsSpan()[..(Size - 4)];
}
public static StoreData BuildDefault(UtilityImpl utilImpl, uint index)
{
StoreData result = new()

View File

@ -1,4 +1,6 @@
using System;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
@ -8,9 +10,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
public const int Size = 0x60;
private byte _storage;
private Array96<byte> _storage;
public Span<byte> Storage => MemoryMarshal.CreateSpan(ref _storage, Size);
public Span<byte> Storage => _storage.AsSpan();
// TODO: define all getters/setters
}

View File

@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Sm
{
@ -235,7 +236,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
private static string ReadName(ServiceCtx context)
{
string name = string.Empty;
StringBuilder nameBuilder = new();
for (int index = 0; index < 8 &&
context.RequestData.BaseStream.Position <
@ -245,11 +246,11 @@ namespace Ryujinx.HLE.HOS.Services.Sm
if (chr >= 0x20 && chr < 0x7f)
{
name += (char)chr;
nameBuilder.Append((char)chr);
}
}
return name;
return nameBuilder.ToString();
}
public override void DestroyAtExit()

View File

@ -669,6 +669,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
lock (Core.Lock)
{
// If we are replacing a buffer that has already been queued, make sure we release the references.
if (Core.Slots[slot].BufferState == BufferState.Queued)
{
Core.Slots[slot].GraphicBuffer.Object.DecrementNvMapHandleRefCount(Core.Owner);
}
Core.Slots[slot].BufferState = BufferState.Free;
Core.Slots[slot].Fence = AndroidFence.NoFence;
Core.Slots[slot].RequestBufferCalled = false;

View File

@ -142,7 +142,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
// OpenDisplay(nn::vi::DisplayName) -> u64 display_id
public ResultCode OpenDisplay(ServiceCtx context)
{
string name = "";
StringBuilder nameBuilder = new();
for (int index = 0; index < 8 && context.RequestData.BaseStream.Position < context.RequestData.BaseStream.Length; index++)
{
@ -150,11 +150,11 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
if (chr >= 0x20 && chr < 0x7f)
{
name += (char)chr;
nameBuilder.Append((char)chr);
}
}
return OpenDisplayImpl(context, name);
return OpenDisplayImpl(context, nameBuilder.ToString());
}
[CommandCmif(1011)]

View File

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.Horizon.Sdk.Sm
{
@ -78,7 +79,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
public override string ToString()
{
string name = string.Empty;
StringBuilder nameBuilder = new();
for (int index = 0; index < sizeof(ulong); index++)
{
@ -89,10 +90,10 @@ namespace Ryujinx.Horizon.Sdk.Sm
break;
}
name += (char)character;
nameBuilder.Append((char)character);
}
return name;
return nameBuilder.ToString();
}
}
}

View File

@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using System.Linq;
using System.Text;
namespace Ryujinx.Ui.LocaleGenerator
{
@ -15,15 +16,17 @@ namespace Ryujinx.Ui.LocaleGenerator
context.RegisterSourceOutput(contents, (spc, content) =>
{
var lines = content.Split('\n').Where(x => x.Trim().StartsWith("\"")).Select(x => x.Split(':')[0].Trim().Replace("\"", ""));
string enumSource = "namespace Ryujinx.Ava.Common.Locale;\n";
enumSource += "internal enum LocaleKeys\n{\n";
StringBuilder enumSourceBuilder = new();
enumSourceBuilder.AppendLine("namespace Ryujinx.Ava.Common.Locale;");
enumSourceBuilder.AppendLine("internal enum LocaleKeys");
enumSourceBuilder.AppendLine("{");
foreach (var line in lines)
{
enumSource += $" {line},\n";
enumSourceBuilder.AppendLine($" {line},");
}
enumSource += "}\n";
enumSourceBuilder.AppendLine("}");
spc.AddSource("LocaleKeys", enumSource);
spc.AddSource("LocaleKeys", enumSourceBuilder.ToString());
});
}
}