Compare commits

...

8 Commits

Author SHA1 Message Date
jhorv
5131b71437 Reducing memory allocations (#4537)
* add RecyclableMemoryStream dependency and MemoryStreamManager

* organize BinaryReader/BinaryWriter extensions

* add StreamExtensions to reduce need for BinaryWriter

* simple replacments of MemoryStream with RecyclableMemoryStream

* add write ReadOnlySequence<byte> support to IVirtualMemoryManager

* avoid 0-length array creation

* rework IpcMessage and related types to greatly reduce memory allocation by using RecylableMemoryStream, keeping streams around longer, avoiding their creation when possible, and avoiding creation of BinaryReader and BinaryWriter when possible

* reduce LINQ-induced memory allocations with custom methods to query KPriorityQueue

* use RecyclableMemoryStream in StreamUtils, and use StreamUtils in EmbeddedResources

* add constants for nanosecond/millisecond conversions

* code formatting

* XML doc adjustments

* fix: StreamExtension.WriteByte not writing non-zero values for lengths <= 16

* XML Doc improvements. Implement StreamExtensions.WriteByte() block writes for large-enough count values.

* add copyless path for StreamExtension.Write(ReadOnlySpan<int>)

* add default implementation of IVirtualMemoryManager.Write(ulong, ReadOnlySequence<byte>); remove previous explicit implementations

* code style fixes

* remove LINQ completely from KScheduler/KPriorityQueue by implementing a custom struct-based enumerator
2023-03-17 13:14:50 +01:00
TSRBerry
7870423671 Update syscall capabilites to include SVCs from FW 15.0.0 (#4530)
* Add CapabilityType enum

* Add SupervisorCallCount

* kernel: Add CapabilityExtensions & Change type of capabilities to uint

* Remove private setter from Mask arrays

* Pass ReadOnlySpan directly & Remove redundant type casts
2023-03-17 12:55:19 +01:00
dependabot[bot]
b72916fbc1 nuget: bump UnicornEngine.Unicorn (#4543)
Bumps [UnicornEngine.Unicorn](https://github.com/unicorn-engine/unicorn) from 2.0.2-rc1-f7c841d to 2.0.2-rc1-fb78016.
- [Release notes](https://github.com/unicorn-engine/unicorn/releases)
- [Changelog](https://github.com/unicorn-engine/unicorn/blob/master/ChangeLog)
- [Commits](https://github.com/unicorn-engine/unicorn/commits)

---
updated-dependencies:
- dependency-name: UnicornEngine.Unicorn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-17 12:50:52 +01:00
riperiperi
da073fce61 GPU: Fast path for adding one texture view to a group (#4528)
* GPU: Fast path for adding one texture view to a group

Texture group handles must store a list of their overlapping views, so they can be properly notified when a write is detected, and a few other things relating to texture readback. This is generally created when the group is established, with each handle looping over all views to find its overlaps. This whole process was also done when only a single view was added (and no handles were changed), however...

Sonic Frontiers had a huge cubemap array with 7350 faces (175 cubemaps * 6 faces * 7 levels), so iterating over both handles and existing views added up very fast. Since we are only adding a single view, we only need to _add_ that view to the existing overlaps, rather than recalculate them all.

This greatly improves performance during loading screens and a few seconds into gameplay on the "open zone" sections of Sonic Frontiers. May improve loading times or stutters on some other games.

Note that the current texture cache rules will cause these views to fall out of the cache, as there are more than the hard cap, so the cost will be repaid when reloading the open zone.

I also added some code to properly remove overlaps when texture views are removed, since it seems that was missing.

This can be improved further by only iterating handles that overlap the view (filter by range), but so can a few places in TextureGroup, so better to do all at once. The full generation of overlaps could probably be improved in a similar way.

I recommend testing a few games to make sure nothing breaks.

* Address feedback
2023-03-14 17:33:44 -03:00
riperiperi
1fc90e57d2 Update range for remapped sparse textures instead of recreating them (#4442)
* Update sparsely mapped texture ranges without recreating

Important TODO in TexturePool. Smaller TODO: should I look into making textures with views also do this? It needs to be able to detect if the views can be instantly deleted without issue if they're now remapped.

* Actually do partial updates

* Signal group dirty after mappings changed

* Fix various issues (should work now)

* Further optimisation

Should load a lot less data (16x) when partial updating 3d textures.

* Improve stability

* Allow granular uploads on large textures, improve rules

* Actually avoid updating slices that aren't modified.

* Address some feedback, minor optimisation

* Small tweak

* Refactor DereferenceRequest

More specific initialization methods.

* Improve code for resetting handles

* Explain data loading a bit more

* Add some safety for setting null from different threads.

All texture sets come from the one thread, but null sets can come from multiple. Only decrement ref count if we succeeded the null set first.

* Address feedback 1

* Make a bit safer
2023-03-14 17:08:44 -03:00
Isaac Marovitz
eafcc314a9 Ava UI: DownloadableContentManager Refactor (#4300)
* Start refactor

* Move around functions

* It builds

* Menu opens

* Buttons

* Fix overlapping text

* SaveAndClose and Close buttons

* Remove button

* Layout

* It’s a little funky but it works

* Enable all/disable all buttons

* Fix UpdateCount desyncs

* Search bar

* Search by title id

* Fix fuck ups

* Fix selection mode

* Update Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Fix search bar

* Log corrupted DLC json

* Fix LibHac changes

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-03-14 17:04:38 +01:00
riperiperi
6e9bd4de13 GPU: Scale counter results before addition (#4471)
* GPU: Scale counter results before addition

Counter results were being scaled on ReportCounter, which meant that the _total_ value of the counter was being scaled. Not only could this result in very large numbers and weird overflows if the game doesn't clear the counter, but it also caused the result to change drastically.

This PR changes scaling to be done when the value is added to the counter on the backend. This should evaluate the scale at the same time as before, on report counter, but avoiding the issue with scaling the total.

Fixes scaling in Warioware, at least in the demo, where it seems to compare old/new counters and broke down when scaling was enabled.

* Fix issues when result is partially uploaded.

Drivers tend to write the low half first, then the high half. Retry if the high half is FFFFFFFF.
2023-03-12 18:01:15 +01:00
TimeZlicer
05a41b31bc Misc: Support space in path on macOS distribution (#4462)
* .

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* wildcard(*) needs to be outside of quotes(") for cp to work

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-03-12 17:21:21 +01:00
75 changed files with 2131 additions and 1031 deletions

View File

@@ -1,6 +1,7 @@
using ARMeilleure.CodeGen.Linking; using ARMeilleure.CodeGen.Linking;
using ARMeilleure.CodeGen.RegisterAllocators; using ARMeilleure.CodeGen.RegisterAllocators;
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using Ryujinx.Common.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -59,7 +60,7 @@ namespace ARMeilleure.CodeGen.Arm64
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable) public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
{ {
_stream = new MemoryStream(); _stream = MemoryStreamManager.Shared.GetStream();
AllocResult = allocResult; AllocResult = allocResult;

View File

@@ -1,5 +1,6 @@
using ARMeilleure.CodeGen.Linking; using ARMeilleure.CodeGen.Linking;
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using Ryujinx.Common.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -1285,7 +1286,7 @@ namespace ARMeilleure.CodeGen.X86
// Write the code, ignoring the dummy bytes after jumps, into a new stream. // Write the code, ignoring the dummy bytes after jumps, into a new stream.
_stream.Seek(0, SeekOrigin.Begin); _stream.Seek(0, SeekOrigin.Begin);
using var codeStream = new MemoryStream(); using var codeStream = MemoryStreamManager.Shared.GetStream();
var assembler = new Assembler(codeStream, HasRelocs); var assembler = new Assembler(codeStream, HasRelocs);
bool hasRelocs = HasRelocs; bool hasRelocs = HasRelocs;

View File

@@ -1,5 +1,6 @@
using ARMeilleure.CodeGen.RegisterAllocators; using ARMeilleure.CodeGen.RegisterAllocators;
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using Ryujinx.Common.Memory;
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
@@ -22,7 +23,7 @@ namespace ARMeilleure.CodeGen.X86
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable) public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
{ {
_stream = new MemoryStream(); _stream = MemoryStreamManager.Shared.GetStream();
_blockLabels = new Operand[blocksCount]; _blockLabels = new Operand[blocksCount];
AllocResult = allocResult; AllocResult = allocResult;

View File

@@ -6,6 +6,7 @@ using ARMeilleure.Memory;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
@@ -150,10 +151,10 @@ namespace ARMeilleure.Translation.PTC
private void InitializeCarriers() private void InitializeCarriers()
{ {
_infosStream = new MemoryStream(); _infosStream = MemoryStreamManager.Shared.GetStream();
_codesList = new List<byte[]>(); _codesList = new List<byte[]>();
_relocsStream = new MemoryStream(); _relocsStream = MemoryStreamManager.Shared.GetStream();
_unwindInfosStream = new MemoryStream(); _unwindInfosStream = MemoryStreamManager.Shared.GetStream();
} }
private void DisposeCarriers() private void DisposeCarriers()

View File

@@ -1,6 +1,7 @@
using ARMeilleure.State; using ARMeilleure.State;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -182,7 +183,7 @@ namespace ARMeilleure.Translation.PTC
return false; return false;
} }
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
{ {
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
@@ -274,7 +275,7 @@ namespace ARMeilleure.Translation.PTC
outerHeader.SetHeaderHash(); outerHeader.SetHeaderHash();
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
{ {
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);

View File

@@ -22,6 +22,7 @@
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" /> <PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
@@ -46,7 +47,7 @@
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" /> <PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.0" /> <PackageVersion Include="System.Management" Version="7.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-f7c841d" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" /> <PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -583,10 +583,10 @@
"SelectUpdateDialogTitle": "Select update files", "SelectUpdateDialogTitle": "Select update files",
"UserProfileWindowTitle": "User Profiles Manager", "UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Cheats Manager", "CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Downloadable Content Manager", "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
"UpdateWindowTitle": "Title Update Manager", "UpdateWindowTitle": "Title Update Manager",
"CheatWindowHeading": "Cheats Available for {0} [{1}]", "CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", "DlcWindowHeading": "{0} Downloadable Content(s)",
"UserProfilesEditProfile": "Edit Selected", "UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel", "Cancel": "Cancel",
"Save": "Save", "Save": "Save",

View File

@@ -13,7 +13,7 @@
</PropertyGroup> </PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))"> <Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<Exec Command="codesign --entitlements $(ProjectDir)..\distribution\macos\entitlements.xml -f --deep -s $(SigningCertificate) $(TargetDir)$(TargetName)" /> <Exec Command="codesign --entitlements '$(ProjectDir)..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
</Target> </Target>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''"> <PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">

View File

@@ -1,4 +1,5 @@
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using System.IO;
namespace Ryujinx.Ava.UI.Models namespace Ryujinx.Ava.UI.Models
{ {
@@ -21,6 +22,8 @@ namespace Ryujinx.Ava.UI.Models
public string ContainerPath { get; } public string ContainerPath { get; }
public string FullPath { get; } public string FullPath { get; }
public string FileName => Path.GetFileName(ContainerPath);
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled) public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
{ {
TitleId = titleId; TitleId = titleId;

View File

@@ -0,0 +1,340 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using DynamicData;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Path = System.IO.Path;
namespace Ryujinx.Ava.UI.ViewModels
{
public class DownloadableContentManagerViewModel : BaseModel
{
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
private readonly string _downloadableContentJsonPath;
private VirtualFileSystem _virtualFileSystem;
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
private AvaloniaList<DownloadableContentModel> _views = new();
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
private string _search;
private ulong _titleId;
private string _titleName;
public AvaloniaList<DownloadableContentModel> DownloadableContents
{
get => _downloadableContents;
set
{
_downloadableContents = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
}
public AvaloniaList<DownloadableContentModel> Views
{
get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public AvaloniaList<DownloadableContentModel> SelectedDownloadableContents
{
get => _selectedDownloadableContents;
set
{
_selectedDownloadableContents = value;
OnPropertyChanged();
}
}
public string Search
{
get => _search;
set
{
_search = value;
OnPropertyChanged();
Sort();
}
}
public string UpdateCount
{
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
}
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
_virtualFileSystem = virtualFileSystem;
_titleId = titleId;
_titleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
try
{
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
}
catch
{
Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize.");
_downloadableContentContainerList = new List<DownloadableContentContainer>();
}
LoadDownloadableContents();
}
private void LoadDownloadableContents()
{
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
{
if (File.Exists(downloadableContentContainer.ContainerPath))
{
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{
using UniqueRef<IFile> ncaFile = new();
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null)
{
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled);
DownloadableContents.Add(content);
if (content.Enabled)
{
SelectedDownloadableContents.Add(content);
}
OnPropertyChanged(nameof(UpdateCount));
}
}
}
}
// NOTE: Save the list again to remove leftovers.
Save();
Sort();
}
public void Sort()
{
DownloadableContents.AsObservableChangeSet()
.Filter(Filter)
.Bind(out var view).AsObservableList();
_views.Clear();
_views.AddRange(view);
OnPropertyChanged(nameof(Views));
}
private bool Filter(object arg)
{
if (arg is DownloadableContentModel content)
{
return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleId.ToLower().Contains(_search.ToLower());
}
return false;
}
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{
try
{
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex)
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadNcaErrorMessage], ex.Message, containerPath));
});
}
return null;
}
public async void Add()
{
OpenFileDialog dialog = new OpenFileDialog()
{
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
AllowMultiple = true
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" }
});
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null)
{
foreach (string file in files)
{
await AddDownloadableContent(file);
}
}
}
}
private async Task AddDownloadableContent(string path)
{
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{
return;
}
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{
break;
}
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true);
DownloadableContents.Add(content);
SelectedDownloadableContents.Add(content);
OnPropertyChanged(nameof(UpdateCount));
Sort();
containsDownloadableContent = true;
}
}
if (!containsDownloadableContent)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
}
}
public void Remove(DownloadableContentModel model)
{
DownloadableContents.Remove(model);
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
public void RemoveAll()
{
DownloadableContents.Clear();
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
public void EnableAll()
{
SelectedDownloadableContents = new(DownloadableContents);
}
public void DisableAll()
{
SelectedDownloadableContents.Clear();
}
public void Save()
{
_downloadableContentContainerList.Clear();
DownloadableContentContainer container = default;
foreach (DownloadableContentModel downloadableContent in DownloadableContents)
{
if (container.ContainerPath != downloadableContent.ContainerPath)
{
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{
_downloadableContentContainerList.Add(container);
}
container = new DownloadableContentContainer
{
ContainerPath = downloadableContent.ContainerPath,
DownloadableContentNcaList = new List<DownloadableContentNca>()
};
}
container.DownloadableContentNcaList.Add(new DownloadableContentNca
{
Enabled = downloadableContent.Enabled,
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
FullPath = downloadableContent.FullPath
});
}
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{
_downloadableContentContainerList.Add(container);
}
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
{
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
}
}
}
}

View File

@@ -1564,7 +1564,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
if (SelectedApplication != null) if (SelectedApplication != null)
{ {
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName).ShowDialog(TopLevel as Window); await DownloadableContentManagerWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
} }
} }

View File

@@ -1,172 +1,194 @@
<window:StyleableWindow <UserControl
x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow" x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
Width="800" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
Height="500" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
MinWidth="800" Width="500"
MinHeight="500" Height="380"
MaxWidth="800"
MaxHeight="500"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d" mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:DownloadableContentManagerViewModel"
Focusable="True"> Focusable="True">
<Grid Name="DownloadableContentGrid" Margin="15"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <Panel
Name="Heading" Margin="0 0 0 10"
Grid.Row="1" Grid.Row="0">
MaxWidth="500" <Grid>
Margin="20,15,20,20" <Grid.ColumnDefinitions>
HorizontalAlignment="Center" <ColumnDefinition Width="Auto" />
VerticalAlignment="Center" <ColumnDefinition Width="Auto" />
LineHeight="18" <ColumnDefinition Width="*" />
TextAlignment="Center" </Grid.ColumnDefinitions>
TextWrapping="Wrap" /> <TextBlock
<DockPanel Grid.Column="0"
Grid.Row="2" Text="{Binding UpdateCount}" />
Margin="0" <StackPanel
HorizontalAlignment="Left"> Margin="10 0"
<Button Grid.Column="1"
Name="EnableAllButton" Orientation="Horizontal">
MinWidth="90" <Button
Margin="5" Name="EnableAllButton"
Command="{Binding EnableAll}"> MinWidth="90"
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" /> Margin="5"
</Button> Command="{ReflectionBinding EnableAll}">
<Button <TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
Name="DisableAllButton" </Button>
MinWidth="90" <Button
Margin="5" Name="DisableAllButton"
Command="{Binding DisableAll}"> MinWidth="90"
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" /> Margin="5"
</Button> Command="{ReflectionBinding DisableAll}">
</DockPanel> <TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</StackPanel>
<TextBox
Grid.Column="2"
MinHeight="27"
MaxHeight="27"
HorizontalAlignment="Stretch"
Watermark="{locale:Locale Search}"
Text="{Binding Search}" />
</Grid>
</Panel>
<Border <Border
Grid.Row="3" Grid.Row="1"
Margin="5" Margin="0 0 0 24"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="Gray" BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1"> BorderThickness="1"
<ScrollViewer CornerRadius="5"
VerticalAlignment="Stretch" Padding="2.5">
HorizontalScrollBarVisibility="Auto" <ListBox
VerticalScrollBarVisibility="Auto"> AutoScrollToSelectedItem="False"
<DataGrid VirtualizationMode="None"
Name="DlcDataGrid" SelectionMode="Multiple, Toggle"
MinHeight="200" Background="Transparent"
HorizontalAlignment="Stretch" SelectionChanged="OnSelectionChanged"
VerticalAlignment="Stretch" SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}"
CanUserReorderColumns="False" Items="{Binding Views}">
CanUserResizeColumns="True" <ListBox.DataTemplates>
CanUserSortColumns="True" <DataTemplate
HorizontalScrollBarVisibility="Auto" DataType="models:DownloadableContentModel">
Items="{Binding _downloadableContents}" <Panel Margin="10">
SelectionMode="Extended" <Grid>
VerticalScrollBarVisibility="Auto"> <Grid.ColumnDefinitions>
<DataGrid.Styles> <ColumnDefinition Width="*" />
<Styles> <ColumnDefinition Width="Auto" />
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)"> </Grid.ColumnDefinitions>
<Setter Property="HorizontalAlignment" Value="Left" /> <Grid
<Setter Property="HorizontalContentAlignment" Value="Left" /> Grid.Column="0">
</Style> <Grid.ColumnDefinitions>
</Styles> <ColumnDefinition Width="*"></ColumnDefinition>
<Styles> <ColumnDefinition Width="Auto"></ColumnDefinition>
<Style Selector="DataGridCell:nth-child(1)"> </Grid.ColumnDefinitions>
<Setter Property="HorizontalAlignment" Value="Right" /> <TextBlock
<Setter Property="HorizontalContentAlignment" Value="Right" /> Grid.Column="0"
</Style> HorizontalAlignment="Left"
</Styles> VerticalAlignment="Center"
</DataGrid.Styles> MaxLines="2"
<DataGrid.Columns> TextWrapping="Wrap"
<DataGridTemplateColumn Width="90"> TextTrimming="CharacterEllipsis"
<DataGridTemplateColumn.CellTemplate> Text="{Binding FileName}" />
<DataTemplate> <TextBlock
<CheckBox Grid.Column="1"
Width="50" Margin="10 0"
MinWidth="40" HorizontalAlignment="Left"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding Enabled}" /> Text="{Binding TitleId}" />
</DataTemplate> </Grid>
</DataGridTemplateColumn.CellTemplate> <StackPanel
<DataGridTemplateColumn.Header> Grid.Column="1"
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" /> Spacing="10"
</DataGridTemplateColumn.Header> Orientation="Horizontal"
</DataGridTemplateColumn> HorizontalAlignment="Right">
<DataGridTextColumn Width="140" Binding="{Binding TitleId}"> <Button
<DataGridTextColumn.Header> VerticalAlignment="Center"
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" /> HorizontalAlignment="Right"
</DataGridTextColumn.Header> Padding="10"
</DataGridTextColumn> MinWidth="0"
<DataGridTextColumn Width="280" Binding="{Binding FullPath}"> MinHeight="0"
<DataGridTextColumn.Header> Click="OpenLocation">
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" /> <ui:SymbolIcon
</DataGridTextColumn.Header> Symbol="OpenFolder"
</DataGridTextColumn> HorizontalAlignment="Center"
<DataGridTextColumn Binding="{Binding ContainerPath}"> VerticalAlignment="Center" />
<DataGridTextColumn.Header> </Button>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" /> <Button
</DataGridTextColumn.Header> VerticalAlignment="Center"
</DataGridTextColumn> HorizontalAlignment="Right"
</DataGrid.Columns> Padding="10"
</DataGrid> MinWidth="0"
</ScrollViewer> MinHeight="0"
Click="RemoveDLC">
<ui:SymbolIcon
Symbol="Cancel"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
</StackPanel>
</Grid>
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
</Style>
</ListBox.Styles>
</ListBox>
</Border> </Border>
<DockPanel <Panel
Grid.Row="4" Grid.Row="2"
Margin="0"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<DockPanel Margin="0" HorizontalAlignment="Left"> <StackPanel
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Left">
<Button <Button
Name="AddButton" Name="AddButton"
MinWidth="90" MinWidth="90"
Margin="5" Margin="5"
Command="{Binding Add}"> Command="{ReflectionBinding Add}">
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
</Button> </Button>
<Button
Name="RemoveButton"
MinWidth="90"
Margin="5"
Command="{Binding RemoveSelected}">
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
</Button>
<Button <Button
Name="RemoveAllButton" Name="RemoveAllButton"
MinWidth="90" MinWidth="90"
Margin="5" Margin="5"
Command="{Binding RemoveAll}"> Command="{ReflectionBinding RemoveAll}">
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" /> <TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
</Button> </Button>
</DockPanel> </StackPanel>
<DockPanel Margin="0" HorizontalAlignment="Right"> <StackPanel
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Right">
<Button <Button
Name="SaveButton" Name="SaveButton"
MinWidth="90" MinWidth="90"
Margin="5" Margin="5"
Command="{Binding SaveAndClose}"> Click="SaveAndClose">
<TextBlock Text="{locale:Locale SettingsButtonSave}" /> <TextBlock Text="{locale:Locale SettingsButtonSave}" />
</Button> </Button>
<Button <Button
Name="CancelButton" Name="CancelButton"
MinWidth="90" MinWidth="90"
Margin="5" Margin="5"
Command="{Binding Close}"> Click="Close">
<TextBlock Text="{locale:Locale InputDialogCancel}" /> <TextBlock Text="{locale:Locale InputDialogCancel}" />
</Button> </Button>
</DockPanel> </StackPanel>
</DockPanel> </Panel>
</Grid> </Grid>
</window:StyleableWindow> </UserControl>

