Compare commits

...

5 Commits

Author SHA1 Message Date
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
MutantAura
eed17f963e Increase access permissions for Ava timezones (#4538) 2023-03-12 17:20:09 +01:00
TSRBerry
c09c0c002d [Flatpak] Beautify multiline strings again & Add full git commit hash (#4535)
* Don't destroy multiline strings

* Use full git commit hash
2023-03-12 10:42:33 +01:00
22 changed files with 690 additions and 481 deletions

View File

@@ -35,7 +35,7 @@ jobs:
id: version_info id: version_info
working-directory: Ryujinx working-directory: Ryujinx
run: | run: |
echo "git_short_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@@ -84,7 +84,7 @@ jobs:
- name: Update flatpak metadata - name: Update flatpak metadata
id: metadata id: metadata
env: env:
RYUJINX_GIT_HASH: ${{ steps.version_info.outputs.git_short_hash }} RYUJINX_GIT_HASH: ${{ steps.version_info.outputs.git_hash }}
shell: python shell: python
run: | run: |
import hashlib import hashlib
@@ -95,6 +95,16 @@ jobs:
from datetime import datetime from datetime import datetime
from lxml import etree from lxml import etree
# Ensure we don't destroy multiline strings
def str_presenter(dumper, data):
if len(data.splitlines()) > 1:
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
yaml_file = "flathub/org.ryujinx.Ryujinx.yml" yaml_file = "flathub/org.ryujinx.Ryujinx.yml"
xml_file = "flathub/org.ryujinx.Ryujinx.appdata.xml" xml_file = "flathub/org.ryujinx.Ryujinx.appdata.xml"

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

@@ -236,7 +236,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public DateTimeOffset DateOffset { get; set; } public DateTimeOffset DateOffset { get; set; }
public TimeSpan TimeOffset { get; set; } public TimeSpan TimeOffset { get; set; }
private AvaloniaList<TimeZone> TimeZones { get; set; } internal AvaloniaList<TimeZone> TimeZones { get; set; }
public AvaloniaList<string> GameDirectories { get; set; } public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; } public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }

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>
<Panel
Margin="0 0 0 10"
Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Name="Heading" Grid.Column="0"
Grid.Row="1" Text="{Binding UpdateCount}" />
MaxWidth="500" <StackPanel
Margin="20,15,20,20" Margin="10 0"
HorizontalAlignment="Center" Grid.Column="1"
VerticalAlignment="Center" Orientation="Horizontal">
LineHeight="18"
TextAlignment="Center"
TextWrapping="Wrap" />
<DockPanel
Grid.Row="2"
Margin="0"
HorizontalAlignment="Left">
<Button <Button
Name="EnableAllButton" Name="EnableAllButton"
MinWidth="90" MinWidth="90"
Margin="5" Margin="5"
Command="{Binding EnableAll}"> Command="{ReflectionBinding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" /> <TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button> </Button>
<Button <Button
Name="DisableAllButton" Name="DisableAllButton"
MinWidth="90" MinWidth="90"
Margin="5" Margin="5"
Command="{Binding DisableAll}"> Command="{ReflectionBinding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" /> <TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button> </Button>
</DockPanel> </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"
VerticalAlignment="Center"
Text="{Binding TitleId}" />
</Grid>
<StackPanel
Grid.Column="1"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button
VerticalAlignment="Center"
HorizontalAlignment="Right"
Padding="10"
MinWidth="0"
MinHeight="0"
Click="OpenLocation">
<ui:SymbolIcon
Symbol="OpenFolder"
HorizontalAlignment="Center" HorizontalAlignment="Center"
IsChecked="{Binding Enabled}" /> VerticalAlignment="Center" />
</Button>
<Button
VerticalAlignment="Center"
HorizontalAlignment="Right"
Padding="10"
MinWidth="0"
MinHeight="0"
Click="RemoveDLC">
<ui:SymbolIcon
Symbol="Cancel"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
</StackPanel>
</Grid>
</Panel>
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </ListBox.DataTemplates>
<DataGridTemplateColumn.Header> <ListBox.Styles>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" /> <Style Selector="ListBoxItem">
</DataGridTemplateColumn.Header> <Setter Property="Background" Value="Transparent" />
</DataGridTemplateColumn> </Style>
<DataGridTextColumn Width="140" Binding="{Binding TitleId}"> </ListBox.Styles>
<DataGridTextColumn.Header> </ListBox>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding ContainerPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</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")); PrimaryButtonText = "",
} SecondaryButtonText = "",
CloseButtonText = "",
private void LoadDownloadableContents() Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId, titleName),
{ Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16"))
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)
{
_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);
}
private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs)
{ {
foreach (string file in files) ViewModel.Save();
((ContentDialog)Parent).Hide();
}
private void Close(object sender, RoutedEventArgs e)
{ {
await AddDownloadableContent(file); ((ContentDialog)Parent).Hide();
}
private void RemoveDLC(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
if (button.DataContext is DownloadableContentModel model)
{
ViewModel.Remove(model);
}
} }
} }
PrintHeading(); private void OpenLocation(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
if (button.DataContext is DownloadableContentModel model)
{
OpenHelper.LocateFile(model.ContainerPath);
} }
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)));
} }
} }
public void SaveAndClose() private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
Save(); foreach (var content in e.AddedItems)
Close(); {
if (content is DownloadableContentModel model)
{
var index = ViewModel.DownloadableContents.IndexOf(model);
if (index != -1)
{
ViewModel.DownloadableContents[index].Enabled = true;
}
}
}
foreach (var content in e.RemovedItems)
{
if (content is DownloadableContentModel model)
{
var index = ViewModel.DownloadableContents.IndexOf(model);
if (index != -1)
{
ViewModel.DownloadableContents[index].Enabled = false;
}
}
}
} }
} }
} }

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

@@ -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

@@ -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

@@ -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