mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 23:19:44 +08:00
Sdl backend (#670)
* [audio] added sdl audio backend and in-tree atrac9 decoder * [input] replaced per-platform pad readers with sdl gamepad input * [video] added sdl window and host display plumbing * [gui] added host display options and per-game render settings * [bink] synced host movie playback to the guest audio clock * [cpu] hooked windows write faults into guest image tracking * [perf] added guest and render profiling, reserved host cpu lanes * [kernel] fixed stale pthread mutex handle alias * [host] wired the sdl session, save-data paths and project references * [audio] hoisted ajm trace stackalloc out of its loop * [video] Add guest image sync setting * [build] Strip native symbols * reuse
This commit is contained in:
@@ -5,6 +5,7 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using SharpEmu.Libs.VideoOut;
|
||||
|
||||
namespace SharpEmu.GUI;
|
||||
|
||||
@@ -12,6 +13,9 @@ public sealed class PerGameSettingsDialog : Window
|
||||
{
|
||||
private static readonly string[] LogLevels =
|
||||
{ "Trace", "Debug", "Info", "Warning", "Error", "Critical" };
|
||||
private static readonly string[] WindowModes = { "Windowed", "Borderless", "Exclusive" };
|
||||
private static readonly string[] ScalingModes = { "Fit", "Cover", "Stretch", "Integer" };
|
||||
private static readonly string[] HdrModes = { "Auto", "On", "Off" };
|
||||
|
||||
private static readonly string[] EnvToggles =
|
||||
{
|
||||
@@ -23,9 +27,12 @@ public sealed class PerGameSettingsDialog : Window
|
||||
"SHARPEMU_LOG_DIRECT_MEMORY",
|
||||
"SHARPEMU_LOG_IO",
|
||||
"SHARPEMU_LOG_NP",
|
||||
"SHARPEMU_GUEST_IMAGE_CPU_SYNC",
|
||||
};
|
||||
|
||||
private readonly string _titleId;
|
||||
private IReadOnlyList<HostDisplayOption> _hostDisplays = [];
|
||||
private bool _updatingHostDisplayOptions;
|
||||
|
||||
private readonly SettingRow _logLevelRow;
|
||||
private readonly ComboBox _logLevel = new() { ItemsSource = LogLevels, Width = 160 };
|
||||
@@ -42,6 +49,27 @@ public sealed class PerGameSettingsDialog : Window
|
||||
private readonly SettingRow _logToFileRow;
|
||||
private readonly ToggleSwitch _logToFile = new();
|
||||
|
||||
private readonly SettingRow _windowModeRow;
|
||||
private readonly ComboBox _windowMode = new() { ItemsSource = WindowModes, Width = 160 };
|
||||
|
||||
private readonly SettingRow _resolutionRow;
|
||||
private readonly ComboBox _resolution = new() { Width = 160 };
|
||||
|
||||
private readonly SettingRow _displayIndexRow;
|
||||
private readonly ComboBox _displayIndex = new() { Width = 240 };
|
||||
|
||||
private readonly SettingRow _refreshRateRow;
|
||||
private readonly ComboBox _refreshRate = new() { Width = 160 };
|
||||
|
||||
private readonly SettingRow _scalingModeRow;
|
||||
private readonly ComboBox _scalingMode = new() { ItemsSource = ScalingModes, Width = 160 };
|
||||
|
||||
private readonly SettingRow _vsyncRow;
|
||||
private readonly ToggleSwitch _vsync = new();
|
||||
|
||||
private readonly SettingRow _hdrModeRow;
|
||||
private readonly ComboBox _hdrMode = new() { ItemsSource = HdrModes, Width = 160 };
|
||||
|
||||
private readonly SettingRow _envRow;
|
||||
private readonly StackPanel _envList = new() { Orientation = Orientation.Vertical, Spacing = 8, Margin = new(0, 4, 0, 0) };
|
||||
private readonly List<(string Name, ToggleSwitch Box)> _envBoxes = new();
|
||||
@@ -60,13 +88,20 @@ public sealed class PerGameSettingsDialog : Window
|
||||
|
||||
Background = new SolidColorBrush(Color.Parse("#0D1017"));
|
||||
|
||||
_strict.OnContent = _logToFile.OnContent = loc.Get("Common.On");
|
||||
_strict.OffContent = _logToFile.OffContent = loc.Get("Common.Off");
|
||||
_strict.OnContent = _logToFile.OnContent = _vsync.OnContent = loc.Get("Common.On");
|
||||
_strict.OffContent = _logToFile.OffContent = _vsync.OffContent = loc.Get("Common.Off");
|
||||
|
||||
_logLevelRow = Row(loc.Get("Options.LogLevel.Label"), loc.Get("Options.LogLevel.Desc"), _logLevel);
|
||||
_traceRow = Row(loc.Get("Options.TraceImports.Label"), loc.Get("Options.TraceImports.Desc"), _trace);
|
||||
_strictRow = Row(loc.Get("Options.Strict.Label"), loc.Get("Options.Strict.Desc"), _strict);
|
||||
_logToFileRow = Row(loc.Get("Options.LogToFile.Label"), loc.Get("Options.LogToFile.Desc"), _logToFile);
|
||||
_windowModeRow = Row(loc.Get("Options.WindowMode.Label"), loc.Get("Options.WindowMode.Desc"), _windowMode);
|
||||
_resolutionRow = Row(loc.Get("Options.Resolution.Label"), loc.Get("Options.Resolution.Desc"), _resolution);
|
||||
_displayIndexRow = Row(loc.Get("Options.Display.Label"), loc.Get("Options.Display.Desc"), _displayIndex);
|
||||
_refreshRateRow = Row(loc.Get("Options.RefreshRate.Label"), loc.Get("Options.RefreshRate.Desc"), _refreshRate);
|
||||
_scalingModeRow = Row(loc.Get("Options.Scaling.Label"), loc.Get("Options.Scaling.Desc"), _scalingMode);
|
||||
_vsyncRow = Row(loc.Get("Options.VSync.Label"), loc.Get("Options.VSync.Desc"), _vsync);
|
||||
_hdrModeRow = Row(loc.Get("Options.Hdr.Label"), loc.Get("Options.Hdr.Desc"), _hdrMode);
|
||||
_envRow = new SettingRow
|
||||
{
|
||||
Label = loc.Get("PerGame.EnvToggles.Label"),
|
||||
@@ -81,6 +116,22 @@ public sealed class PerGameSettingsDialog : Window
|
||||
_envList.Children.Add(box);
|
||||
}
|
||||
|
||||
var general = new StackPanel { Orientation = Orientation.Vertical, Spacing = 12, Margin = new(0, 12, 0, 0) };
|
||||
general.Children.Add(Card(loc.Get("Options.Section.Emulation"), _strictRow));
|
||||
general.Children.Add(Card(loc.Get("Options.Section.Logging"), _logLevelRow, _traceRow, _logToFileRow));
|
||||
general.Children.Add(Card(loc.Get("Options.Section.Environment"), _envRow, _envList));
|
||||
|
||||
var graphics = new StackPanel { Orientation = Orientation.Vertical, Spacing = 12, Margin = new(0, 12, 0, 0) };
|
||||
graphics.Children.Add(Card(
|
||||
loc.Get("Options.Section.Display"),
|
||||
_windowModeRow,
|
||||
_resolutionRow,
|
||||
_displayIndexRow,
|
||||
_refreshRateRow,
|
||||
_scalingModeRow,
|
||||
_vsyncRow,
|
||||
_hdrModeRow));
|
||||
|
||||
var content = new StackPanel { Orientation = Orientation.Vertical, Spacing = 12, Margin = new(16) };
|
||||
content.Children.Add(new TextBlock
|
||||
{
|
||||
@@ -88,9 +139,14 @@ public sealed class PerGameSettingsDialog : Window
|
||||
Foreground = new SolidColorBrush(Color.Parse("#8B94A7")),
|
||||
FontSize = 12,
|
||||
});
|
||||
content.Children.Add(Card(loc.Get("Options.Section.Emulation"), _strictRow));
|
||||
content.Children.Add(Card(loc.Get("Options.Section.Logging"), _logLevelRow, _traceRow, _logToFileRow));
|
||||
content.Children.Add(Card(loc.Get("Options.Section.Environment"), _envRow, _envList));
|
||||
content.Children.Add(new TabControl
|
||||
{
|
||||
ItemsSource = new[]
|
||||
{
|
||||
new TabItem { Header = loc.Get("PerGame.Tab.General"), Content = general },
|
||||
new TabItem { Header = loc.Get("PerGame.Tab.Graphics"), Content = graphics },
|
||||
},
|
||||
});
|
||||
|
||||
var save = new Button { Content = loc.Get("Common.Save"), Classes = { "accent" } };
|
||||
var cancel = new Button { Content = loc.Get("Common.Cancel"), Classes = { "ghost" } };
|
||||
@@ -119,6 +175,8 @@ public sealed class PerGameSettingsDialog : Window
|
||||
root.Children.Add(buttonBar);
|
||||
Content = root;
|
||||
|
||||
_displayIndex.SelectionChanged += (_, _) => OnHostDisplayChanged();
|
||||
_resolution.SelectionChanged += (_, _) => OnHostResolutionChanged();
|
||||
LoadValues(global);
|
||||
_envRow.PropertyChanged += (_, e) =>
|
||||
{
|
||||
@@ -154,16 +212,38 @@ public sealed class PerGameSettingsDialog : Window
|
||||
|
||||
private void LoadValues(GuiSettings global)
|
||||
{
|
||||
var existing = PerGameSettings.Load(_titleId);
|
||||
var displayIndex = Math.Max(0, existing?.DisplayIndex ?? global.DisplayIndex);
|
||||
var resolution = existing?.Resolution ?? global.Resolution;
|
||||
var refreshRate = Math.Clamp(existing?.RefreshRate ?? global.RefreshRate, 0, 1000);
|
||||
|
||||
_updatingHostDisplayOptions = true;
|
||||
try
|
||||
{
|
||||
_hostDisplays = HostDisplayOptions.BuildDisplays(HostDisplayCatalog.Query(), displayIndex);
|
||||
_displayIndex.ItemsSource = _hostDisplays;
|
||||
var display = HostDisplayOptions.SelectDisplay(_hostDisplays, displayIndex);
|
||||
_displayIndex.SelectedItem = display;
|
||||
PopulateHostModes(display, resolution, refreshRate);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updatingHostDisplayOptions = false;
|
||||
}
|
||||
|
||||
_logLevel.SelectedItem = Array.IndexOf(LogLevels, global.LogLevel) >= 0 ? global.LogLevel : "Info";
|
||||
_trace.Value = global.ImportTraceLimit;
|
||||
_strict.IsChecked = global.StrictDynlibResolution;
|
||||
_logToFile.IsChecked = global.LogToFile;
|
||||
_windowMode.SelectedItem = ChoiceOrDefault(WindowModes, global.WindowMode, "Windowed");
|
||||
_scalingMode.SelectedItem = ChoiceOrDefault(ScalingModes, global.ScalingMode, "Fit");
|
||||
_vsync.IsChecked = global.VSync;
|
||||
_hdrMode.SelectedItem = ChoiceOrDefault(HdrModes, global.HdrMode, "Auto");
|
||||
foreach (var (name, box) in _envBoxes)
|
||||
{
|
||||
box.IsChecked = global.EnvironmentToggles.Contains(name);
|
||||
box.IsChecked = IsEnvironmentEnabled(global.EnvironmentToggles, name, defaultValue: name == "SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
||||
}
|
||||
|
||||
var existing = PerGameSettings.Load(_titleId);
|
||||
if (existing is null)
|
||||
{
|
||||
return;
|
||||
@@ -178,16 +258,120 @@ public sealed class PerGameSettingsDialog : Window
|
||||
if (existing.ImportTraceLimit is { } t) { _traceRow.IsOverridden = true; _trace.Value = t; }
|
||||
if (existing.StrictDynlibResolution is { } s) { _strictRow.IsOverridden = true; _strict.IsChecked = s; }
|
||||
if (existing.LogToFile is { } l) { _logToFileRow.IsOverridden = true; _logToFile.IsChecked = l; }
|
||||
if (existing.WindowMode is { } windowMode && WindowModes.Contains(windowMode, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
_windowModeRow.IsOverridden = true;
|
||||
_windowMode.SelectedItem = ChoiceOrDefault(WindowModes, windowMode, "Windowed");
|
||||
}
|
||||
if (existing.Resolution is not null)
|
||||
{
|
||||
_resolutionRow.IsOverridden = true;
|
||||
}
|
||||
if (existing.DisplayIndex is not null)
|
||||
{
|
||||
_displayIndexRow.IsOverridden = true;
|
||||
}
|
||||
if (existing.RefreshRate is not null)
|
||||
{
|
||||
_refreshRateRow.IsOverridden = true;
|
||||
}
|
||||
if (existing.ScalingMode is { } scalingMode && ScalingModes.Contains(scalingMode, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
_scalingModeRow.IsOverridden = true;
|
||||
_scalingMode.SelectedItem = ChoiceOrDefault(ScalingModes, scalingMode, "Fit");
|
||||
}
|
||||
if (existing.VSync is { } vsync)
|
||||
{
|
||||
_vsyncRow.IsOverridden = true;
|
||||
_vsync.IsChecked = vsync;
|
||||
}
|
||||
if (existing.HdrMode is { } hdrMode && HdrModes.Contains(hdrMode, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
_hdrModeRow.IsOverridden = true;
|
||||
_hdrMode.SelectedItem = ChoiceOrDefault(HdrModes, hdrMode, "Auto");
|
||||
}
|
||||
if (existing.EnvironmentToggles is { } env)
|
||||
{
|
||||
_envRow.IsOverridden = true;
|
||||
foreach (var (name, box) in _envBoxes)
|
||||
{
|
||||
box.IsChecked = env.Contains(name);
|
||||
box.IsChecked = IsEnvironmentEnabled(env, name, defaultValue: name == "SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ChoiceOrDefault(string[] choices, string? value, string fallback) =>
|
||||
choices.FirstOrDefault(choice => string.Equals(choice, value, StringComparison.OrdinalIgnoreCase)) ?? fallback;
|
||||
|
||||
private void OnHostDisplayChanged()
|
||||
{
|
||||
if (_updatingHostDisplayOptions || _displayIndex.SelectedItem is not HostDisplayOption display)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_updatingHostDisplayOptions = true;
|
||||
try
|
||||
{
|
||||
PopulateHostModes(
|
||||
display,
|
||||
_resolution.SelectedItem as string ?? "1920x1080",
|
||||
SelectedRefreshRate());
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updatingHostDisplayOptions = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHostResolutionChanged()
|
||||
{
|
||||
if (_updatingHostDisplayOptions || _displayIndex.SelectedItem is not HostDisplayOption display)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedRefreshRate = SelectedRefreshRate();
|
||||
_updatingHostDisplayOptions = true;
|
||||
try
|
||||
{
|
||||
PopulateRefreshRates(display, _resolution.SelectedItem as string, selectedRefreshRate);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updatingHostDisplayOptions = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateHostModes(
|
||||
HostDisplayOption display,
|
||||
string selectedResolution,
|
||||
int selectedRefreshRate)
|
||||
{
|
||||
var resolutions = HostDisplayOptions.BuildResolutions(display, selectedResolution);
|
||||
_resolution.ItemsSource = resolutions;
|
||||
_resolution.SelectedItem = resolutions.FirstOrDefault(resolution =>
|
||||
string.Equals(resolution, selectedResolution, StringComparison.OrdinalIgnoreCase)) ?? resolutions[0];
|
||||
PopulateRefreshRates(display, _resolution.SelectedItem as string, selectedRefreshRate);
|
||||
}
|
||||
|
||||
private void PopulateRefreshRates(
|
||||
HostDisplayOption display,
|
||||
string? resolution,
|
||||
int selectedRefreshRate)
|
||||
{
|
||||
var rates = HostDisplayOptions.BuildRefreshRates(
|
||||
display,
|
||||
resolution,
|
||||
selectedRefreshRate,
|
||||
Localization.Instance.Get("Options.RefreshRate.Automatic"));
|
||||
_refreshRate.ItemsSource = rates;
|
||||
_refreshRate.SelectedItem = rates.FirstOrDefault(rate => rate.Value == selectedRefreshRate) ?? rates[0];
|
||||
}
|
||||
|
||||
private int SelectedRefreshRate() =>
|
||||
_refreshRate.SelectedItem is HostRefreshRateOption refreshRate ? refreshRate.Value : 0;
|
||||
|
||||
private void Persist()
|
||||
{
|
||||
var settings = new PerGameSettings
|
||||
@@ -196,10 +380,54 @@ public sealed class PerGameSettingsDialog : Window
|
||||
ImportTraceLimit = _traceRow.IsOverridden ? (int)(_trace.Value ?? 0) : null,
|
||||
StrictDynlibResolution = _strictRow.IsOverridden ? _strict.IsChecked == true : null,
|
||||
LogToFile = _logToFileRow.IsOverridden ? _logToFile.IsChecked == true : null,
|
||||
EnvironmentToggles = _envRow.IsOverridden
|
||||
? _envBoxes.Where(e => e.Box.IsChecked == true).Select(e => e.Name).ToList()
|
||||
WindowMode = _windowModeRow.IsOverridden ? _windowMode.SelectedItem as string : null,
|
||||
Resolution = _resolutionRow.IsOverridden ? _resolution.SelectedItem as string : null,
|
||||
DisplayIndex = _displayIndexRow.IsOverridden && _displayIndex.SelectedItem is HostDisplayOption display
|
||||
? display.Index
|
||||
: null,
|
||||
RefreshRate = _refreshRateRow.IsOverridden ? SelectedRefreshRate() : null,
|
||||
ScalingMode = _scalingModeRow.IsOverridden ? _scalingMode.SelectedItem as string : null,
|
||||
VSync = _vsyncRow.IsOverridden ? _vsync.IsChecked == true : null,
|
||||
HdrMode = _hdrModeRow.IsOverridden ? _hdrMode.SelectedItem as string : null,
|
||||
EnvironmentToggles = _envRow.IsOverridden ? BuildEnvironmentEntries() : null,
|
||||
};
|
||||
settings.Save(_titleId);
|
||||
}
|
||||
|
||||
private List<string> BuildEnvironmentEntries()
|
||||
{
|
||||
const string guestImageCpuSync = "SHARPEMU_GUEST_IMAGE_CPU_SYNC";
|
||||
var entries = _envBoxes
|
||||
.Where(entry => entry.Name != guestImageCpuSync && entry.Box.IsChecked == true)
|
||||
.Select(entry => entry.Name)
|
||||
.ToList();
|
||||
if (_envBoxes.First(entry => entry.Name == guestImageCpuSync).Box.IsChecked != true)
|
||||
{
|
||||
entries.Add(guestImageCpuSync + "=0");
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static bool IsEnvironmentEnabled(
|
||||
IEnumerable<string> entries,
|
||||
string name,
|
||||
bool defaultValue)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (string.Equals(entry, name + "=0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.Equals(entry, name, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(entry, name + "=1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user