View File

@@ -1,314 +1,115 @@
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Interactivity;
using LibHac.Common; using Avalonia.Styling;
using LibHac.Fs; using FluentAvalonia.UI.Controls;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Common.Configuration; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using System; using Ryujinx.Ui.Common.Helper;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Path = System.IO.Path; using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
public partial class DownloadableContentManagerWindow : StyleableWindow public partial class DownloadableContentManagerWindow : UserControl
{ {
private readonly List<DownloadableContentContainer> _downloadableContentContainerList; public DownloadableContentManagerViewModel ViewModel;
private readonly string _downloadableContentJsonPath;
private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
private ulong _titleId { get; }
private string _titleName { get; }
public DownloadableContentManagerWindow() public DownloadableContentManagerWindow()
{ {
DataContext = this; DataContext = this;
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.DlcWindowTitle]} - {_titleName} ({_titleId:X16})";
} }
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
_virtualFileSystem = virtualFileSystem; DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId, titleName);
_downloadableContents = new AvaloniaList<DownloadableContentModel>();
_titleId = titleId;
_titleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
try
{
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
}
catch
{
_downloadableContentContainerList = new List<DownloadableContentContainer>();
}
DataContext = this;
InitializeComponent(); InitializeComponent();
RemoveButton.IsEnabled = false;
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.DlcWindowTitle]} - {_titleName} ({_titleId:X16})";
LoadDownloadableContents();
PrintHeading();
} }
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0); ContentDialog contentDialog = new()
}
private void PrintHeading()
{
Heading.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DlcWindowHeading, _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
}
private void LoadDownloadableContents()
{
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
{ {
if (File.Exists(downloadableContentContainer.ContainerPath)) PrimaryButtonText = "",
{ SecondaryButtonText = "",
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); CloseButtonText = "",
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId, titleName),
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage()); Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16"))
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{
using UniqueRef<IFile> ncaFile = new();
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null)
{
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled));
}
}
}
}
// NOTE: Save the list again to remove leftovers.
Save();
}
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{
try
{
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex)
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, containerPath));
});
}
return null;
}
private async Task AddDownloadableContent(string path)
{
if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{
return;
}
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{
break;
}
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true;
}
}
if (!containsDownloadableContent)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
}
}
private void RemoveDownloadableContents(bool removeSelectedOnly = false)
{
if (removeSelectedOnly)
{
AvaloniaList<DownloadableContentModel> removedItems = new();
foreach (var item in DlcDataGrid.SelectedItems)
{
removedItems.Add(item as DownloadableContentModel);
}
DlcDataGrid.SelectedItems.Clear();
foreach (var item in removedItems)
{
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
}
}
else
{
_downloadableContents.Clear();
}
PrintHeading();
}
public void RemoveSelected()
{
RemoveDownloadableContents(true);
}
public void RemoveAll()
{
RemoveDownloadableContents();
}
public void EnableAll()
{
foreach(var item in _downloadableContents)
{
item.Enabled = true;
}
}
public void DisableAll()
{
foreach (var item in _downloadableContents)
{
item.Enabled = false;
}
}
public async void Add()
{
OpenFileDialog dialog = new OpenFileDialog()
{
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
AllowMultiple = true
}; };
dialog.Filters.Add(new FileDialogFilter Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
{ bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false));
Name = "NSP",
Extensions = { "nsp" }
});
string[] files = await dialog.ShowAsync(this); contentDialog.Styles.Add(bottomBorder);
if (files != null) await ContentDialogHelper.ShowAsync(contentDialog);
{
foreach (string file in files)
{
await AddDownloadableContent(file);
}
}
PrintHeading();
} }
public void Save() private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs)
{ {
_downloadableContentContainerList.Clear(); ViewModel.Save();
((ContentDialog)Parent).Hide();
}
DownloadableContentContainer container = default; private void Close(object sender, RoutedEventArgs e)
{
((ContentDialog)Parent).Hide();
}
foreach (DownloadableContentModel downloadableContent in _downloadableContents) private void RemoveDLC(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{ {
if (container.ContainerPath != downloadableContent.ContainerPath) if (button.DataContext is DownloadableContentModel model)
{ {
if (!string.IsNullOrWhiteSpace(container.ContainerPath)) ViewModel.Remove(model);
}
}
}
private void OpenLocation(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
if (button.DataContext is DownloadableContentModel model)
{
OpenHelper.LocateFile(model.ContainerPath);
}
}
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var content in e.AddedItems)
{
if (content is DownloadableContentModel model)
{
var index = ViewModel.DownloadableContents.IndexOf(model);
if (index != -1)
{ {
_downloadableContentContainerList.Add(container); ViewModel.DownloadableContents[index].Enabled = true;
} }
container = new DownloadableContentContainer
{
ContainerPath = downloadableContent.ContainerPath,
DownloadableContentNcaList = new List<DownloadableContentNca>()
};
} }
}
container.DownloadableContentNcaList.Add(new DownloadableContentNca foreach (var content in e.RemovedItems)
{
if (content is DownloadableContentModel model)
{ {
Enabled = downloadableContent.Enabled, var index = ViewModel.DownloadableContents.IndexOf(model);
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
FullPath = downloadableContent.FullPath
});
}
if (!string.IsNullOrWhiteSpace(container.ContainerPath)) if (index != -1)
{ {
_downloadableContentContainerList.Add(container); ViewModel.DownloadableContents[index].Enabled = false;
}
}
} }
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
{
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
}
}
public void SaveAndClose()
{
Save();
Close();
} }
} }
} }

View File

@@ -12,19 +12,5 @@ namespace Ryujinx.Common
{ {
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0]; return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
} }
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
where T : unmanaged
{
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
writer.Write(data);
}
public static void Write(this BinaryWriter writer, UInt128 value)
{
writer.Write((ulong)value);
writer.Write((ulong)(value >> 64));
}
} }
} }

View File

@@ -0,0 +1,28 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
public static class BinaryWriterExtensions
{
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
where T : unmanaged
{
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
writer.Write(data);
}
public static void Write(this BinaryWriter writer, UInt128 value)
{
writer.Write((ulong)value);
writer.Write((ulong)(value >> 64));
}
public static void Write(this BinaryWriter writer, MemoryStream stream)
{
stream.CopyTo(writer.BaseStream);
}
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
public static class StreamExtensions
{
/// <summary>
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
///
/// This default implementation converts each buffer value to a stack-allocated
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="buffer">The buffer of values to be written</param>
public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
{
if (buffer.Length == 0)
{
return;
}
if (BitConverter.IsLittleEndian)
{
ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
stream.Write(byteBuffer);
}
else
{
Span<byte> byteBuffer = stackalloc byte[sizeof(int)];
foreach (int value in buffer)
{
BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
stream.Write(byteBuffer);
}
}
}
/// <summary>
/// Writes a four-byte signed integer to this stream. The current position
/// of the stream is advanced by four.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, int value)
{
Span<byte> buffer = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes an eight-byte signed integer to this stream. The current position
/// of the stream is advanced by eight.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, long value)
{
Span<byte> buffer = stackalloc byte[sizeof(long)];
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
// Writes a four-byte unsigned integer to this stream. The current position
// of the stream is advanced by four.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes an eight-byte unsigned integer to this stream. The current
/// position of the stream is advanced by eight.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes the contents of source to stream by calling source.CopyTo(stream).
/// Provides consistency with other Stream.Write methods.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="source">The stream to be read from</param>
public static void Write(this Stream stream, Stream source)
{
source.CopyTo(stream);
}
/// <summary>
/// Writes a sequence of bytes to the Stream.
/// </summary>
/// <param name="stream">The stream to be written to.</param>
/// <param name="value">The byte to be written</param>
/// <param name="count">The number of times the value should be written</param>
public static void WriteByte(this Stream stream, byte value, int count)
{
if (count <= 0)
{
return;
}
const int BlockSize = 16;
int blockCount = count / BlockSize;
if (blockCount > 0)
{
Span<byte> span = stackalloc byte[BlockSize];
span.Fill(value);
for (int x = 0; x < blockCount; x++)
{
stream.Write(span);
}
}
int nonBlockBytes = count % BlockSize;
for (int x = 0; x < nonBlockBytes; x++)
{
stream.WriteByte(value);
}
}
}
}

View File

@@ -0,0 +1,99 @@
using Microsoft.IO;
using System;
namespace Ryujinx.Common.Memory
{
public static class MemoryStreamManager
{
private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager();
/// <summary>
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
/// and b) return them as <c>RecyclableMemoryStream</c> so we don't have to cast.
/// </summary>
public static class Shared
{
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with no tag and a default initial capacity.
/// </summary>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream()
=> new RecyclableMemoryStream(_shared);
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(byte[] buffer)
=> GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length);
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(ReadOnlySpan<byte> buffer)
=> GetStream(Guid.NewGuid(), null, buffer);
/// <summary>
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
/// <param name="tag">A tag which can be used to track the source of the stream</param>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan<byte> buffer)
{
RecyclableMemoryStream stream = null;
try
{
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
stream.Write(buffer);
stream.Position = 0;
return stream;
}
catch
{
stream?.Dispose();
throw;
}
}
/// <summary>
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned</remarks>
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
/// <param name="tag">A tag which can be used to track the source of the stream</param>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <param name="offset">The offset from the start of the buffer to copy from</param>
/// <param name="count">The number of bytes to copy from the buffer</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count)
{
RecyclableMemoryStream stream = null;
try
{
stream = new RecyclableMemoryStream(_shared, id, tag, count);
stream.Write(buffer, offset, count);
stream.Position = 0;
return stream;
}
catch
{
stream?.Dispose();
throw;
}
}
}
}
}

View File

@@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="MsgPack.Cli" /> <PackageReference Include="MsgPack.Cli" />
<PackageReference Include="System.Management" /> <PackageReference Include="System.Management" />
</ItemGroup> </ItemGroup>

View File

@@ -1,3 +1,5 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -38,12 +40,7 @@ namespace Ryujinx.Common
return null; return null;
} }
using (var mem = new MemoryStream()) return StreamUtils.StreamToBytes(stream);
{
stream.CopyTo(mem);
return mem.ToArray();
}
} }
} }
@@ -56,12 +53,7 @@ namespace Ryujinx.Common
return null; return null;
} }
using (var mem = new MemoryStream()) return await StreamUtils.StreamToBytesAsync(stream);
{
await stream.CopyToAsync(mem);
return mem.ToArray();
}
} }
} }

View File

@@ -1,4 +1,8 @@
using System.IO; using Microsoft.IO;
using Ryujinx.Common.Memory;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Common.Utilities namespace Ryujinx.Common.Utilities
{ {
@@ -6,12 +10,22 @@ namespace Ryujinx.Common.Utilities
{ {
public static byte[] StreamToBytes(Stream input) public static byte[] StreamToBytes(Stream input)
{ {
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
{ {
input.CopyTo(stream); input.CopyTo(stream);
return stream.ToArray(); return stream.ToArray();
} }
} }
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
{
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
{
await input.CopyToAsync(stream, cancellationToken);
return stream.ToArray();
}
}
} }
} }

View File

@@ -1,4 +1,6 @@
using Ryujinx.Memory; using Microsoft.IO;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -40,7 +42,7 @@ namespace Ryujinx.Cpu
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1) public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
{ {
using (MemoryStream ms = new MemoryStream()) using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
{ {
for (long offs = 0; offs < maxSize || maxSize == -1; offs++) for (long offs = 0; offs < maxSize || maxSize == -1; offs++)
{ {
@@ -54,7 +56,7 @@ namespace Ryujinx.Cpu
ms.WriteByte(value); ms.WriteByte(value);
} }
return Encoding.ASCII.GetString(ms.ToArray()); return Encoding.ASCII.GetString(ms.GetReadOnlySequence());
} }
} }
} }

View File

@@ -28,5 +28,10 @@ namespace Ryujinx.Cpu.Tracking
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty); public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size); public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
public bool RangeEquals(CpuRegionHandle other)
{
return _impl.RealAddress == other._impl.RealAddress && _impl.RealSize == other._impl.RealSize;
}
} }
} }

View File

@@ -20,5 +20,15 @@ namespace Ryujinx.Graphics.GAL
{ {
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray; return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
} }
public static bool HasDepthOrLayers(this Target target)
{
return target == Target.Texture3D ||
target == Target.Texture1DArray ||
target == Target.Texture2DArray ||
target == Target.Texture2DMultisampleArray ||
target == Target.Cubemap ||
target == Target.CubemapArray;
}
} }
} }

View File

@@ -152,21 +152,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong ticks = _context.GetTimestamp(); ulong ticks = _context.GetTimestamp();
float divisor = type switch
{
ReportCounterType.SamplesPassed => _channel.TextureManager.RenderTargetScale * _channel.TextureManager.RenderTargetScale,
_ => 1f
};
ICounterEvent counter = null; ICounterEvent counter = null;
void resultHandler(object evt, ulong result) void resultHandler(object evt, ulong result)
{ {
if (divisor != 1f)
{
result = (ulong)MathF.Ceiling(result / divisor);
}
CounterData counterData = new CounterData CounterData counterData = new CounterData
{ {
Counter = result, Counter = result,

View File

@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
public TexturePool Pool; public TexturePool Pool;
public int ID; public int ID;
public ulong GpuAddress;
} }
private GpuContext _context; private GpuContext _context;
@@ -162,6 +163,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public bool IsView => _viewStorage != this; public bool IsView => _viewStorage != this;
/// <summary>
/// Whether or not this texture has views.
/// </summary>
public bool HasViews => _views.Count > 0;
private int _referenceCount; private int _referenceCount;
private List<TexturePoolOwner> _poolOwners; private List<TexturePoolOwner> _poolOwners;
@@ -354,7 +360,7 @@ namespace Ryujinx.Graphics.Gpu.Image
texture._viewStorage = this; texture._viewStorage = this;
Group.UpdateViews(_views); Group.UpdateViews(_views, texture);
if (texture.Group != null && texture.Group != Group) if (texture.Group != null && texture.Group != Group)
{ {
@@ -378,11 +384,24 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
_views.Remove(texture); _views.Remove(texture);
Group.RemoveView(texture);
texture._viewStorage = texture; texture._viewStorage = texture;
DecrementReferenceCount(); DecrementReferenceCount();
} }
/// <summary>
/// Replaces the texture's physical memory range. This forces tracking to regenerate.
/// </summary>
/// <param name="range">New physical memory range backing the texture</param>
public void ReplaceRange(MultiRange range)
{
Range = range;
Group.RangeChanged();
}
/// <summary> /// <summary>
/// Create a copy dependency to a texture that is view compatible with this one. /// Create a copy dependency to a texture that is view compatible with this one.
/// When either texture is modified, the texture data will be copied to the other to keep them in sync. /// When either texture is modified, the texture data will be copied to the other to keep them in sync.
@@ -715,6 +734,8 @@ namespace Ryujinx.Graphics.Gpu.Image
height = Math.Max(height >> level, 1); height = Math.Max(height >> level, 1);
depth = Math.Max(depth >> level, 1); depth = Math.Max(depth >> level, 1);
int sliceDepth = single ? 1 : depth;
SpanOrArray<byte> result; SpanOrArray<byte> result;
if (Info.IsLinear) if (Info.IsLinear)
@@ -735,7 +756,7 @@ namespace Ryujinx.Graphics.Gpu.Image
width, width,
height, height,
depth, depth,
single ? 1 : depth, sliceDepth,
levels, levels,
layers, layers,
Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockWidth,
@@ -759,7 +780,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Info.FormatInfo.BlockHeight, Info.FormatInfo.BlockHeight,
width, width,
height, height,
depth, sliceDepth,
levels, levels,
layers, layers,
out byte[] decoded)) out byte[] decoded))
@@ -771,7 +792,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (GraphicsConfig.EnableTextureRecompression) if (GraphicsConfig.EnableTextureRecompression)
{ {
decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers); decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers);
} }
result = decoded; result = decoded;
@@ -782,15 +803,15 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
case Format.Etc2RgbaSrgb: case Format.Etc2RgbaSrgb:
case Format.Etc2RgbaUnorm: case Format.Etc2RgbaUnorm:
result = ETC2Decoder.DecodeRgba(result, width, height, depth, levels, layers); result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers);
break; break;
case Format.Etc2RgbPtaSrgb: case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbPtaUnorm: case Format.Etc2RgbPtaUnorm:
result = ETC2Decoder.DecodePta(result, width, height, depth, levels, layers); result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers);
break; break;
case Format.Etc2RgbSrgb: case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm: case Format.Etc2RgbUnorm:
result = ETC2Decoder.DecodeRgb(result, width, height, depth, levels, layers); result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers);
break; break;
} }
} }
@@ -800,31 +821,31 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
case Format.Bc1RgbaSrgb: case Format.Bc1RgbaSrgb:
case Format.Bc1RgbaUnorm: case Format.Bc1RgbaUnorm:
result = BCnDecoder.DecodeBC1(result, width, height, depth, levels, layers); result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers);
break; break;
case Format.Bc2Srgb: case Format.Bc2Srgb:
case Format.Bc2Unorm: case Format.Bc2Unorm:
result = BCnDecoder.DecodeBC2(result, width, height, depth, levels, layers); result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers);
break; break;
case Format.Bc3Srgb: case Format.Bc3Srgb:
case Format.Bc3Unorm: case Format.Bc3Unorm:
result = BCnDecoder.DecodeBC3(result, width, height, depth, levels, layers); result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers);
break; break;
case Format.Bc4Snorm: case Format.Bc4Snorm:
case Format.Bc4Unorm: case Format.Bc4Unorm:
result = BCnDecoder.DecodeBC4(result, width, height, depth, levels, layers, Format == Format.Bc4Snorm); result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
break; break;
case Format.Bc5Snorm: case Format.Bc5Snorm:
case Format.Bc5Unorm: case Format.Bc5Unorm:
result = BCnDecoder.DecodeBC5(result, width, height, depth, levels, layers, Format == Format.Bc5Snorm); result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
break; break;
case Format.Bc6HSfloat: case Format.Bc6HSfloat:
case Format.Bc6HUfloat: case Format.Bc6HUfloat:
result = BCnDecoder.DecodeBC6(result, width, height, depth, levels, layers, Format == Format.Bc6HSfloat); result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
break; break;
case Format.Bc7Srgb: case Format.Bc7Srgb:
case Format.Bc7Unorm: case Format.Bc7Unorm:
result = BCnDecoder.DecodeBC7(result, width, height, depth, levels, layers); result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers);
break; break;
} }
} }
@@ -1484,11 +1505,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="pool">The texture pool this texture has been added to</param> /// <param name="pool">The texture pool this texture has been added to</param>
/// <param name="id">The ID of the reference to this texture in the pool</param> /// <param name="id">The ID of the reference to this texture in the pool</param>
public void IncrementReferenceCount(TexturePool pool, int id) /// <param name="gpuVa">GPU VA of the pool reference</param>
public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
{ {
lock (_poolOwners) lock (_poolOwners)
{ {
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id }); _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
} }
_referenceCount++; _referenceCount++;
@@ -1585,6 +1607,36 @@ namespace Ryujinx.Graphics.Gpu.Image
InvalidatedSequence++; InvalidatedSequence++;
} }
/// <summary>
/// Queue updating texture mappings on the pool. Happens from another thread.
/// </summary>
public void UpdatePoolMappings()
{
lock (_poolOwners)
{
ulong address = 0;
foreach (var owner in _poolOwners)
{
if (address == 0 || address == owner.GpuAddress)
{
address = owner.GpuAddress;
owner.Pool.QueueUpdateMapping(this, owner.ID);
}
else
{
// If there is a different GPU VA mapping, prefer the first and delete the others.
owner.Pool.ForceRemove(this, owner.ID, true);
}
}
_poolOwners.Clear();
}
InvalidatedSequence++;
}
/// <summary> /// <summary>
/// Delete the texture if it is not used anymore. /// Delete the texture if it is not used anymore.
/// The texture is considered unused when the reference count is zero, /// The texture is considered unused when the reference count is zero,
@@ -1636,7 +1688,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Group.ClearModified(unmapRange); Group.ClearModified(unmapRange);
} }
RemoveFromPools(true); UpdatePoolMappings();
} }
/// <summary> /// <summary>

View File

@@ -194,6 +194,39 @@ namespace Ryujinx.Graphics.Gpu.Image
_cache.Lift(texture); _cache.Lift(texture);
} }
/// <summary>
/// Attempts to update a texture's physical memory range.
/// Returns false if there is an existing texture that matches with the updated range.
/// </summary>
/// <param name="texture">Texture to update</param>
/// <param name="range">New physical memory range</param>
/// <returns>True if the mapping was updated, false otherwise</returns>
public bool UpdateMapping(Texture texture, MultiRange range)
{
// There cannot be an existing texture compatible with this mapping in the texture cache already.
int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
for (int i = 0; i < overlapCount; i++)
{
var other = _textureOverlaps[i];
if (texture != other &&
(texture.IsViewCompatible(other.Info, other.Range, true, other.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible ||
other.IsViewCompatible(texture.Info, texture.Range, true, texture.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible))
{
return false;
}
}
_textures.Remove(texture);
texture.ReplaceRange(range);
_textures.Add(texture);
return true;
}
/// <summary> /// <summary>
/// Tries to find an existing texture, or create a new one if not found. /// Tries to find an existing texture, or create a new one if not found.
/// </summary> /// </summary>

View File

@@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
class TextureGroup : IDisposable class TextureGroup : IDisposable
{ {
/// <summary>
/// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures.
/// </summary>
private const int GranularLayerThreshold = 8;
private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false); private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
/// <summary> /// <summary>
@@ -116,7 +121,29 @@ namespace Ryujinx.Graphics.Gpu.Image
_allOffsets = size.AllOffsets; _allOffsets = size.AllOffsets;
_sliceSizes = size.SliceSizes; _sliceSizes = size.SliceSizes;
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews); if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
{
_hasLayerViews = true;
_hasMipViews = true;
}
else
{
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
// If the texture is partially mapped, fully subdivide handles immediately.
MultiRange range = Storage.Range;
for (int i = 0; i < range.Count; i++)
{
if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
{
_hasLayerViews = true;
_hasMipViews = true;
break;
}
}
}
RecalculateHandleRegions(); RecalculateHandleRegions();
} }
@@ -249,7 +276,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
bool dirty = false; bool dirty = false;
bool anyModified = false; bool anyModified = false;
bool anyUnmapped = false; bool anyNotDirty = false;
for (int i = 0; i < regionCount; i++) for (int i = 0; i < regionCount; i++)
{ {
@@ -294,20 +321,21 @@ namespace Ryujinx.Graphics.Gpu.Image
dirty |= handleDirty; dirty |= handleDirty;
} }
anyUnmapped |= handleUnmapped;
if (group.NeedsCopy) if (group.NeedsCopy)
{ {
// The texture we copied from is still being written to. Copy from it again the next time this texture is used. // The texture we copied from is still being written to. Copy from it again the next time this texture is used.
texture.SignalGroupDirty(); texture.SignalGroupDirty();
} }
_loadNeeded[baseHandle + i] = handleDirty && !handleUnmapped; bool loadNeeded = handleDirty && !handleUnmapped;
anyNotDirty |= !loadNeeded;
_loadNeeded[baseHandle + i] = loadNeeded;
} }
if (dirty) if (dirty)
{ {
if (anyUnmapped || (_handles.Length > 1 && (anyModified || split))) if (anyNotDirty || (_handles.Length > 1 && (anyModified || split)))
{ {
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage. // Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
@@ -331,24 +359,56 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="regionCount">The number of handles to synchronize</param> /// <param name="regionCount">The number of handles to synchronize</param>
private void SynchronizePartial(int baseHandle, int regionCount) private void SynchronizePartial(int baseHandle, int regionCount)
{ {
int spanEndIndex = -1;
int spanBase = 0;
ReadOnlySpan<byte> dataSpan = ReadOnlySpan<byte>.Empty;
for (int i = 0; i < regionCount; i++) for (int i = 0; i < regionCount; i++)
{ {
if (_loadNeeded[baseHandle + i]) if (_loadNeeded[baseHandle + i])
{ {
var info = GetHandleInformation(baseHandle + i); var info = GetHandleInformation(baseHandle + i);
// Ensure the data for this handle is loaded in the span.
if (spanEndIndex <= i - 1)
{
spanEndIndex = i;
if (_is3D)
{
// Look ahead to see how many handles need to be loaded.
for (int j = i + 1; j < regionCount; j++)
{
if (_loadNeeded[baseHandle + j])
{
spanEndIndex = j;
}
else
{
break;
}
}
}
var endInfo = spanEndIndex == i ? info : GetHandleInformation(baseHandle + spanEndIndex);
spanBase = _allOffsets[info.Index];
int spanLast = _allOffsets[endInfo.Index + endInfo.Layers * endInfo.Levels - 1];
int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size);
int size = endOffset - spanBase;
dataSpan = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)spanBase, (ulong)size));
}
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views. // Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
for (int layer = 0; layer < info.Layers; layer++) for (int layer = 0; layer < info.Layers; layer++)
{ {
for (int level = 0; level < info.Levels; level++) for (int level = 0; level < info.Levels; level++)
{ {
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level); int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
int offset = _allOffsets[offsetIndex]; int offset = _allOffsets[offsetIndex];
int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
int size = endOffset - offset;
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size)); ReadOnlySpan<byte> data = dataSpan.Slice(offset - spanBase);
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
@@ -865,8 +925,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>A TextureGroupHandle covering the given views</returns> /// <returns>A TextureGroupHandle covering the given views</returns>
private TextureGroupHandle GenerateHandles(int viewStart, int views) private TextureGroupHandle GenerateHandles(int viewStart, int views)
{ {
int viewEnd = viewStart + views - 1;
(_, int lastLevel) = GetLayerLevelForView(viewEnd);
int offset = _allOffsets[viewStart]; int offset = _allOffsets[viewStart];
int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views]; int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
int size = endOffset - offset; int size = endOffset - offset;
var result = new List<CpuRegionHandle>(); var result = new List<CpuRegionHandle>();
@@ -926,7 +989,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Update the views in this texture group, rebuilding the memory tracking if required. /// Update the views in this texture group, rebuilding the memory tracking if required.
/// </summary> /// </summary>
/// <param name="views">The views list of the storage texture</param> /// <param name="views">The views list of the storage texture</param>
public void UpdateViews(List<Texture> views) /// <param name="texture">The texture that has been added, if that is the only change, otherwise null</param>
public void UpdateViews(List<Texture> views, Texture texture)
{ {
// This is saved to calculate overlapping views for each handle. // This is saved to calculate overlapping views for each handle.
_views = views; _views = views;
@@ -964,17 +1028,44 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!regionsRebuilt) if (!regionsRebuilt)
{ {
// Must update the overlapping views on all handles, but only if they were not just recreated. if (texture != null)
foreach (TextureGroupHandle handle in _handles)
{ {
handle.RecalculateOverlaps(this, views); int offset = FindOffset(texture);
foreach (TextureGroupHandle handle in _handles)
{
handle.AddOverlap(offset, texture);
}
}
else
{
// Must update the overlapping views on all handles, but only if they were not just recreated.
foreach (TextureGroupHandle handle in _handles)
{
handle.RecalculateOverlaps(this, views);
}
} }
} }
SignalAllDirty(); SignalAllDirty();
} }
/// <summary>
/// Removes a view from the group, removing it from all overlap lists.
/// </summary>
/// <param name="view">View to remove from the group</param>
public void RemoveView(Texture view)
{
int offset = FindOffset(view);
foreach (TextureGroupHandle handle in _handles)
{
handle.RemoveOverlap(offset, view);
}
}
/// <summary> /// <summary>
/// Inherit handle state from an old set of handles, such as modified and dirty flags. /// Inherit handle state from an old set of handles, such as modified and dirty flags.
/// </summary> /// </summary>
@@ -1057,7 +1148,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The dirty flags from the previous handles will be kept. /// The dirty flags from the previous handles will be kept.
/// </summary> /// </summary>
/// <param name="handles">The handles to replace the current handles with</param> /// <param name="handles">The handles to replace the current handles with</param>
private void ReplaceHandles(TextureGroupHandle[] handles) /// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
private void ReplaceHandles(TextureGroupHandle[] handles, bool rangeChanged)
{ {
if (_handles != null) if (_handles != null)
{ {
@@ -1065,9 +1157,50 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (TextureGroupHandle groupHandle in handles) foreach (TextureGroupHandle groupHandle in handles)
{ {
foreach (CpuRegionHandle handle in groupHandle.Handles) if (rangeChanged)
{ {
handle.Reprotect(); // When the storage range changes, this becomes a little different.
// If a range does not match one in the original, treat it as modified.
// It has been newly mapped and its data must be synchronized.
if (groupHandle.Handles.Length == 0)
{
continue;
}
foreach (var oldGroup in _handles)
{
if (!groupHandle.OverlapsWith(oldGroup.Offset, oldGroup.Size))
{
continue;
}
foreach (CpuRegionHandle handle in groupHandle.Handles)
{
bool hasMatch = false;
foreach (var oldHandle in oldGroup.Handles)
{
if (oldHandle.RangeEquals(handle))
{
hasMatch = true;
break;
}
}
if (hasMatch)
{
handle.Reprotect();
}
}
}
}
else
{
foreach (CpuRegionHandle handle in groupHandle.Handles)
{
handle.Reprotect();
}
} }
} }
@@ -1089,7 +1222,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Recalculate handle regions for this texture group, and inherit existing state into the new handles. /// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
/// </summary> /// </summary>
private void RecalculateHandleRegions() /// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
private void RecalculateHandleRegions(bool rangeChanged = false)
{ {
TextureGroupHandle[] handles; TextureGroupHandle[] handles;
@@ -1171,7 +1305,21 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
ReplaceHandles(handles); ReplaceHandles(handles, rangeChanged);
}
/// <summary>
/// Regenerates handles when the storage range has been remapped.
/// This forces the regions to be fully subdivided.
/// </summary>
public void RangeChanged()
{
_hasLayerViews = true;
_hasMipViews = true;
RecalculateHandleRegions(true);
SignalAllDirty();
} }
/// <summary> /// <summary>

View File

@@ -159,6 +159,42 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// Adds a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
public void AddOverlap(int offset, Texture view)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
if (OverlapsWith(offset, (int)view.Size))
{
lock (Overlaps)
{
Overlaps.Add(view);
}
}
}
/// <summary>
/// Removes a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
public void RemoveOverlap(int offset, Texture view)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
if (OverlapsWith(offset, (int)view.Size))
{
lock (Overlaps)
{
Overlaps.Remove(view);
}
}
}
/// <summary> /// <summary>
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle. /// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
/// </summary> /// </summary>

View File

@@ -1,9 +1,12 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@@ -12,8 +15,63 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool> class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
{ {
/// <summary>
/// A request to dereference a texture from a pool.
/// </summary>
private struct DereferenceRequest
{
/// <summary>
/// Whether the dereference is due to a mapping change or not.
/// </summary>
public readonly bool IsRemapped;
/// <summary>
/// The texture being dereferenced.
/// </summary>
public readonly Texture Texture;
/// <summary>
/// The ID of the pool entry this reference belonged to.
/// </summary>
public readonly int ID;
/// <summary>
/// Create a dereference request for a texture with a specific pool ID, and remapped flag.
/// </summary>
/// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param>
/// <param name="texture">The texture being dereferenced</param>
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
private DereferenceRequest(bool isRemapped, Texture texture, int id)
{
IsRemapped = isRemapped;
Texture = texture;
ID = id;
}
/// <summary>
/// Create a dereference request for a texture removal.
/// </summary>
/// <param name="texture">The texture being removed</param>
/// <returns>A texture removal dereference request</returns>
public static DereferenceRequest Remove(Texture texture)
{
return new DereferenceRequest(false, texture, 0);
}
/// <summary>
/// Create a dereference request for a texture remapping with a specific pool ID.
/// </summary>
/// <param name="texture">The texture being remapped</param>
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
/// <returns>A remap dereference request</returns>
public static DereferenceRequest Remap(Texture texture, int id)
{
return new DereferenceRequest(true, texture, id);
}
}
private readonly GpuChannel _channel; private readonly GpuChannel _channel;
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>(); private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>();
private TextureDescriptor _defaultDescriptor; private TextureDescriptor _defaultDescriptor;
/// <summary> /// <summary>
@@ -58,7 +116,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
TextureInfo info = GetInfo(descriptor, out int layerSize); TextureInfo info = GetInfo(descriptor, out int layerSize);
ProcessDereferenceQueue(); // The dereference queue can put our texture back on the cache.
if ((texture = ProcessDereferenceQueue(id)) != null)
{
return ref descriptor;
}
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize); texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
@@ -69,10 +131,10 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
texture.IncrementReferenceCount(this, id);
Items[id] = texture; Items[id] = texture;
texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
DescriptorCache[id] = descriptor; DescriptorCache[id] = descriptor;
} }
else else
@@ -155,11 +217,14 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param> /// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
public void ForceRemove(Texture texture, int id, bool deferred) public void ForceRemove(Texture texture, int id, bool deferred)
{ {
Items[id] = null; var previous = Interlocked.Exchange(ref Items[id], null);
if (deferred) if (deferred)
{ {
_dereferenceQueue.Enqueue(texture); if (previous != null)
{
_dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
}
} }
else else
{ {
@@ -167,16 +232,91 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// Queues a request to update a texture's mapping.
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
/// </summary>
/// <param name="texture">Texture with potential mapping change</param>
/// <param name="id">ID in cache of texture with potential mapping change</param>
public void QueueUpdateMapping(Texture texture, int id)
{
if (Interlocked.Exchange(ref Items[id], null) == texture)
{
_dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
}
}
/// <summary> /// <summary>
/// Process the dereference queue, decrementing the reference count for each texture in it. /// Process the dereference queue, decrementing the reference count for each texture in it.
/// This is used to ensure that texture disposal happens on the render thread. /// This is used to ensure that texture disposal happens on the render thread.
/// </summary> /// </summary>
private void ProcessDereferenceQueue() /// <param name="id">The ID of the entry that triggered this method</param>
/// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns>
private Texture ProcessDereferenceQueue(int id = -1)
{ {
while (_dereferenceQueue.TryDequeue(out Texture toRemove)) while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
{ {
toRemove.DecrementReferenceCount(); Texture texture = request.Texture;
// Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
// TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
{
// Has the mapping for this texture changed?
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
ulong address = descriptor.UnpackAddress();
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
// If the texture is not mapped at all, delete its reference.
if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
{
texture.DecrementReferenceCount();
continue;
}
Items[request.ID] = texture;
// Create a new pool reference, as the last one was removed on unmap.
texture.IncrementReferenceCount(this, request.ID, address);
texture.DecrementReferenceCount();
// Refetch the range. Changes since the last check could have been lost
// as the cache entry was not restored (required to queue mapping change).
range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
if (!range.Equals(texture.Range))
{
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
{
// Texture could not be remapped due to a collision, just delete it.
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
{
// If this is null, a request was already queued to decrement reference.
texture.DecrementReferenceCount(this, request.ID);
}
continue;
}
}
if (request.ID == id)
{
return texture;
}
}
else
{
texture.DecrementReferenceCount();
}
} }
return null;
} }
/// <summary> /// <summary>
@@ -213,9 +353,10 @@ namespace Ryujinx.Graphics.Gpu.Image
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor); _channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
} }
texture.DecrementReferenceCount(this, id); if (Interlocked.Exchange(ref Items[id], null) != null)
{
Items[id] = null; texture.DecrementReferenceCount(this, id);
}
} }
} }
} }

View File

@@ -1,3 +1,5 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
@@ -11,16 +13,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{ {
public static byte[] Pack(ShaderSource[] sources) public static byte[] Pack(ShaderSource[] sources)
{ {
using MemoryStream output = new MemoryStream(); using MemoryStream output = MemoryStreamManager.Shared.GetStream();
using BinaryWriter writer = new BinaryWriter(output);
writer.Write(sources.Length); output.Write(sources.Length);
for (int i = 0; i < sources.Length; i++) foreach (ShaderSource source in sources)
{ {
writer.Write((int)sources[i].Stage); output.Write((int)source.Stage);
writer.Write(sources[i].BinaryCode.Length); output.Write(source.BinaryCode.Length);
writer.Write(sources[i].BinaryCode); output.Write(source.BinaryCode);
} }
return output.ToArray(); return output.ToArray();

View File

@@ -178,7 +178,7 @@ namespace Ryujinx.Graphics.OpenGL
} }
_pipeline.Initialize(this); _pipeline.Initialize(this);
_counters.Initialize(); _counters.Initialize(_pipeline);
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles. // This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
// This call is expected to fail if we're running with a core profile, // This call is expected to fail if we're running with a core profile,

View File

@@ -773,6 +773,16 @@ namespace Ryujinx.Graphics.OpenGL
_tfEnabled = false; _tfEnabled = false;
} }
public double GetCounterDivisor(CounterType type)
{
if (type == CounterType.SamplesPassed)
{
return _renderScale[0].X * _renderScale[0].X;
}
return 1;
}
public void SetAlphaTest(bool enable, float reference, CompareOp op) public void SetAlphaTest(bool enable, float reference, CompareOp op)
{ {
if (!enable) if (!enable)

View File

@@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
{ {
private const int MaxQueryRetries = 5000; private const int MaxQueryRetries = 5000;
private const long DefaultValue = -1; private const long DefaultValue = -1;
private const ulong HighMask = 0xFFFFFFFF00000000;
public int Query { get; } public int Query { get; }
@@ -63,11 +64,17 @@ namespace Ryujinx.Graphics.OpenGL.Queries
} }
} }
private bool WaitingForValue(long data)
{
return data == DefaultValue ||
((ulong)data & HighMask) == (unchecked((ulong)DefaultValue) & HighMask);
}
public bool TryGetResult(out long result) public bool TryGetResult(out long result)
{ {
result = Marshal.ReadInt64(_bufferMap); result = Marshal.ReadInt64(_bufferMap);
return result != DefaultValue; return WaitingForValue(result);
} }
public long AwaitResult(AutoResetEvent wakeSignal = null) public long AwaitResult(AutoResetEvent wakeSignal = null)
@@ -76,7 +83,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
if (wakeSignal == null) if (wakeSignal == null)
{ {
while (data == DefaultValue) while (WaitingForValue(data))
{ {
data = Marshal.ReadInt64(_bufferMap); data = Marshal.ReadInt64(_bufferMap);
} }
@@ -84,10 +91,10 @@ namespace Ryujinx.Graphics.OpenGL.Queries
else else
{ {
int iterations = 0; int iterations = 0;
while (data == DefaultValue && iterations++ < MaxQueryRetries) while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
{ {
data = Marshal.ReadInt64(_bufferMap); data = Marshal.ReadInt64(_bufferMap);
if (data == DefaultValue) if (WaitingForValue(data))
{ {
wakeSignal.WaitOne(1); wakeSignal.WaitOne(1);
} }

View File

@@ -13,6 +13,8 @@ namespace Ryujinx.Graphics.OpenGL.Queries
public CounterType Type { get; } public CounterType Type { get; }
public bool Disposed { get; private set; } public bool Disposed { get; private set; }
private readonly Pipeline _pipeline;
private Queue<CounterQueueEvent> _events = new Queue<CounterQueueEvent>(); private Queue<CounterQueueEvent> _events = new Queue<CounterQueueEvent>();
private CounterQueueEvent _current; private CounterQueueEvent _current;
@@ -28,10 +30,12 @@ namespace Ryujinx.Graphics.OpenGL.Queries
private Thread _consumerThread; private Thread _consumerThread;
internal CounterQueue(CounterType type) internal CounterQueue(Pipeline pipeline, CounterType type)
{ {
Type = type; Type = type;
_pipeline = pipeline;
QueryTarget glType = GetTarget(Type); QueryTarget glType = GetTarget(Type);
_queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize); _queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize);
@@ -119,7 +123,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
_current.ReserveForHostAccess(); _current.ReserveForHostAccess();
} }
_current.Complete(draws > 0); _current.Complete(draws > 0, _pipeline.GetCounterDivisor(Type));
_events.Enqueue(_current); _events.Enqueue(_current);
_current.OnResult += resultHandler; _current.OnResult += resultHandler;

View File

@@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
private object _lock = new object(); private object _lock = new object();
private ulong _result = ulong.MaxValue; private ulong _result = ulong.MaxValue;
private double _divisor = 1f;
public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex) public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex)
{ {
@@ -45,9 +46,11 @@ namespace Ryujinx.Graphics.OpenGL.Queries
ClearCounter = true; ClearCounter = true;
} }
internal void Complete(bool withResult) internal void Complete(bool withResult, double divisor)
{ {
_counter.End(withResult); _counter.End(withResult);
_divisor = divisor;
} }
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null) internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
@@ -78,7 +81,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
} }
} }
result += (ulong)queryResult; result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor);
_result = result; _result = result;

View File

@@ -14,12 +14,12 @@ namespace Ryujinx.Graphics.OpenGL.Queries
_counterQueues = new CounterQueue[count]; _counterQueues = new CounterQueue[count];
} }
public void Initialize() public void Initialize(Pipeline pipeline)
{ {
for (int index = 0; index < _counterQueues.Length; index++) for (int index = 0; index < _counterQueues.Length; index++)
{ {
CounterType type = (CounterType)index; CounterType type = (CounterType)index;
_counterQueues[index] = new CounterQueue(type); _counterQueues[index] = new CounterQueue(pipeline, type);
} }
} }

View File

@@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Texture
int outSize = GetTextureSize( int outSize = GetTextureSize(
width, width,
height, height,
depth, sliceDepth,
levels, levels,
layers, layers,
blockWidth, blockWidth,

View File

@@ -684,6 +684,16 @@ namespace Ryujinx.Graphics.Vulkan
_tfEnabled = false; _tfEnabled = false;
} }
public double GetCounterDivisor(CounterType type)
{
if (type == CounterType.SamplesPassed)
{
return _renderScale[0].X * _renderScale[0].X;
}
return 1;
}
public bool IsCommandBufferActive(CommandBuffer cb) public bool IsCommandBufferActive(CommandBuffer cb)
{ {
return CommandBuffer.Handle == cb.Handle; return CommandBuffer.Handle == cb.Handle;

View File

@@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
private const int MaxQueryRetries = 5000; private const int MaxQueryRetries = 5000;
private const long DefaultValue = -1; private const long DefaultValue = -1;
private const long DefaultValueInt = 0xFFFFFFFF; private const long DefaultValueInt = 0xFFFFFFFF;
private const ulong HighMask = 0xFFFFFFFF00000000;
private readonly Vk _api; private readonly Vk _api;
private readonly Device _device; private readonly Device _device;
@@ -125,6 +126,12 @@ namespace Ryujinx.Graphics.Vulkan.Queries
} }
} }
private bool WaitingForValue(long data)
{
return data == _defaultValue ||
(!_result32Bit && ((ulong)data & HighMask) == ((ulong)_defaultValue & HighMask));
}
public bool TryGetResult(out long result) public bool TryGetResult(out long result)
{ {
result = Marshal.ReadInt64(_bufferMap); result = Marshal.ReadInt64(_bufferMap);
@@ -138,7 +145,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
if (wakeSignal == null) if (wakeSignal == null)
{ {
while (data == _defaultValue) while (WaitingForValue(data))
{ {
data = Marshal.ReadInt64(_bufferMap); data = Marshal.ReadInt64(_bufferMap);
} }
@@ -146,10 +153,10 @@ namespace Ryujinx.Graphics.Vulkan.Queries
else else
{ {
int iterations = 0; int iterations = 0;
while (data == _defaultValue && iterations++ < MaxQueryRetries) while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
{ {
data = Marshal.ReadInt64(_bufferMap); data = Marshal.ReadInt64(_bufferMap);
if (data == _defaultValue) if (WaitingForValue(data))
{ {
wakeSignal.WaitOne(1); wakeSignal.WaitOne(1);
} }

View File

@@ -148,7 +148,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
_current.ReserveForHostAccess(); _current.ReserveForHostAccess();
} }
_current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten); _current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten, _pipeline.GetCounterDivisor(Type));
_events.Enqueue(_current); _events.Enqueue(_current);
_current.OnResult += resultHandler; _current.OnResult += resultHandler;

View File

@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
private object _lock = new object(); private object _lock = new object();
private ulong _result = ulong.MaxValue; private ulong _result = ulong.MaxValue;
private double _divisor = 1f;
public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex) public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex)
{ {
@@ -52,9 +53,11 @@ namespace Ryujinx.Graphics.Vulkan.Queries
ClearCounter = true; ClearCounter = true;
} }
internal void Complete(bool withResult) internal void Complete(bool withResult, double divisor)
{ {
_counter.End(withResult); _counter.End(withResult);
_divisor = divisor;
} }
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null) internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
@@ -85,7 +88,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
} }
} }
result += (ulong)queryResult; result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor);
_result = result; _result = result;

View File

@@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.Ncm; using LibHac.Tools.Ncm;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Services.Ssl; using Ryujinx.HLE.HOS.Services.Ssl;
@@ -637,12 +638,12 @@ namespace Ryujinx.HLE.FileSystem
private Stream GetZipStream(ZipArchiveEntry entry) private Stream GetZipStream(ZipArchiveEntry entry)
{ {
MemoryStream dest = new MemoryStream(); MemoryStream dest = MemoryStreamManager.Shared.GetStream();
Stream src = entry.Open(); using (Stream src = entry.Open())
{
src.CopyTo(dest); src.CopyTo(dest);
src.Dispose(); }
return dest; return dest;
} }

View File

@@ -1,5 +1,6 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -70,7 +71,7 @@ namespace Ryujinx.HLE.HOS.Applets.Browser
private byte[] BuildResponseOld(WebCommonReturnValue result) private byte[] BuildResponseOld(WebCommonReturnValue result)
{ {
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
using (BinaryWriter writer = new BinaryWriter(stream)) using (BinaryWriter writer = new BinaryWriter(stream))
{ {
writer.WriteStruct(result); writer.WriteStruct(result);
@@ -80,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Applets.Browser
} }
private byte[] BuildResponseNew(List<BrowserOutput> outputArguments) private byte[] BuildResponseNew(List<BrowserOutput> outputArguments)
{ {
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
using (BinaryWriter writer = new BinaryWriter(stream)) using (BinaryWriter writer = new BinaryWriter(stream))
{ {
writer.WriteStruct(new WebArgHeader writer.WriteStruct(new WebArgHeader

View File

@@ -1,4 +1,5 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Hid.Types; using Ryujinx.HLE.HOS.Services.Hid.Types;
@@ -123,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Applets
private byte[] BuildResponse(ControllerSupportResultInfo result) private byte[] BuildResponse(ControllerSupportResultInfo result)
{ {
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
using (BinaryWriter writer = new BinaryWriter(stream)) using (BinaryWriter writer = new BinaryWriter(stream))
{ {
writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf<ControllerSupportResultInfo>()))); writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf<ControllerSupportResultInfo>())));
@@ -134,7 +135,7 @@ namespace Ryujinx.HLE.HOS.Applets
private byte[] BuildResponse() private byte[] BuildResponse()
{ {
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
using (BinaryWriter writer = new BinaryWriter(stream)) using (BinaryWriter writer = new BinaryWriter(stream))
{ {
writer.Write((ulong)ResultCode.Success); writer.Write((ulong)ResultCode.Success);

View File

@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using System; using System;
using System.IO; using System.IO;
@@ -43,7 +44,7 @@ namespace Ryujinx.HLE.HOS.Applets
{ {
UserProfile currentUser = _system.AccountManager.LastOpenedUser; UserProfile currentUser = _system.AccountManager.LastOpenedUser;
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
using (BinaryWriter writer = new BinaryWriter(stream)) using (BinaryWriter writer = new BinaryWriter(stream))
{ {
writer.Write((ulong)PlayerSelectResult.Success); writer.Write((ulong)PlayerSelectResult.Success);

View File

@@ -338,7 +338,7 @@ namespace Ryujinx.HLE.HOS
ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0); ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
int[] defaultCapabilities = new int[] uint[] defaultCapabilities = new uint[]
{ {
0x030363F7, 0x030363F7,
0x1FFFFFCF, 0x1FFFFFCF,
@@ -552,4 +552,4 @@ namespace Ryujinx.HLE.HOS
IsPaused = pause; IsPaused = pause;
} }
} }
} }

View File

@@ -1,3 +1,6 @@
using Microsoft.IO;
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System; using System;
using System.IO; using System.IO;
@@ -18,20 +21,27 @@ namespace Ryujinx.HLE.HOS.Ipc
HasPId = (word & 1) != 0; HasPId = (word & 1) != 0;
ToCopy = new int[(word >> 1) & 0xf];
ToMove = new int[(word >> 5) & 0xf];
PId = HasPId ? reader.ReadUInt64() : 0; PId = HasPId ? reader.ReadUInt64() : 0;
for (int index = 0; index < ToCopy.Length; index++) int toCopySize = (word >> 1) & 0xf;
int[] toCopy = toCopySize == 0 ? Array.Empty<int>() : new int[toCopySize];
for (int index = 0; index < toCopy.Length; index++)
{ {
ToCopy[index] = reader.ReadInt32(); toCopy[index] = reader.ReadInt32();
} }
for (int index = 0; index < ToMove.Length; index++) ToCopy = toCopy;
int toMoveSize = (word >> 5) & 0xf;
int[] toMove = toMoveSize == 0 ? Array.Empty<int>() : new int[toMoveSize];
for (int index = 0; index < toMove.Length; index++)
{ {
ToMove[index] = reader.ReadInt32(); toMove[index] = reader.ReadInt32();
} }
ToMove = toMove;
} }
public IpcHandleDesc(int[] copy, int[] move) public IpcHandleDesc(int[] copy, int[] move)
@@ -57,36 +67,27 @@ namespace Ryujinx.HLE.HOS.Ipc
return new IpcHandleDesc(Array.Empty<int>(), handles); return new IpcHandleDesc(Array.Empty<int>(), handles);
} }
public byte[] GetBytes() public RecyclableMemoryStream GetStream()
{ {
using (MemoryStream ms = new MemoryStream()) RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
int word = HasPId ? 1 : 0;
word |= (ToCopy.Length & 0xf) << 1;
word |= (ToMove.Length & 0xf) << 5;
ms.Write(word);
if (HasPId)
{ {
BinaryWriter writer = new BinaryWriter(ms); ms.Write(PId);
int word = HasPId ? 1 : 0;
word |= (ToCopy.Length & 0xf) << 1;
word |= (ToMove.Length & 0xf) << 5;
writer.Write(word);
if (HasPId)
{
writer.Write(PId);
}
foreach (int handle in ToCopy)
{
writer.Write(handle);
}
foreach (int handle in ToMove)
{
writer.Write(handle);
}
return ms.ToArray();
} }
ms.Write(ToCopy);
ms.Write(ToMove);
ms.Position = 0;
return ms;
} }
} }
} }

View File

@@ -1,4 +1,8 @@
using Microsoft.IO;
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@@ -32,9 +36,9 @@ namespace Ryujinx.HLE.HOS.Ipc
ObjectIds = new List<int>(); ObjectIds = new List<int>();
} }
public IpcMessage(byte[] data, long cmdPtr) : this() public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr) : this()
{ {
using (MemoryStream ms = new MemoryStream(data)) using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data))
{ {
BinaryReader reader = new BinaryReader(ms); BinaryReader reader = new BinaryReader(ms);
@@ -114,124 +118,119 @@ namespace Ryujinx.HLE.HOS.Ipc
for (int index = 0; index < recvListCount; index++) for (int index = 0; index < recvListCount; index++)
{ {
RecvListBuff.Add(new IpcRecvListBuffDesc(reader)); RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64()));
} }
} }
public byte[] GetBytes(long cmdPtr, ulong recvListAddr) public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)
{ {
using (MemoryStream ms = new MemoryStream()) RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
int word0;
int word1;
word0 = (int)Type;
word0 |= (PtrBuff.Count & 0xf) << 16;
word0 |= (SendBuff.Count & 0xf) << 20;
word0 |= (ReceiveBuff.Count & 0xf) << 24;
word0 |= (ExchangeBuff.Count & 0xf) << 28;
using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream();
int dataLength = RawData?.Length ?? 0;
dataLength = (dataLength + 3) & ~3;
int rawLength = dataLength;
int pad0 = (int)GetPadSize16(cmdPtr + 8 + (handleDataStream?.Length ?? 0) + PtrBuff.Count * 8);
// Apparently, padding after Raw Data is 16 bytes, however when there is
// padding before Raw Data too, we need to subtract the size of this padding.
// This is the weirdest padding I've seen so far...
int pad1 = 0x10 - pad0;
dataLength = (dataLength + pad0 + pad1) / 4;
word1 = (dataLength & 0x3ff) | (2 << 10);
if (HandleDesc != null)
{ {
BinaryWriter writer = new BinaryWriter(ms); word1 |= 1 << 31;
int word0;
int word1;
word0 = (int)Type;
word0 |= (PtrBuff.Count & 0xf) << 16;
word0 |= (SendBuff.Count & 0xf) << 20;
word0 |= (ReceiveBuff.Count & 0xf) << 24;
word0 |= (ExchangeBuff.Count & 0xf) << 28;
byte[] handleData = Array.Empty<byte>();
if (HandleDesc != null)
{
handleData = HandleDesc.GetBytes();
}
int dataLength = RawData?.Length ?? 0;
dataLength = (dataLength + 3) & ~3;
int rawLength = dataLength;
int pad0 = (int)GetPadSize16(cmdPtr + 8 + handleData.Length + PtrBuff.Count * 8);
// Apparently, padding after Raw Data is 16 bytes, however when there is
// padding before Raw Data too, we need to subtract the size of this padding.
// This is the weirdest padding I've seen so far...
int pad1 = 0x10 - pad0;
dataLength = (dataLength + pad0 + pad1) / 4;
word1 = (dataLength & 0x3ff) | (2 << 10);
if (HandleDesc != null)
{
word1 |= 1 << 31;
}
writer.Write(word0);
writer.Write(word1);
writer.Write(handleData);
for (int index = 0; index < PtrBuff.Count; index++)
{
writer.Write(PtrBuff[index].GetWord0());
writer.Write(PtrBuff[index].GetWord1());
}
ms.Seek(pad0, SeekOrigin.Current);
if (RawData != null)
{
writer.Write(RawData);
ms.Seek(rawLength - RawData.Length, SeekOrigin.Current);
}
writer.Write(new byte[pad1]);
writer.Write(recvListAddr);
return ms.ToArray();
} }
ms.Write(word0);
ms.Write(word1);
if (handleDataStream != null)
{
ms.Write(handleDataStream);
}
foreach (IpcPtrBuffDesc ptrBuffDesc in PtrBuff)
{
ms.Write(ptrBuffDesc.GetWord0());
ms.Write(ptrBuffDesc.GetWord1());
}
ms.WriteByte(0, pad0);
if (RawData != null)
{
ms.Write(RawData);
ms.WriteByte(0, rawLength - RawData.Length);
}
ms.WriteByte(0, pad1);
ms.Write(recvListAddr);
ms.Position = 0;
return ms;
} }
public byte[] GetBytesTipc() public RecyclableMemoryStream GetStreamTipc()
{ {
Debug.Assert(PtrBuff.Count == 0); Debug.Assert(PtrBuff.Count == 0);
using (MemoryStream ms = new MemoryStream()) RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
int word0;
int word1;
word0 = (int)Type;
word0 |= (SendBuff.Count & 0xf) << 20;
word0 |= (ReceiveBuff.Count & 0xf) << 24;
word0 |= (ExchangeBuff.Count & 0xf) << 28;
using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream();
int dataLength = RawData?.Length ?? 0;
dataLength = ((dataLength + 3) & ~3) / 4;
word1 = (dataLength & 0x3ff);
if (HandleDesc != null)
{ {
BinaryWriter writer = new BinaryWriter(ms); word1 |= 1 << 31;
int word0;
int word1;
word0 = (int)Type;
word0 |= (SendBuff.Count & 0xf) << 20;
word0 |= (ReceiveBuff.Count & 0xf) << 24;
word0 |= (ExchangeBuff.Count & 0xf) << 28;
byte[] handleData = Array.Empty<byte>();
if (HandleDesc != null)
{
handleData = HandleDesc.GetBytes();
}
int dataLength = RawData?.Length ?? 0;
dataLength = ((dataLength + 3) & ~3) / 4;
word1 = (dataLength & 0x3ff);
if (HandleDesc != null)
{
word1 |= 1 << 31;
}
writer.Write(word0);
writer.Write(word1);
writer.Write(handleData);
if (RawData != null)
{
writer.Write(RawData);
}
return ms.ToArray();
} }
ms.Write(word0);
ms.Write(word1);
if (handleDataStream != null)
{
ms.Write(handleDataStream);
}
if (RawData != null)
{
ms.Write(RawData);
}
return ms;
} }
private long GetPadSize16(long position) private long GetPadSize16(long position)

View File

@@ -13,13 +13,11 @@ namespace Ryujinx.HLE.HOS.Ipc
Size = size; Size = size;
} }
public IpcRecvListBuffDesc(BinaryReader reader) public IpcRecvListBuffDesc(ulong packedValue)
{ {
ulong value = reader.ReadUInt64(); Position = packedValue & 0xffffffffffff;
Position = value & 0xffffffffffff; Size = (ushort)(packedValue >> 48);
Size = (ushort)(value >> 48);
} }
} }
} }

View File

@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint) public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint)
{ {
Object = schedulerObj; Object = schedulerObj;
TimePoint = timePoint; TimePoint = timePoint;
} }
} }
@@ -27,6 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
private bool _keepRunning; private bool _keepRunning;
private long _enforceWakeupFromSpinWait; private long _enforceWakeupFromSpinWait;
private const long NanosecondsPerSecond = 1000000000L;
private const long NanosecondsPerMillisecond = 1000000L;
public KTimeManager(KernelContext context) public KTimeManager(KernelContext context)
{ {
_context = context; _context = context;
@@ -55,7 +58,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
{ {
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
if (timeout < 1000000) if (timeout < NanosecondsPerMillisecond)
{ {
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1); Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
} }
@@ -142,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
private WaitingObject GetNextWaitingObject() private WaitingObject GetNextWaitingObject()
{ {
WaitingObject selected = null; WaitingObject selected = null;
long lowestTimePoint = long.MaxValue; long lowestTimePoint = long.MaxValue;
for (int index = _waitingObjects.Count - 1; index >= 0; index--) for (int index = _waitingObjects.Count - 1; index >= 0; index--)
@@ -161,7 +164,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
public static long ConvertNanosecondsToMilliseconds(long time) public static long ConvertNanosecondsToMilliseconds(long time)
{ {
time /= 1000000; time /= NanosecondsPerMillisecond;
if ((ulong)time > int.MaxValue) if ((ulong)time > int.MaxValue)
{ {
@@ -173,18 +176,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
public static long ConvertMillisecondsToNanoseconds(long time) public static long ConvertMillisecondsToNanoseconds(long time)
{ {
return time * 1000000; return time * NanosecondsPerMillisecond;
} }
public static long ConvertNanosecondsToHostTicks(long ns) public static long ConvertNanosecondsToHostTicks(long ns)
{ {
long nsDiv = ns / 1000000000; long nsDiv = ns / NanosecondsPerSecond;
long nsMod = ns % 1000000000; long nsMod = ns % NanosecondsPerSecond;
long tickDiv = PerformanceCounter.TicksPerSecond / 1000000000; long tickDiv = PerformanceCounter.TicksPerSecond / NanosecondsPerSecond;
long tickMod = PerformanceCounter.TicksPerSecond % 1000000000; long tickMod = PerformanceCounter.TicksPerSecond % NanosecondsPerSecond;
long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / 1000000000; long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / NanosecondsPerSecond;
return (nsDiv * tickDiv) * 1000000000 + nsDiv * tickMod + nsMod * tickDiv + baseTicks; return (nsDiv * tickDiv) * NanosecondsPerSecond + nsDiv * tickMod + nsMod * tickDiv + baseTicks;
} }
public static long ConvertGuestTicksToNanoseconds(long ticks) public static long ConvertGuestTicksToNanoseconds(long ticks)

View File

@@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Kernel
public const int InitialKipId = 1; public const int InitialKipId = 1;
public const int InitialProcessId = 0x51; public const int InitialProcessId = 0x51;
public const int SupervisorCallCount = 0xC0;
public const int MemoryBlockAllocatorSize = 0x2710; public const int MemoryBlockAllocatorSize = 0x2710;
public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase; public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase;
@@ -15,4 +17,4 @@ namespace Ryujinx.HLE.HOS.Kernel
public const ulong CounterFrequency = 19200000; public const ulong CounterFrequency = 19200000;
} }
} }

View File

@@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel
public static Result StartInitialProcess( public static Result StartInitialProcess(
KernelContext context, KernelContext context,
ProcessCreationInfo creationInfo, ProcessCreationInfo creationInfo,
ReadOnlySpan<int> capabilities, ReadOnlySpan<uint> capabilities,
int mainThreadPriority, int mainThreadPriority,
ThreadStart customThreadStart) ThreadStart customThreadStart)
{ {

View File

@@ -0,0 +1,22 @@
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
static class CapabilityExtensions
{
public static CapabilityType GetCapabilityType(this uint cap)
{
return (CapabilityType)(((cap + 1) & ~cap) - 1);
}
public static uint GetFlag(this CapabilityType type)
{
return (uint)type + 1;
}
public static uint GetId(this CapabilityType type)
{
return (uint)BitOperations.TrailingZeroCount(type.GetFlag());
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Ryujinx.HLE.HOS.Kernel.Process
{
enum CapabilityType : uint
{
CorePriority = (1u << 3) - 1,
SyscallMask = (1u << 4) - 1,
MapRange = (1u << 6) - 1,
MapIoPage = (1u << 7) - 1,
MapRegion = (1u << 10) - 1,
InterruptPair = (1u << 11) - 1,
ProgramType = (1u << 13) - 1,
KernelVersion = (1u << 14) - 1,
HandleTable = (1u << 15) - 1,
DebugFlags = (1u << 16) - 1,
Invalid = 0u,
Padding = ~0u
}
}

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private int _activeSlotsCount; private int _activeSlotsCount;
private int _size; private uint _size;
private ushort _idCounter; private ushort _idCounter;
@@ -28,9 +28,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_context = context; _context = context;
} }
public Result Initialize(int size) public Result Initialize(uint size)
{ {
if ((uint)size > 1024) if (size > 1024)
{ {
return KernelResult.OutOfMemory; return KernelResult.OutOfMemory;
} }

View File

@@ -16,11 +16,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
class KProcess : KSynchronizationObject class KProcess : KSynchronizationObject
{ {
public const int KernelVersionMajor = 10; public const uint KernelVersionMajor = 10;
public const int KernelVersionMinor = 4; public const uint KernelVersionMinor = 4;
public const int KernelVersionRevision = 0; public const uint KernelVersionRevision = 0;
public const int KernelVersionPacked = public const uint KernelVersionPacked =
(KernelVersionMajor << 19) | (KernelVersionMajor << 19) |
(KernelVersionMinor << 15) | (KernelVersionMinor << 15) |
(KernelVersionRevision << 0); (KernelVersionRevision << 0);
@@ -119,7 +119,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public Result InitializeKip( public Result InitializeKip(
ProcessCreationInfo creationInfo, ProcessCreationInfo creationInfo,
ReadOnlySpan<int> capabilities, ReadOnlySpan<uint> capabilities,
KPageList pageList, KPageList pageList,
KResourceLimit resourceLimit, KResourceLimit resourceLimit,
MemoryRegion memRegion, MemoryRegion memRegion,
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public Result Initialize( public Result Initialize(
ProcessCreationInfo creationInfo, ProcessCreationInfo creationInfo,
ReadOnlySpan<int> capabilities, ReadOnlySpan<uint> capabilities,
KResourceLimit resourceLimit, KResourceLimit resourceLimit,
MemoryRegion memRegion, MemoryRegion memRegion,
IProcessContextFactory contextFactory, IProcessContextFactory contextFactory,

View File

@@ -1,4 +1,3 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
@@ -9,48 +8,49 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
class KProcessCapabilities class KProcessCapabilities
{ {
public byte[] SvcAccessMask { get; private set; } public byte[] SvcAccessMask { get; }
public byte[] IrqAccessMask { get; private set; } public byte[] IrqAccessMask { get; }
public ulong AllowedCpuCoresMask { get; private set; } public ulong AllowedCpuCoresMask { get; private set; }
public ulong AllowedThreadPriosMask { get; private set; } public ulong AllowedThreadPriosMask { get; private set; }
public int DebuggingFlags { get; private set; } public uint DebuggingFlags { get; private set; }
public int HandleTableSize { get; private set; } public uint HandleTableSize { get; private set; }
public int KernelReleaseVersion { get; private set; } public uint KernelReleaseVersion { get; private set; }
public int ApplicationType { get; private set; } public uint ApplicationType { get; private set; }
public KProcessCapabilities() public KProcessCapabilities()
{ {
SvcAccessMask = new byte[0x10]; // length / number of bits of the underlying type
SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / 8];
IrqAccessMask = new byte[0x80]; IrqAccessMask = new byte[0x80];
} }
public Result InitializeForKernel(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager) public Result InitializeForKernel(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
{ {
AllowedCpuCoresMask = 0xf; AllowedCpuCoresMask = 0xf;
AllowedThreadPriosMask = ulong.MaxValue; AllowedThreadPriosMask = ulong.MaxValue;
DebuggingFlags &= ~3; DebuggingFlags &= ~3u;
KernelReleaseVersion = KProcess.KernelVersionPacked; KernelReleaseVersion = KProcess.KernelVersionPacked;
return Parse(capabilities, memoryManager); return Parse(capabilities, memoryManager);
} }
public Result InitializeForUser(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager) public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
{ {
return Parse(capabilities, memoryManager); return Parse(capabilities, memoryManager);
} }
private Result Parse(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager) private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
{ {
int mask0 = 0; int mask0 = 0;
int mask1 = 0; int mask1 = 0;
for (int index = 0; index < capabilities.Length; index++) for (int index = 0; index < capabilities.Length; index++)
{ {
int cap = capabilities[index]; uint cap = capabilities[index];
if (((cap + 1) & ~cap) != 0x40) if (cap.GetCapabilityType() != CapabilityType.MapRange)
{ {
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager); Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
@@ -66,7 +66,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return KernelResult.InvalidCombination; return KernelResult.InvalidCombination;
} }
int prevCap = cap; uint prevCap = cap;
cap = capabilities[++index]; cap = capabilities[++index];
@@ -85,8 +85,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return KernelResult.InvalidSize; return KernelResult.InvalidSize;
} }
long address = ((long)(uint)prevCap << 5) & 0xffffff000; long address = ((long)prevCap << 5) & 0xffffff000;
long size = ((long)(uint)cap << 5) & 0xfffff000; long size = ((long)cap << 5) & 0xfffff000;
if (((ulong)(address + size - 1) >> 36) != 0) if (((ulong)(address + size - 1) >> 36) != 0)
{ {
@@ -118,20 +118,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return Result.Success; return Result.Success;
} }
private Result ParseCapability(int cap, ref int mask0, ref int mask1, KPageTableBase memoryManager) private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
{ {
int code = (cap + 1) & ~cap; CapabilityType code = cap.GetCapabilityType();
if (code == 1) if (code == CapabilityType.Invalid)
{ {
return KernelResult.InvalidCapability; return KernelResult.InvalidCapability;
} }
else if (code == 0) else if (code == CapabilityType.Padding)
{ {
return Result.Success; return Result.Success;
} }
int codeMask = 1 << (32 - BitOperations.LeadingZeroCount((uint)code + 1)); int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.GetFlag() + 1));
// Check if the property was already set. // Check if the property was already set.
if (((mask0 & codeMask) & 0x1e008) != 0) if (((mask0 & codeMask) & 0x1e008) != 0)
@@ -143,23 +143,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
switch (code) switch (code)
{ {
case 8: case CapabilityType.CorePriority:
{ {
if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0) if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0)
{ {
return KernelResult.InvalidCapability; return KernelResult.InvalidCapability;
} }
int lowestCpuCore = (cap >> 16) & 0xff; uint lowestCpuCore = (cap >> 16) & 0xff;
int highestCpuCore = (cap >> 24) & 0xff; uint highestCpuCore = (cap >> 24) & 0xff;
if (lowestCpuCore > highestCpuCore) if (lowestCpuCore > highestCpuCore)
{ {
return KernelResult.InvalidCombination; return KernelResult.InvalidCombination;
} }
int highestThreadPrio = (cap >> 4) & 0x3f; uint highestThreadPrio = (cap >> 4) & 0x3f;
int lowestThreadPrio = (cap >> 10) & 0x3f; uint lowestThreadPrio = (cap >> 10) & 0x3f;
if (lowestThreadPrio > highestThreadPrio) if (lowestThreadPrio > highestThreadPrio)
{ {
@@ -177,9 +177,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break; break;
} }
case 0x10: case CapabilityType.SyscallMask:
{ {
int slot = (cap >> 29) & 7; int slot = ((int)cap >> 29) & 7;
int svcSlotMask = 1 << slot; int svcSlotMask = 1 << slot;
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
mask1 |= svcSlotMask; mask1 |= svcSlotMask;
int svcMask = (cap >> 5) & 0xffffff; uint svcMask = (cap >> 5) & 0xffffff;
int baseSvc = slot * 24; int baseSvc = slot * 24;
@@ -203,7 +203,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
int svcId = baseSvc + index; int svcId = baseSvc + index;
if (svcId > 0x7f) if (svcId >= KernelConstants.SupervisorCallCount)
{ {
return KernelResult.MaximumExceeded; return KernelResult.MaximumExceeded;
} }
@@ -214,20 +214,27 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break; break;
} }
case 0x80: case CapabilityType.MapIoPage:
{ {
long address = ((long)(uint)cap << 4) & 0xffffff000; long address = ((long)cap << 4) & 0xffffff000;
memoryManager.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite); memoryManager.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite);
break; break;
} }
case 0x800: case CapabilityType.MapRegion:
{
// TODO: Implement capabilities for MapRegion
break;
}
case CapabilityType.InterruptPair:
{ {
// TODO: GIC distributor check. // TODO: GIC distributor check.
int irq0 = (cap >> 12) & 0x3ff; int irq0 = ((int)cap >> 12) & 0x3ff;
int irq1 = (cap >> 22) & 0x3ff; int irq1 = ((int)cap >> 22) & 0x3ff;
if (irq0 != 0x3ff) if (irq0 != 0x3ff)
{ {
@@ -242,11 +249,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break; break;
} }
case 0x2000: case CapabilityType.ProgramType:
{ {
int applicationType = cap >> 14; uint applicationType = (cap >> 14);
if ((uint)applicationType > 7) if (applicationType > 7)
{ {
return KernelResult.ReservedValue; return KernelResult.ReservedValue;
} }
@@ -256,7 +263,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break; break;
} }
case 0x4000: case CapabilityType.KernelVersion:
{ {
// Note: This check is bugged on kernel too, we are just replicating the bug here. // Note: This check is bugged on kernel too, we are just replicating the bug here.
if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000) if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000)
@@ -269,11 +276,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break; break;
} }
case 0x8000: case CapabilityType.HandleTable:
{ {
int handleTableSize = cap >> 26; uint handleTableSize = cap >> 26;
if ((uint)handleTableSize > 0x3ff) if (handleTableSize > 0x3ff)
{ {
return KernelResult.ReservedValue; return KernelResult.ReservedValue;
} }
@@ -283,16 +290,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break; break;
} }
case 0x10000: case CapabilityType.DebugFlags:
{ {
int debuggingFlags = cap >> 19; uint debuggingFlags = cap >> 19;
if ((uint)debuggingFlags > 3) if (debuggingFlags > 3)
{ {
return KernelResult.ReservedValue; return KernelResult.ReservedValue;
} }
DebuggingFlags &= ~3; DebuggingFlags &= ~3u;
DebuggingFlags |= debuggingFlags; DebuggingFlags |= debuggingFlags;
break; break;
@@ -304,18 +311,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return Result.Success; return Result.Success;
} }
private static ulong GetMaskFromMinMax(int min, int max) private static ulong GetMaskFromMinMax(uint min, uint max)
{ {
int range = max - min + 1; uint range = max - min + 1;
if (range == 64) if (range == 64)
{ {
return ulong.MaxValue; return ulong.MaxValue;
} }
ulong mask = (1UL << range) - 1; ulong mask = (1UL << (int)range) - 1;
return mask << min; return mask << (int)min;
} }
} }
} }

View File

@@ -1,5 +1,8 @@
namespace Ryujinx.HLE.HOS.Kernel.Process using System;
namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
[Flags]
enum ProcessCreationFlags enum ProcessCreationFlags
{ {
Is64Bit = 1 << 0, Is64Bit = 1 << 0,

View File

@@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
public Result CreateProcess( public Result CreateProcess(
out int handle, out int handle,
ProcessCreationInfo info, ProcessCreationInfo info,
ReadOnlySpan<int> capabilities, ReadOnlySpan<uint> capabilities,
IProcessContextFactory contextFactory, IProcessContextFactory contextFactory,
ThreadStart customThreadStart = null) ThreadStart customThreadStart = null)
{ {
@@ -553,7 +553,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
KSynchronizationObject[] syncObjs = new KSynchronizationObject[handles.Length]; KSynchronizationObject[] syncObjs = handles.Length == 0 ? Array.Empty<KSynchronizationObject>() : new KSynchronizationObject[handles.Length];
for (int index = 0; index < handles.Length; index++) for (int index = 0; index < handles.Length; index++)
{ {
@@ -3002,4 +3002,4 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return (address & 3) != 0; return (address & 3) != 0;
} }
} }
} }

View File

@@ -5,11 +5,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
class KPriorityQueue class KPriorityQueue
{ {
private LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore; private readonly LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
private LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore; private readonly LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
private long[] _scheduledPrioritiesPerCore; private readonly long[] _scheduledPrioritiesPerCore;
private long[] _suggestedPrioritiesPerCore; private readonly long[] _suggestedPrioritiesPerCore;
public KPriorityQueue() public KPriorityQueue()
{ {
@@ -32,43 +32,134 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; _suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
} }
public IEnumerable<KThread> SuggestedThreads(int core) public readonly ref struct KThreadEnumerable
{ {
return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core); readonly LinkedList<KThread>[][] _listPerPrioPerCore;
} readonly long[] _prios;
readonly int _core;
public IEnumerable<KThread> ScheduledThreads(int core) public KThreadEnumerable(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
{
return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
}
private IEnumerable<KThread> Iterate(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
{
long prioMask = prios[core];
int prio = BitOperations.TrailingZeroCount(prioMask);
prioMask &= ~(1L << prio);
while (prio < KScheduler.PrioritiesCount)
{ {
LinkedList<KThread> list = listPerPrioPerCore[prio][core]; _listPerPrioPerCore = listPerPrioPerCore;
_prios = prios;
_core = core;
}
LinkedListNode<KThread> node = list.First; public Enumerator GetEnumerator()
{
return new Enumerator(_listPerPrioPerCore, _prios, _core);
}
while (node != null) public ref struct Enumerator
{
private readonly LinkedList<KThread>[][] _listPerPrioPerCore;
private readonly int _core;
private long _prioMask;
private int _prio;
private LinkedList<KThread> _list;
private LinkedListNode<KThread> _node;
public Enumerator(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
{ {
yield return node.Value; _listPerPrioPerCore = listPerPrioPerCore;
_core = core;
node = node.Next; _prioMask = prios[core];
_prio = BitOperations.TrailingZeroCount(_prioMask);
_prioMask &= ~(1L << _prio);
} }
prio = BitOperations.TrailingZeroCount(prioMask); public KThread Current => _node?.Value;
prioMask &= ~(1L << prio); public bool MoveNext()
{
_node = _node?.Next;
if (_node == null)
{
if (!MoveNextListAndFirstNode())
{
return false;
}
}
return _node != null;
}
private bool MoveNextListAndFirstNode()
{
if (_prio < KScheduler.PrioritiesCount)
{
_list = _listPerPrioPerCore[_prio][_core];
_node = _list.First;
_prio = BitOperations.TrailingZeroCount(_prioMask);
_prioMask &= ~(1L << _prio);
return true;
}
else
{
_list = null;
_node = null;
return false;
}
}
} }
} }
public KThreadEnumerable ScheduledThreads(int core)
{
return new KThreadEnumerable(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
}
public KThreadEnumerable SuggestedThreads(int core)
{
return new KThreadEnumerable(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core);
}
public KThread ScheduledThreadsFirstOrDefault(int core)
{
return ScheduledThreadsElementAtOrDefault(core, 0);
}
public KThread ScheduledThreadsElementAtOrDefault(int core, int index)
{
int currentIndex = 0;
foreach (var scheduledThread in ScheduledThreads(core))
{
if (currentIndex == index)
{
return scheduledThread;
}
else
{
currentIndex++;
}
}
return null;
}
public KThread ScheduledThreadsWithDynamicPriorityFirstOrDefault(int core, int dynamicPriority)
{
foreach (var scheduledThread in ScheduledThreads(core))
{
if (scheduledThread.DynamicPriority == dynamicPriority)
{
return scheduledThread;
}
}
return null;
}
public bool HasScheduledThreads(int core)
{
return ScheduledThreadsFirstOrDefault(core) != null;
}
public void TransferToCore(int prio, int dstCore, KThread thread) public void TransferToCore(int prio, int dstCore, KThread thread)
{ {
int srcCore = thread.ActiveCore; int srcCore = thread.ActiveCore;

View File

@@ -1,8 +1,6 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
@@ -17,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 }; private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 };
private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount];
private readonly KernelContext _context; private readonly KernelContext _context;
private readonly int _coreId; private readonly int _coreId;
@@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
for (int core = 0; core < CpuCoresCount; core++) for (int core = 0; core < CpuCoresCount; core++)
{ {
KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault(); KThread thread = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core);
if (thread != null && if (thread != null &&
thread.Owner != null && thread.Owner != null &&
@@ -115,12 +115,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
// If the core is not idle (there's already a thread running on it), // If the core is not idle (there's already a thread running on it),
// then we don't need to attempt load balancing. // then we don't need to attempt load balancing.
if (context.PriorityQueue.ScheduledThreads(core).Any()) if (context.PriorityQueue.HasScheduledThreads(core))
{ {
continue; continue;
} }
int[] srcCoresHighestPrioThreads = new int[CpuCoresCount]; Array.Fill(_srcCoresHighestPrioThreads, 0);
int srcCoresHighestPrioThreadsCount = 0; int srcCoresHighestPrioThreadsCount = 0;
@@ -136,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
break; break;
} }
srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore; _srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore;
} }
// Not yet selected candidate found. // Not yet selected candidate found.
@@ -158,9 +158,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// (the first one that doesn't make the source core idle if moved). // (the first one that doesn't make the source core idle if moved).
for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++) for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++)
{ {
int srcCore = srcCoresHighestPrioThreads[index]; int srcCore = _srcCoresHighestPrioThreads[index];
KThread src = context.PriorityQueue.ScheduledThreads(srcCore).ElementAtOrDefault(1); KThread src = context.PriorityQueue.ScheduledThreadsElementAtOrDefault(srcCore, 1);
if (src != null) if (src != null)
{ {
@@ -422,9 +422,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private static void RotateScheduledQueue(KernelContext context, int core, int prio) private static void RotateScheduledQueue(KernelContext context, int core, int prio)
{ {
IEnumerable<KThread> scheduledThreads = context.PriorityQueue.ScheduledThreads(core); KThread selectedThread = context.PriorityQueue.ScheduledThreadsWithDynamicPriorityFirstOrDefault(core, prio);
KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio);
KThread nextThread = null; KThread nextThread = null;
// Yield priority queue. // Yield priority queue.
@@ -433,14 +431,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread); nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread);
} }
IEnumerable<KThread> SuitableCandidates() static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread selectedThread, KThread nextThread, Predicate< KThread> predicate)
{ {
foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
{ {
int suggestedCore = suggested.ActiveCore; int suggestedCore = suggested.ActiveCore;
if (suggestedCore >= 0) if (suggestedCore >= 0)
{ {
KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault(); KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore);
if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2)) if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2))
{ {
@@ -453,14 +451,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
nextThread == null || nextThread == null ||
nextThread.LastScheduledTime >= suggested.LastScheduledTime) nextThread.LastScheduledTime >= suggested.LastScheduledTime)
{ {
yield return suggested; if (predicate(suggested))
{
return suggested;
}
} }
} }
return null;
} }
// Select candidate threads that could run on this core. // Select candidate threads that could run on this core.
// Only take into account threads that are not yet selected. // Only take into account threads that are not yet selected.
KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio); KThread dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority == prio);
if (dst != null) if (dst != null)
{ {
@@ -469,11 +472,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// If the priority of the currently selected thread is lower or same as the preemption priority, // If the priority of the currently selected thread is lower or same as the preemption priority,
// then try to migrate a thread with lower priority. // then try to migrate a thread with lower priority.
KThread bestCandidate = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault(); KThread bestCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core);
if (bestCandidate != null && bestCandidate.DynamicPriority >= prio) if (bestCandidate != null && bestCandidate.DynamicPriority >= prio)
{ {
dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority < bestCandidate.DynamicPriority); dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority < bestCandidate.DynamicPriority);
if (dst != null) if (dst != null)
{ {
@@ -534,7 +537,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// Move current thread to the end of the queue. // Move current thread to the end of the queue.
KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread); KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread);
IEnumerable<KThread> SuitableCandidates() static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread nextThread, int lessThanOrEqualPriority)
{ {
foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
{ {
@@ -554,12 +557,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (suggested.LastScheduledTime <= nextThread.LastScheduledTime || if (suggested.LastScheduledTime <= nextThread.LastScheduledTime ||
suggested.DynamicPriority < nextThread.DynamicPriority) suggested.DynamicPriority < nextThread.DynamicPriority)
{ {
yield return suggested; if (suggested.DynamicPriority <= lessThanOrEqualPriority)
{
return suggested;
}
} }
} }
return null;
} }
KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); KThread dst = FirstSuitableCandidateOrDefault(context, core, nextThread, prio);
if (dst != null) if (dst != null)
{ {
@@ -596,7 +604,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread); context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread);
if (!context.PriorityQueue.ScheduledThreads(core).Any()) if (!context.PriorityQueue.HasScheduledThreads(core))
{ {
KThread selectedThread = null; KThread selectedThread = null;
@@ -609,7 +617,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
continue; continue;
} }
KThread firstCandidate = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault(); KThread firstCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore);
if (firstCandidate == suggested) if (firstCandidate == suggested)
{ {

View File

@@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
} }
else else
{ {
LinkedListNode<KThread>[] syncNodes = new LinkedListNode<KThread>[syncObjs.Length]; LinkedListNode<KThread>[] syncNodes = syncObjs.Length == 0 ? Array.Empty<LinkedListNode<KThread>>() : new LinkedListNode<KThread>[syncObjs.Length];
for (int index = 0; index < syncObjs.Length; index++) for (int index = 0; index < syncObjs.Length; index++)
{ {

View File

@@ -80,7 +80,7 @@ namespace Ryujinx.HLE.HOS
ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL; ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL;
ulong codeAddress = codeBaseAddress + (ulong)kip.TextOffset; ulong codeAddress = codeBaseAddress + kip.TextOffset;
ProcessCreationFlags flags = 0; ProcessCreationFlags flags = 0;
@@ -231,13 +231,13 @@ namespace Ryujinx.HLE.HOS
nsoSize = BitUtils.AlignUp<uint>(nsoSize, KPageTableBase.PageSize); nsoSize = BitUtils.AlignUp<uint>(nsoSize, KPageTableBase.PageSize);
nsoBase[index] = codeStart + (ulong)codeSize; nsoBase[index] = codeStart + codeSize;
codeSize += nsoSize; codeSize += nsoSize;
if (arguments != null && argsSize == 0) if (arguments != null && argsSize == 0)
{ {
argsStart = (ulong)codeSize; argsStart = codeSize;
argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize); argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize);
@@ -318,7 +318,7 @@ namespace Ryujinx.HLE.HOS
result = process.Initialize( result = process.Initialize(
creationInfo, creationInfo,
MemoryMarshal.Cast<byte, int>(npdm.KernelCapabilityData).ToArray(), MemoryMarshal.Cast<byte, uint>(npdm.KernelCapabilityData),
resourceLimit, resourceLimit,
memoryRegion, memoryRegion,
processContextFactory); processContextFactory);

View File

@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.IO; using System.IO;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
@@ -10,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
public static byte[] MakeLaunchParams(UserProfile userProfile) public static byte[] MakeLaunchParams(UserProfile userProfile)
{ {
// Size needs to be at least 0x88 bytes otherwise application errors. // Size needs to be at least 0x88 bytes otherwise application errors.
using (MemoryStream ms = new MemoryStream()) using (MemoryStream ms = MemoryStreamManager.Shared.GetStream())
{ {
BinaryWriter writer = new BinaryWriter(ms); BinaryWriter writer = new BinaryWriter(ms);

View File

@@ -5,6 +5,7 @@ using LibHac.FsSystem;
using LibHac.Ncm; using LibHac.Ncm;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -160,7 +161,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl
static uint KXor(uint data) => data ^ FontKey; static uint KXor(uint data) => data ^ FontKey;
using (BinaryReader reader = new BinaryReader(bfttfStream)) using (BinaryReader reader = new BinaryReader(bfttfStream))
using (MemoryStream ttfStream = new MemoryStream()) using (MemoryStream ttfStream = MemoryStreamManager.Shared.GetStream())
using (BinaryWriter output = new BinaryWriter(ttfStream)) using (BinaryWriter output = new BinaryWriter(ttfStream))
{ {
if (KXor(reader.ReadUInt32()) != BFTTFMagic) if (KXor(reader.ReadUInt32()) != BFTTFMagic)

View File

@@ -1,3 +1,5 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Ipc;
@@ -5,6 +7,7 @@ using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using System; using System;
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -19,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services
// not large enough. // not large enough.
private const int PointerBufferSize = 0x8000; private const int PointerBufferSize = 0x8000;
private readonly static int[] DefaultCapabilities = new int[] private readonly static uint[] DefaultCapabilities = new uint[]
{ {
0x030363F7, 0x030363F7,
0x1FFFFFCF, 0x1FFFFFCF,
@@ -37,14 +40,27 @@ namespace Ryujinx.HLE.HOS.Services
private readonly Dictionary<int, IpcService> _sessions = new Dictionary<int, IpcService>(); private readonly Dictionary<int, IpcService> _sessions = new Dictionary<int, IpcService>();
private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>(); private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>();
private readonly MemoryStream _requestDataStream;
private readonly BinaryReader _requestDataReader;
private readonly MemoryStream _responseDataStream;
private readonly BinaryWriter _responseDataWriter;
public ManualResetEvent InitDone { get; } public ManualResetEvent InitDone { get; }
public string Name { get; } public string Name { get; }
public Func<IpcService> SmObjectFactory { get; } public Func<IpcService> SmObjectFactory { get; }
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null) public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
{ {
InitDone = new ManualResetEvent(false);
_context = context; _context = context;
_requestDataStream = MemoryStreamManager.Shared.GetStream();
_requestDataReader = new BinaryReader(_requestDataStream);
_responseDataStream = MemoryStreamManager.Shared.GetStream();
_responseDataWriter = new BinaryWriter(_responseDataStream);
InitDone = new ManualResetEvent(false);
Name = name; Name = name;
SmObjectFactory = smObjectFactory; SmObjectFactory = smObjectFactory;
@@ -110,15 +126,15 @@ namespace Ryujinx.HLE.HOS.Services
while (true) while (true)
{ {
int[] portHandles = _portHandles.ToArray(); int handleCount = _portHandles.Count + _sessionHandles.Count;
int[] sessionHandles = _sessionHandles.ToArray();
int[] handles = new int[portHandles.Length + sessionHandles.Length];
portHandles.CopyTo(handles, 0); int[] handles = ArrayPool<int>.Shared.Rent(handleCount);
sessionHandles.CopyTo(handles, portHandles.Length);
_portHandles.CopyTo(handles, 0);
_sessionHandles.CopyTo(handles, _portHandles.Count);
// We still need a timeout here to allow the service to pick up and listen new sessions... // We still need a timeout here to allow the service to pick up and listen new sessions...
var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles, replyTargetHandle, 1000000L); var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L);
thread.HandlePostSyscall(); thread.HandlePostSyscall();
@@ -129,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Services
replyTargetHandle = 0; replyTargetHandle = 0;
if (rc == Result.Success && signaledIndex >= portHandles.Length) if (rc == Result.Success && signaledIndex >= _portHandles.Count)
{ {
// We got a IPC request, process it, pass to the appropriate service if needed. // We got a IPC request, process it, pass to the appropriate service if needed.
int signaledHandle = handles[signaledIndex]; int signaledHandle = handles[signaledIndex];
@@ -156,6 +172,8 @@ namespace Ryujinx.HLE.HOS.Services
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
} }
ArrayPool<int>.Shared.Return(handles);
} }
Dispose(); Dispose();
@@ -166,13 +184,9 @@ namespace Ryujinx.HLE.HOS.Services
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
KThread thread = KernelStatic.GetCurrentThread(); KThread thread = KernelStatic.GetCurrentThread();
ulong messagePtr = thread.TlsAddress; ulong messagePtr = thread.TlsAddress;
ulong messageSize = 0x100;
byte[] reqData = new byte[messageSize]; IpcMessage request = ReadRequest(process, messagePtr);
process.CpuMemory.Read(messagePtr, reqData);
IpcMessage request = new IpcMessage(reqData, (long)messagePtr);
IpcMessage response = new IpcMessage(); IpcMessage response = new IpcMessage();
ulong tempAddr = recvListAddr; ulong tempAddr = recvListAddr;
@@ -202,158 +216,157 @@ namespace Ryujinx.HLE.HOS.Services
bool shouldReply = true; bool shouldReply = true;
bool isTipcCommunication = false; bool isTipcCommunication = false;
using (MemoryStream raw = new MemoryStream(request.RawData)) _requestDataStream.SetLength(0);
_requestDataStream.Write(request.RawData);
_requestDataStream.Position = 0;
if (request.Type == IpcMessageType.HipcRequest ||
request.Type == IpcMessageType.HipcRequestWithContext)
{ {
BinaryReader reqReader = new BinaryReader(raw); response.Type = IpcMessageType.HipcResponse;
if (request.Type == IpcMessageType.HipcRequest || _responseDataStream.SetLength(0);
request.Type == IpcMessageType.HipcRequestWithContext)
{
response.Type = IpcMessageType.HipcResponse;
using (MemoryStream resMs = new MemoryStream()) ServiceCtx context = new ServiceCtx(
{ _context.Device,
BinaryWriter resWriter = new BinaryWriter(resMs); process,
process.CpuMemory,
thread,
request,
response,
_requestDataReader,
_responseDataWriter);
ServiceCtx context = new ServiceCtx( _sessions[serverSessionHandle].CallHipcMethod(context);
_context.Device,
process,
process.CpuMemory,
thread,
request,
response,
reqReader,
resWriter);
_sessions[serverSessionHandle].CallHipcMethod(context); response.RawData = _responseDataStream.ToArray();
response.RawData = resMs.ToArray();
}
}
else if (request.Type == IpcMessageType.HipcControl ||
request.Type == IpcMessageType.HipcControlWithContext)
{
uint magic = (uint)reqReader.ReadUInt64();
uint cmdId = (uint)reqReader.ReadUInt64();
switch (cmdId)
{
case 0:
request = FillResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain());
break;
case 3:
request = FillResponse(response, 0, PointerBufferSize);
break;
// TODO: Whats the difference between IpcDuplicateSession/Ex?
case 2:
case 4:
int unknown = reqReader.ReadInt32();
_context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0);
AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]);
response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle);
request = FillResponse(response, 0);
break;
default: throw new NotImplementedException(cmdId.ToString());
}
}
else if (request.Type == IpcMessageType.HipcCloseSession || request.Type == IpcMessageType.TipcCloseSession)
{
_context.Syscall.CloseHandle(serverSessionHandle);
_sessionHandles.Remove(serverSessionHandle);
IpcService service = _sessions[serverSessionHandle];
if (service is IDisposable disposableObj)
{
disposableObj.Dispose();
}
_sessions.Remove(serverSessionHandle);
shouldReply = false;
}
// If the type is past 0xF, we are using TIPC
else if (request.Type > IpcMessageType.TipcCloseSession)
{
isTipcCommunication = true;
// Response type is always the same as request on TIPC.
response.Type = request.Type;
using (MemoryStream resMs = new MemoryStream())
{
BinaryWriter resWriter = new BinaryWriter(resMs);
ServiceCtx context = new ServiceCtx(
_context.Device,
process,
process.CpuMemory,
thread,
request,
response,
reqReader,
resWriter);
_sessions[serverSessionHandle].CallTipcMethod(context);
response.RawData = resMs.ToArray();
}
process.CpuMemory.Write(messagePtr, response.GetBytesTipc());
}
else
{
throw new NotImplementedException(request.Type.ToString());
}
if (!isTipcCommunication)
{
process.CpuMemory.Write(messagePtr, response.GetBytes((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48)));
}
return shouldReply;
} }
else if (request.Type == IpcMessageType.HipcControl ||
request.Type == IpcMessageType.HipcControlWithContext)
{
uint magic = (uint)_requestDataReader.ReadUInt64();
uint cmdId = (uint)_requestDataReader.ReadUInt64();
switch (cmdId)
{
case 0:
FillHipcResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain());
break;
case 3:
FillHipcResponse(response, 0, PointerBufferSize);
break;
// TODO: Whats the difference between IpcDuplicateSession/Ex?
case 2:
case 4:
int unknown = _requestDataReader.ReadInt32();
_context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0);
AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]);
response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle);
FillHipcResponse(response, 0);
break;
default: throw new NotImplementedException(cmdId.ToString());
}
}
else if (request.Type == IpcMessageType.HipcCloseSession || request.Type == IpcMessageType.TipcCloseSession)
{
_context.Syscall.CloseHandle(serverSessionHandle);
_sessionHandles.Remove(serverSessionHandle);
IpcService service = _sessions[serverSessionHandle];
(service as IDisposable)?.Dispose();
_sessions.Remove(serverSessionHandle);
shouldReply = false;
}
// If the type is past 0xF, we are using TIPC
else if (request.Type > IpcMessageType.TipcCloseSession)
{
isTipcCommunication = true;
// Response type is always the same as request on TIPC.
response.Type = request.Type;
_responseDataStream.SetLength(0);
ServiceCtx context = new ServiceCtx(
_context.Device,
process,
process.CpuMemory,
thread,
request,
response,
_requestDataReader,
_responseDataWriter);
_sessions[serverSessionHandle].CallTipcMethod(context);
response.RawData = _responseDataStream.ToArray();
using var responseStream = response.GetStreamTipc();
process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence());
}
else
{
throw new NotImplementedException(request.Type.ToString());
}
if (!isTipcCommunication)
{
using var responseStream = response.GetStream((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48));
process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence());
}
return shouldReply;
} }
private static IpcMessage FillResponse(IpcMessage response, long result, params int[] values) private static IpcMessage ReadRequest(KProcess process, ulong messagePtr)
{ {
using (MemoryStream ms = new MemoryStream()) const int messageSize = 0x100;
{
BinaryWriter writer = new BinaryWriter(ms);
foreach (int value in values) byte[] reqData = ArrayPool<byte>.Shared.Rent(messageSize);
{
writer.Write(value);
}
return FillResponse(response, result, ms.ToArray()); Span<byte> reqDataSpan = reqData.AsSpan(0, messageSize);
} reqDataSpan.Clear();
process.CpuMemory.Read(messagePtr, reqDataSpan);
IpcMessage request = new IpcMessage(reqDataSpan, (long)messagePtr);
ArrayPool<byte>.Shared.Return(reqData);
return request;
} }
private static IpcMessage FillResponse(IpcMessage response, long result, byte[] data = null) private void FillHipcResponse(IpcMessage response, long result)
{
FillHipcResponse(response, result, ReadOnlySpan<byte>.Empty);
}
private void FillHipcResponse(IpcMessage response, long result, int value)
{
Span<byte> span = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(span, value);
FillHipcResponse(response, result, span);
}
private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan<byte> data)
{ {
response.Type = IpcMessageType.HipcResponse; response.Type = IpcMessageType.HipcResponse;
using (MemoryStream ms = new MemoryStream()) _responseDataStream.SetLength(0);
{
BinaryWriter writer = new BinaryWriter(ms);
writer.Write(IpcMagic.Sfco); _responseDataStream.Write(IpcMagic.Sfco);
writer.Write(result); _responseDataStream.Write(result);
if (data != null) _responseDataStream.Write(data);
{
writer.Write(data);
}
response.RawData = ms.ToArray(); response.RawData = _responseDataStream.ToArray();
}
return response;
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
@@ -372,6 +385,11 @@ namespace Ryujinx.HLE.HOS.Services
_sessions.Clear(); _sessions.Clear();
_requestDataReader.Dispose();
_requestDataStream.Dispose();
_responseDataWriter.Dispose();
_responseDataStream.Dispose();
InitDone.Dispose(); InitDone.Dispose();
} }
} }

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.HLE.Loaders.Executables
public uint DataSize { get; } public uint DataSize { get; }
public uint BssSize { get; } public uint BssSize { get; }
public int[] Capabilities { get; } public uint[] Capabilities { get; }
public bool UsesSecureMemory { get; } public bool UsesSecureMemory { get; }
public bool Is64BitAddressSpace { get; } public bool Is64BitAddressSpace { get; }
public bool Is64Bit { get; } public bool Is64Bit { get; }
@@ -57,11 +57,11 @@ namespace Ryujinx.HLE.Loaders.Executables
Version = reader.Version; Version = reader.Version;
Name = reader.Name.ToString(); Name = reader.Name.ToString();
Capabilities = new int[32]; Capabilities = new uint[32];
for (int index = 0; index < Capabilities.Length; index++) for (int index = 0; index < Capabilities.Length; index++)
{ {
Capabilities[index] = (int)reader.Capabilities[index]; Capabilities[index] = reader.Capabilities[index];
} }
reader.GetSegmentSize(KipReader.SegmentType.Data, out int uncompressedSize).ThrowIfFailure(); reader.GetSegmentSize(KipReader.SegmentType.Data, out int uncompressedSize).ThrowIfFailure();

View File

@@ -1,4 +1,6 @@
using LibHac.Common; using LibHac.Common;
using Microsoft.IO;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using System; using System;
using System.Globalization; using System.Globalization;
@@ -77,7 +79,7 @@ namespace Ryujinx.HLE.Utilities
ulong position = context.Request.PtrBuff[index].Position; ulong position = context.Request.PtrBuff[index].Position;
ulong size = context.Request.PtrBuff[index].Size; ulong size = context.Request.PtrBuff[index].Size;
using (MemoryStream ms = new MemoryStream()) using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
{ {
while (size-- > 0) while (size-- > 0)
{ {
@@ -91,7 +93,7 @@ namespace Ryujinx.HLE.Utilities
ms.WriteByte(value); ms.WriteByte(value);
} }
return Encoding.UTF8.GetString(ms.ToArray()); return Encoding.UTF8.GetString(ms.GetReadOnlySequence());
} }
} }
@@ -110,7 +112,7 @@ namespace Ryujinx.HLE.Utilities
ulong position = context.Request.SendBuff[index].Position; ulong position = context.Request.SendBuff[index].Position;
ulong size = context.Request.SendBuff[index].Size; ulong size = context.Request.SendBuff[index].Size;
using (MemoryStream ms = new MemoryStream()) using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
{ {
while (size-- > 0) while (size-- > 0)
{ {
@@ -124,7 +126,7 @@ namespace Ryujinx.HLE.Utilities
ms.WriteByte(value); ms.WriteByte(value);
} }
return Encoding.UTF8.GetString(ms.ToArray()); return Encoding.UTF8.GetString(ms.GetReadOnlySequence());
} }
} }

View File

@@ -3,6 +3,7 @@ using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.Input.Motion.CemuHook.Protocol; using Ryujinx.Input.Motion.CemuHook.Protocol;
using System; using System;
@@ -381,7 +382,7 @@ namespace Ryujinx.Input.Motion.CemuHook
Header header = GenerateHeader(clientId); Header header = GenerateHeader(clientId);
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
using (BinaryWriter writer = new BinaryWriter(stream)) using (BinaryWriter writer = new BinaryWriter(stream))
{ {
writer.WriteStruct(header); writer.WriteStruct(header);
@@ -421,7 +422,7 @@ namespace Ryujinx.Input.Motion.CemuHook
Header header = GenerateHeader(clientId); Header header = GenerateHeader(clientId);
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
using (BinaryWriter writer = new BinaryWriter(stream)) using (BinaryWriter writer = new BinaryWriter(stream))
{ {
writer.WriteStruct(header); writer.WriteStruct(header);

View File

@@ -1,5 +1,6 @@
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Memory namespace Ryujinx.Memory
@@ -77,6 +78,21 @@ namespace Ryujinx.Memory
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception> /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
void Write(ulong va, ReadOnlySpan<byte> data); void Write(ulong va, ReadOnlySpan<byte> data);
/// <summary>
/// Writes data to CPU mapped memory, with write tracking.
/// </summary>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
public void Write(ulong va, ReadOnlySequence<byte> data)
{
foreach (ReadOnlyMemory<byte> segment in data)
{
Write(va, segment.Span);
va += (ulong)segment.Length;
}
}
/// <summary> /// <summary>
/// Writes data to the application process, returning false if the data was not changed. /// Writes data to the application process, returning false if the data was not changed.
/// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date. /// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.

View File

@@ -8,6 +8,8 @@ namespace Ryujinx.Memory.Range
/// </summary> /// </summary>
public readonly struct MultiRange : IEquatable<MultiRange> public readonly struct MultiRange : IEquatable<MultiRange>
{ {
private const ulong InvalidAddress = ulong.MaxValue;
private readonly MemoryRange _singleRange; private readonly MemoryRange _singleRange;
private readonly MemoryRange[] _ranges; private readonly MemoryRange[] _ranges;
@@ -107,7 +109,16 @@ namespace Ryujinx.Memory.Range
else if (offset < range.Size) else if (offset < range.Size)
{ {
ulong sliceSize = Math.Min(size, range.Size - offset); ulong sliceSize = Math.Min(size, range.Size - offset);
ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
if (range.Address == InvalidAddress)
{
ranges.Add(new MemoryRange(range.Address, sliceSize));
}
else
{
ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
}
size -= sliceSize; size -= sliceSize;
} }

View File

@@ -6,6 +6,7 @@ using LibHac.FsSystem;
using LibHac.Ncm; using LibHac.Ncm;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
@@ -136,8 +137,8 @@ namespace Ryujinx.Ui.Windows
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
using (MemoryStream streamPng = new MemoryStream()) using (MemoryStream streamPng = MemoryStreamManager.Shared.GetStream())
{ {
file.Get.AsStream().CopyTo(stream); file.Get.AsStream().CopyTo(stream);
@@ -169,7 +170,7 @@ namespace Ryujinx.Ui.Windows
private byte[] ProcessImage(byte[] data) private byte[] ProcessImage(byte[] data)
{ {
using (MemoryStream streamJpg = new MemoryStream()) using (MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream())
{ {
Image avatarImage = Image.Load(data, new PngDecoder()); Image avatarImage = Image.Load(data, new PngDecoder());

View File

@@ -1,4 +1,5 @@
using Gtk; using Gtk;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
@@ -181,7 +182,7 @@ namespace Ryujinx.Ui.Windows
{ {
image.Mutate(x => x.Resize(256, 256)); image.Mutate(x => x.Resize(256, 256));
using (MemoryStream streamJpg = new MemoryStream()) using (MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream())
{ {
image.SaveAsJpeg(streamJpg); image.SaveAsJpeg(streamJpg);

View File

@@ -6,31 +6,31 @@ PUBLISH_DIRECTORY=$1
OUTPUT_DIRECTORY=$2 OUTPUT_DIRECTORY=$2
ENTITLEMENTS_FILE_PATH=$3 ENTITLEMENTS_FILE_PATH=$3
APP_BUNDLE_DIRECTORY=$OUTPUT_DIRECTORY/Ryujinx.app APP_BUNDLE_DIRECTORY="$OUTPUT_DIRECTORY/Ryujinx.app"
rm -rf $APP_BUNDLE_DIRECTORY rm -rf "$APP_BUNDLE_DIRECTORY"
mkdir -p $APP_BUNDLE_DIRECTORY/Contents mkdir -p "$APP_BUNDLE_DIRECTORY/Contents"
mkdir $APP_BUNDLE_DIRECTORY/Contents/Frameworks mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
mkdir $APP_BUNDLE_DIRECTORY/Contents/MacOS mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
mkdir $APP_BUNDLE_DIRECTORY/Contents/Resources mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
# Copy executables first # Copy executables first
cp $PUBLISH_DIRECTORY/Ryujinx.Ava $APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
chmod u+x $APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
# Then all libraries # Then all libraries
cp $PUBLISH_DIRECTORY/*.dylib $APP_BUNDLE_DIRECTORY/Contents/Frameworks cp "$PUBLISH_DIRECTORY"/*.dylib "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
# Then resources # Then resources
cp Info.plist $APP_BUNDLE_DIRECTORY/Contents cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents"
cp Ryujinx.icns $APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns"
cp updater.sh $APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh"
cp -r $PUBLISH_DIRECTORY/THIRDPARTY.md $APP_BUNDLE_DIRECTORY/Contents/Resources cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources"
echo -n "APPL????" > $APP_BUNDLE_DIRECTORY/Contents/PkgInfo echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo"
# Fixup libraries and executable # Fixup libraries and executable
python3 bundle_fix_up.py $APP_BUNDLE_DIRECTORY MacOS/Ryujinx python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx
# Now sign it # Now sign it
if ! [ -x "$(command -v codesign)" ]; if ! [ -x "$(command -v codesign)" ];
@@ -44,9 +44,9 @@ then
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
echo "Usign rcodesign for ad-hoc signing" echo "Usign rcodesign for ad-hoc signing"
rcodesign sign --entitlements-xml-path $ENTITLEMENTS_FILE_PATH $APP_BUNDLE_DIRECTORY rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
else else
echo "Usign codesign for ad-hoc signing" echo "Usign codesign for ad-hoc signing"
codesign --entitlements $ENTITLEMENTS_FILE_PATH -f --deep -s - $APP_BUNDLE_DIRECTORY codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$APP_BUNDLE_DIRECTORY"
fi fi

View File

@@ -7,54 +7,54 @@ if [ "$#" -ne 6 ]; then
exit 1 exit 1
fi fi
mkdir -p $1 mkdir -p "$1"
mkdir -p $2 mkdir -p "$2"
mkdir -p $3 mkdir -p "$3"
BASE_DIR=$(readlink -f $1) BASE_DIR=$(readlink -f "$1")
TEMP_DIRECTORY=$(readlink -f $2) TEMP_DIRECTORY=$(readlink -f "$2")
OUTPUT_DIRECTORY=$(readlink -f $3) OUTPUT_DIRECTORY=$(readlink -f "$3")
ENTITLEMENTS_FILE_PATH=$(readlink -f $4) ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
VERSION=$5 VERSION=$5
SOURCE_REVISION_ID=$6 SOURCE_REVISION_ID=$6
RELEASE_TAR_FILE_NAME=Ryujinx-$VERSION-macos_universal.app.tar RELEASE_TAR_FILE_NAME=Ryujinx-$VERSION-macos_universal.app.tar
ARM64_APP_BUNDLE=$TEMP_DIRECTORY/output_arm64/Ryujinx.app ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
X64_APP_BUNDLE=$TEMP_DIRECTORY/output_x64/Ryujinx.app X64_APP_BUNDLE="$TEMP_DIRECTORY/output_x64/Ryujinx.app"
UNIVERSAL_APP_BUNDLE=$OUTPUT_DIRECTORY/Ryujinx.app UNIVERSAL_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app"
EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
rm -rf $TEMP_DIRECTORY rm -rf "$TEMP_DIRECTORY"
mkdir -p $TEMP_DIRECTORY mkdir -p "$TEMP_DIRECTORY"
DOTNET_COMMON_ARGS="-p:DebugType=embedded -p:Version=$VERSION -p:SourceRevisionId=$SOURCE_REVISION_ID --self-contained true" DOTNET_COMMON_ARGS="-p:DebugType=embedded -p:Version=$VERSION -p:SourceRevisionId=$SOURCE_REVISION_ID --self-contained true"
dotnet restore dotnet restore
dotnet build -c Release Ryujinx.Ava dotnet build -c Release Ryujinx.Ava
dotnet publish -c Release -r osx-arm64 -o $TEMP_DIRECTORY/publish_arm64 $DOTNET_COMMON_ARGS Ryujinx.Ava dotnet publish -c Release -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" $DOTNET_COMMON_ARGS Ryujinx.Ava
dotnet publish -c Release -r osx-x64 -o $TEMP_DIRECTORY/publish_x64 $DOTNET_COMMON_ARGS Ryujinx.Ava dotnet publish -c Release -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" $DOTNET_COMMON_ARGS Ryujinx.Ava
# Get ride of the support library for ARMeilleur for x64 (that's only for arm64) # Get ride of the support library for ARMeilleur for x64 (that's only for arm64)
rm -rf $TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
# Get ride of libsoundio from arm64 builds as we don't have a arm64 variant # Get ride of libsoundio from arm64 builds as we don't have a arm64 variant
# TODO: remove this once done # TODO: remove this once done
rm -rf $TEMP_DIRECTORY/publish_arm64/libsoundio.dylib rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
pushd $BASE_DIR/distribution/macos pushd "$BASE_DIR/distribution/macos"
./create_app_bundle.sh $TEMP_DIRECTORY/publish_x64 $TEMP_DIRECTORY/output_x64 $ENTITLEMENTS_FILE_PATH ./create_app_bundle.sh "$TEMP_DIRECTORY/publish_x64" "$TEMP_DIRECTORY/output_x64" "$ENTITLEMENTS_FILE_PATH"
./create_app_bundle.sh $TEMP_DIRECTORY/publish_arm64 $TEMP_DIRECTORY/output_arm64 $ENTITLEMENTS_FILE_PATH ./create_app_bundle.sh "$TEMP_DIRECTORY/publish_arm64" "$TEMP_DIRECTORY/output_arm64" "$ENTITLEMENTS_FILE_PATH"
popd popd
rm -rf $UNIVERSAL_APP_BUNDLE rm -rf "$UNIVERSAL_APP_BUNDLE"
mkdir -p $OUTPUT_DIRECTORY mkdir -p "$OUTPUT_DIRECTORY"
# Let's copy one of the two different app bundle and remove the executable # Let's copy one of the two different app bundle and remove the executable
cp -R $ARM64_APP_BUNDLE $UNIVERSAL_APP_BUNDLE cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
rm $UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
# Make it libraries universal # Make it libraries universal
python3 $BASE_DIR/distribution/macos/construct_universal_dylib.py $ARM64_APP_BUNDLE $X64_APP_BUNDLE $UNIVERSAL_APP_BUNDLE "**/*.dylib" python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
if ! [ -x "$(command -v lipo)" ]; if ! [ -x "$(command -v lipo)" ];
then then
@@ -69,12 +69,12 @@ else
fi fi
# Make it the executable universal # Make it the executable universal
$LIPO $ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH $X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH -output $UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH -create $LIPO "$ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" "$X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -create
# Patch up the Info.plist to have appropriate version # Patch up the Info.plist to have appropriate version
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" $UNIVERSAL_APP_BUNDLE/Contents/Info.plist sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" $UNIVERSAL_APP_BUNDLE/Contents/Info.plist sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
rm $UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck rm "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck"
# Now sign it # Now sign it
if ! [ -x "$(command -v codesign)" ]; if ! [ -x "$(command -v codesign)" ];
@@ -88,16 +88,16 @@ then
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
echo "Usign rcodesign for ad-hoc signing" echo "Usign rcodesign for ad-hoc signing"
rcodesign sign --entitlements-xml-path $ENTITLEMENTS_FILE_PATH $UNIVERSAL_APP_BUNDLE rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
else else
echo "Usign codesign for ad-hoc signing" echo "Usign codesign for ad-hoc signing"
codesign --entitlements $ENTITLEMENTS_FILE_PATH -f --deep -s - $UNIVERSAL_APP_BUNDLE codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$UNIVERSAL_APP_BUNDLE"
fi fi
echo "Creating archive" echo "Creating archive"
pushd $OUTPUT_DIRECTORY pushd "$OUTPUT_DIRECTORY"
tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf $RELEASE_TAR_FILE_NAME Ryujinx.app 1> /dev/null tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf $RELEASE_TAR_FILE_NAME Ryujinx.app 1> /dev/null
python3 $BASE_DIR/distribution/misc/add_tar_exec.py $RELEASE_TAR_FILE_NAME "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx" python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" $RELEASE_TAR_FILE_NAME "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
gzip -9 < $RELEASE_TAR_FILE_NAME > $RELEASE_TAR_FILE_NAME.gz gzip -9 < $RELEASE_TAR_FILE_NAME > $RELEASE_TAR_FILE_NAME.gz
rm $RELEASE_TAR_FILE_NAME rm $RELEASE_TAR_FILE_NAME
popd popd