mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-29 22:19:39 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26cf72189c |
@@ -45,9 +45,6 @@ public sealed class GuiSettings
|
||||
/// <summary>UI language, matching a file code under Languages/ (e.g. "en", "tr").</summary>
|
||||
public string Language { get; set; } = "en";
|
||||
|
||||
/// <summary>Default text-entry profile exposed to games.</summary>
|
||||
public string DefaultProfile { get; set; } = "Sharp";
|
||||
|
||||
/// <summary>Publish launcher/game status to Discord Rich Presence.</summary>
|
||||
public bool DiscordRichPresence { get; set; } = true;
|
||||
|
||||
@@ -115,18 +112,6 @@ public sealed class GuiSettings
|
||||
settings.EnvironmentToggles = FilterNullOrEmpty(settings.EnvironmentToggles);
|
||||
settings.LogLevel ??= "Info";
|
||||
settings.Language ??= "en";
|
||||
var legacyProfile = settings.EnvironmentToggles
|
||||
.Select(entry => entry.Split('=', 2, StringSplitOptions.TrimEntries))
|
||||
.FirstOrDefault(parts =>
|
||||
parts.Length == 2 &&
|
||||
string.Equals(parts[0], "SHARPEMU_DEFAULT_PROFILE", StringComparison.OrdinalIgnoreCase));
|
||||
settings.EnvironmentToggles.RemoveAll(entry =>
|
||||
string.Equals(
|
||||
entry.Split('=', 2, StringSplitOptions.TrimEntries)[0],
|
||||
"SHARPEMU_DEFAULT_PROFILE",
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
settings.DefaultProfile = NormalizeDefaultProfile(
|
||||
legacyProfile is { Length: 2 } ? legacyProfile[1] : settings.DefaultProfile);
|
||||
settings.DiscordClientId ??= "1525606762248540221";
|
||||
if (settings.RenderResolutionScale <= 0 || settings.RenderResolutionScale > 2.0)
|
||||
{
|
||||
@@ -167,12 +152,6 @@ public sealed class GuiSettings
|
||||
return $"{width}x{height}";
|
||||
}
|
||||
|
||||
internal static string NormalizeDefaultProfile(string? value)
|
||||
{
|
||||
var trimmed = value?.Trim();
|
||||
return string.IsNullOrEmpty(trimmed) ? "Sharp" : trimmed;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
|
||||
"Options.Language.Label": "لغة المحاكي",
|
||||
"Options.Language.Desc": "اللغة المستخدمة في جميع أنحاء المشغل. تُطبق فوراً.",
|
||||
"Options.DefaultProfile.Label": "اسم الملف الشخصي الافتراضي",
|
||||
"Options.DefaultProfile.Desc": "الاسم المستخدم عندما تطلب لعبة إدخال نص. القيمة الافتراضية هي Sharp.",
|
||||
|
||||
"Common.On": "تشغيل",
|
||||
"Common.Off": "إيقاف",
|
||||
|
||||
@@ -76,8 +76,6 @@
|
||||
|
||||
"Options.Language.Label": "Idioma do emulador",
|
||||
"Options.Language.Desc": "Idioma usado em toda a interface do emulador. A alteração é aplicada imediatamente.",
|
||||
"Options.DefaultProfile.Label": "Nome de perfil padrão",
|
||||
"Options.DefaultProfile.Desc": "Nome usado quando um jogo solicita entrada de texto. O padrão é Sharp.",
|
||||
|
||||
"Common.On": "Ativado",
|
||||
"Common.Off": "Desativado",
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
|
||||
"Options.Language.Label": "Emulator-Sprache",
|
||||
"Options.Language.Desc": "Sprache der Benutzeroberfläche. Wird sofort angewendet.",
|
||||
"Options.DefaultProfile.Label": "Standardprofilname",
|
||||
"Options.DefaultProfile.Desc": "Name für Texteingaben in Spielen. Der Standardwert ist Sharp.",
|
||||
|
||||
"Common.On": "An",
|
||||
"Common.Off": "Aus",
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
|
||||
"Options.Language.Label": "Emulatorsprog",
|
||||
"Options.Language.Desc": "Sprog der bruges i hele launcheren. Anvendes med det samme.",
|
||||
"Options.DefaultProfile.Label": "Standardprofilnavn",
|
||||
"Options.DefaultProfile.Desc": "Navn der bruges, når et spil beder om tekstinput. Standardværdien er Sharp.",
|
||||
|
||||
"Common.On": "Til",
|
||||
"Common.Off": "Fra",
|
||||
|
||||
@@ -38,8 +38,6 @@
|
||||
"Options.Env.LogIo.Desc": "Log file open, read, and path-resolve activity to the console.\nUse when a game cannot find its data files during boot.",
|
||||
"Options.Env.LogNp.Desc": "Log NP (PlayStation Network) library calls to the console.",
|
||||
"Options.Env.GuestImageCpuSync.Desc": "Re-upload guest surfaces the game's own CPU code rewrites.\nLeave off normally. Turn on for titles whose CPU-drawn surfaces never reach the screen.\nCosts performance and regresses some titles, such as GTA V.",
|
||||
"Options.DefaultProfile.Label": "Default profile name",
|
||||
"Options.DefaultProfile.Desc": "Name used when a game asks for text input. Defaults to Sharp.",
|
||||
"Options.Section.Emulation": "EMULATION",
|
||||
"Options.Section.Logging": "LOGGING",
|
||||
"Options.Section.Launcher": "LAUNCHER",
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
|
||||
"Options.Language.Label": "Idioma del emulador",
|
||||
"Options.Language.Desc": "Idioma utilizado en todo el launcher. Se aplica inmediatamente.",
|
||||
"Options.DefaultProfile.Label": "Nombre de perfil predeterminado",
|
||||
"Options.DefaultProfile.Desc": "Nombre utilizado cuando un juego solicita introducir texto. El valor predeterminado es Sharp.",
|
||||
|
||||
"Common.On": "Encendido",
|
||||
"Common.Off": "Apagado",
|
||||
|
||||
@@ -76,8 +76,6 @@
|
||||
|
||||
"Options.Language.Label": "Langue de l’émulateur",
|
||||
"Options.Language.Desc": "Langue utilisée dans l’ensemble du lanceur. Le changement est immédiat.",
|
||||
"Options.DefaultProfile.Label": "Nom de profil par défaut",
|
||||
"Options.DefaultProfile.Desc": "Nom utilisé lorsqu’un jeu demande une saisie de texte. La valeur par défaut est Sharp.",
|
||||
|
||||
"Common.On": "Activé",
|
||||
"Common.Off": "Désactivé",
|
||||
|
||||
@@ -76,8 +76,6 @@
|
||||
|
||||
"Options.Language.Label": "Emulátor nyelve",
|
||||
"Options.Language.Desc": "Az indítóban használt nyelv. Azonnal érvénybe lép.",
|
||||
"Options.DefaultProfile.Label": "Alapértelmezett profilnév",
|
||||
"Options.DefaultProfile.Desc": "A játékok szövegbeviteli kéréseinél használt név. Az alapértelmezett érték Sharp.",
|
||||
|
||||
"Common.On": "Be",
|
||||
"Common.Off": "Ki",
|
||||
|
||||
@@ -69,8 +69,6 @@
|
||||
|
||||
"Options.Language.Label": "Lingua dell'emulatore",
|
||||
"Options.Language.Desc": "Lingua utilizzata in tutto il launcher. Viene applicata immediatamente.",
|
||||
"Options.DefaultProfile.Label": "Nome profilo predefinito",
|
||||
"Options.DefaultProfile.Desc": "Nome usato quando un gioco richiede l'inserimento di testo. Il valore predefinito è Sharp.",
|
||||
|
||||
"Common.On": "On",
|
||||
"Common.Off": "Off",
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
|
||||
"Options.Language.Label": "エミュレータの言語",
|
||||
"Options.Language.Desc": "ランチャー全体で使用される言語。変更はすぐに適用されます。",
|
||||
"Options.DefaultProfile.Label": "既定のプロフィール名",
|
||||
"Options.DefaultProfile.Desc": "ゲームがテキスト入力を要求したときに使用する名前です。既定値は Sharp です。",
|
||||
|
||||
"Common.On": "オン",
|
||||
"Common.Off": "オフ",
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
|
||||
"Options.Language.Label": "에뮬레이터 언어",
|
||||
"Options.Language.Desc": "런처 전체에 사용되는 언어입니다. 변경 사항은 즉시 적용됩니다.",
|
||||
"Options.DefaultProfile.Label": "기본 프로필 이름",
|
||||
"Options.DefaultProfile.Desc": "게임에서 텍스트 입력을 요청할 때 사용할 이름입니다. 기본값은 Sharp입니다.",
|
||||
|
||||
"Common.On": "켬",
|
||||
"Common.Off": "끔",
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
|
||||
"Options.Language.Label": "Taal van de emulator",
|
||||
"Options.Language.Desc": "Taal die in de hele launcher wordt gebruikt. Wordt direct toegepast.",
|
||||
"Options.DefaultProfile.Label": "Standaardprofielnaam",
|
||||
"Options.DefaultProfile.Desc": "Naam die wordt gebruikt wanneer een game om tekstinvoer vraagt. De standaardwaarde is Sharp.",
|
||||
|
||||
"Common.On": "Aan",
|
||||
"Common.Off": "Uit",
|
||||
|
||||
@@ -76,8 +76,6 @@
|
||||
|
||||
"Options.Language.Label": "Idioma do emulador",
|
||||
"Options.Language.Desc": "Idioma utilizado em todo o lançador. Aplica-se de imediato.",
|
||||
"Options.DefaultProfile.Label": "Nome de perfil predefinido",
|
||||
"Options.DefaultProfile.Desc": "Nome utilizado quando um jogo solicita a introdução de texto. O valor predefinido é Sharp.",
|
||||
|
||||
"Common.On": "Ativado",
|
||||
"Common.Off": "Desativado",
|
||||
|
||||
@@ -109,8 +109,6 @@
|
||||
|
||||
"Options.Language.Label": "Язык эмулятора",
|
||||
"Options.Language.Desc": "Язык интерфейса лаунчера. Изменение применяется сразу.",
|
||||
"Options.DefaultProfile.Label": "Имя профиля по умолчанию",
|
||||
"Options.DefaultProfile.Desc": "Имя, используемое, когда игра запрашивает ввод текста. Значение по умолчанию — Sharp.",
|
||||
|
||||
"Common.On": "Включено",
|
||||
"Common.Off": "Выключено",
|
||||
|
||||
@@ -173,8 +173,6 @@
|
||||
"Options.Env.LogIo.Desc": "Dosya açma, okuma ve yol çözümleme etkinliğini konsola günlükle.\nBir oyun açılışta veri dosyalarını bulamıyorsa kullanın.",
|
||||
"Options.Env.LogNp.Desc": "NP (PlayStation Network) kütüphane çağrılarını konsola günlükle.",
|
||||
"Options.Env.GuestImageCpuSync.Desc": "Oyunun kendi CPU kodunun yeniden yazdığı misafir yüzeyleri tekrar yükler.\nNormalde kapalı bırakın. CPU ile çizilen yüzeyleri ekrana ulaşmayan oyunlarda açın.\nPerformansa mal olur ve GTA V gibi bazı oyunlarda soruna yol açar.",
|
||||
"Options.DefaultProfile.Label": "Varsayilan profil adi",
|
||||
"Options.DefaultProfile.Desc": "Oyun metin girisi istediginde kullanilacak ad. Varsayilan deger Sharp'tir.",
|
||||
"Common.Save": "Kaydet",
|
||||
"Common.Cancel": "İptal",
|
||||
"PerGame.Title": "Oyuna özel ayarlar — {0} ({1})",
|
||||
|
||||
@@ -346,15 +346,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</ComboBox>
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="DefaultProfileRow"
|
||||
Label="{Binding [Options.DefaultProfile.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.DefaultProfile.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<TextBox x:Name="DefaultProfileBox"
|
||||
Width="180"
|
||||
MaxLength="16"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="TitleMusicRow"
|
||||
Label="{Binding [Options.TitleMusic.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.TitleMusic.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
@@ -655,7 +646,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -298,8 +298,6 @@ public partial class MainWindow : Window
|
||||
SetEnvironmentToggle(
|
||||
"SHARPEMU_GUEST_IMAGE_CPU_SYNC",
|
||||
EnvGuestImageCpuSyncToggle.IsChecked == true);
|
||||
DefaultProfileBox.TextChanged += (_, _) =>
|
||||
_settings.DefaultProfile = GuiSettings.NormalizeDefaultProfile(DefaultProfileBox.Text);
|
||||
LanguageBox.SelectionChanged += (_, _) => OnLanguageChanged();
|
||||
|
||||
GameList.AddHandler(ContextRequestedEvent, OnGameContextRequested, RoutingStrategies.Tunnel);
|
||||
@@ -885,7 +883,6 @@ public partial class MainWindow : Window
|
||||
EnvLogNpToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_NP");
|
||||
EnvGuestImageCpuSyncToggle.IsChecked =
|
||||
_settings.EnvironmentToggles.Contains("SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
||||
DefaultProfileBox.Text = _settings.DefaultProfile;
|
||||
WindowModeBox.SelectedIndex = ChoiceIndex(_settings.WindowMode, "Windowed", "Borderless", "Exclusive");
|
||||
LoadHostDisplayOptions();
|
||||
ScalingModeBox.SelectedIndex = ChoiceIndex(_settings.ScalingMode, "Fit", "Cover", "Stretch", "Integer");
|
||||
@@ -1112,8 +1109,6 @@ public partial class MainWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
private const string DefaultProfileEnvironmentName = "SHARPEMU_DEFAULT_PROFILE";
|
||||
|
||||
private string SelectedLogLevel()
|
||||
{
|
||||
return LogLevelBox.SelectedIndex switch
|
||||
@@ -2011,20 +2006,10 @@ public partial class MainWindow : Window
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(name, DefaultProfileEnvironmentName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable(name, value);
|
||||
_appliedEnvironmentVariables.Add(name);
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable(
|
||||
DefaultProfileEnvironmentName,
|
||||
GuiSettings.NormalizeDefaultProfile(_settings.DefaultProfile));
|
||||
_appliedEnvironmentVariables.Add(DefaultProfileEnvironmentName);
|
||||
|
||||
Environment.SetEnvironmentVariable(
|
||||
"SHARPEMU_RENDER_SCALE",
|
||||
_settings.RenderResolutionScale.ToString(
|
||||
|
||||
@@ -46,9 +46,6 @@ public static partial class AgcExports
|
||||
private const uint ItNumInstances = 0x2F;
|
||||
private const uint ItDrawIndexMultiAuto = 0x30;
|
||||
private const uint ItDrawIndexOffset2 = 0x35;
|
||||
private const uint ItDrawIndexIndirectMulti = 0x38;
|
||||
private const uint DrawIndexedIndirectArgsSize = 20;
|
||||
private const uint DrawIndexedIndirectMaxScan = 1024;
|
||||
private const uint ItWriteData = 0x37;
|
||||
private const uint ItDispatchDirect = 0x15;
|
||||
private const uint ItDispatchIndirect = 0x16;
|
||||
@@ -322,7 +319,6 @@ public static partial class AgcExports
|
||||
private static int _tracedVertexRangeCount;
|
||||
private static long _dcbWaitRegMemTraceCount;
|
||||
private static long _createShaderTraceCount;
|
||||
private static long _duplicateTargetTraceCount;
|
||||
private static long _cbMetadataSkipTraceCount;
|
||||
private static long _packetPayloadTraceCount;
|
||||
private static bool _tracedMissingPixelShaderBindings;
|
||||
@@ -1998,65 +1994,6 @@ public static partial class AgcExports
|
||||
return ReturnPointer(ctx, drawCommand);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "1q1titRBL6o",
|
||||
ExportName = "sceAgcDcbDrawIndirect",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbDrawIndirect(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var dataOffset = (uint)ctx[CpuRegister.Rsi];
|
||||
var emit = Interlocked.Increment(ref _indirectDrawEmitCount);
|
||||
|
||||
if (emit <= 12 || emit % 250 == 0)
|
||||
{
|
||||
var rcx = ctx[CpuRegister.Rcx];
|
||||
var dump = string.Empty;
|
||||
for (var word = 0; word < 8; word++)
|
||||
{
|
||||
dump += TryReadUInt32(ctx, rcx + dataOffset + ((ulong)word * 4), out var raw)
|
||||
? $" {raw}"
|
||||
: " ?";
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] agc.emit_indirect#{emit} buf=0x{commandBufferAddress:X16} " +
|
||||
$"off=0x{dataOffset:X} rdx=0x{ctx[CpuRegister.Rdx]:X} rcx=0x{rcx:X} " +
|
||||
$"r8=0x{ctx[CpuRegister.R8]:X} rcx_words:{dump}");
|
||||
}
|
||||
|
||||
if (commandBufferAddress == 0)
|
||||
{
|
||||
Interlocked.Increment(ref _indirectDrawEmitRejectCount);
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var drawCommand) ||
|
||||
!TryWriteUInt32(ctx, drawCommand, Pm4(5, ItDrawIndirect, 0)) ||
|
||||
!TryWriteUInt32(ctx, drawCommand + 4, dataOffset) ||
|
||||
!TryWriteUInt32(ctx, drawCommand + 8, 0) ||
|
||||
!TryWriteUInt32(ctx, drawCommand + 12, 0) ||
|
||||
!TryWriteUInt32(ctx, drawCommand + 16, 0))
|
||||
{
|
||||
var rejects = Interlocked.Increment(ref _indirectDrawEmitRejectCount);
|
||||
if (rejects <= 8 || rejects % 250 == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] agc.emit_indirect_reject#{rejects} " +
|
||||
$"buf=0x{commandBufferAddress:X16} reason=alloc_or_write");
|
||||
}
|
||||
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc(
|
||||
$"agc.dcb_draw_indirect buf=0x{commandBufferAddress:X16} " +
|
||||
$"draw=0x{drawCommand:X16} offset=0x{dataOffset:X}");
|
||||
|
||||
return ReturnPointer(ctx, drawCommand);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Yw0jKSqop+E",
|
||||
ExportName = "sceAgcDcbDrawIndexAuto",
|
||||
@@ -2115,39 +2052,6 @@ public static partial class AgcExports
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ypVBz4uPKcQ",
|
||||
ExportName = "sceAgcDcbDrawIndexIndirectMulti",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbDrawIndexIndirectMulti(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var dataOffset = (uint)ctx[CpuRegister.Rsi];
|
||||
var drawCount = (uint)ctx[CpuRegister.Rdx];
|
||||
var stride = DrawIndexedIndirectArgsSize;
|
||||
var modifier = (uint)ctx[CpuRegister.R8];
|
||||
if (commandBufferAddress == 0 ||
|
||||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 8, out var commandAddress) ||
|
||||
!TryWriteUInt32(ctx, commandAddress, Pm4(8, ItDrawIndexIndirectMulti, 0)) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 4, dataOffset) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 8, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 12, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 16, 0) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 20, drawCount) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 24, stride) ||
|
||||
!TryWriteUInt32(ctx, commandAddress + 28, modifier))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc(
|
||||
$"agc.dcb_draw_index_indirect_multi buf=0x{commandBufferAddress:X16} " +
|
||||
$"cmd=0x{commandAddress:X16} offset=0x{dataOffset:X8} draws={drawCount} " +
|
||||
$"stride={stride} modifier=0x{modifier:X8}");
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "mStuvI0zOtc",
|
||||
ExportName = "sceAgcDcbDrawIndexIndirectGetSize",
|
||||
@@ -3908,7 +3812,6 @@ public static partial class AgcExports
|
||||
|
||||
if (TryReadSubmittedDrawCount(
|
||||
ctx,
|
||||
gpuState,
|
||||
state,
|
||||
currentAddress,
|
||||
length,
|
||||
@@ -3932,8 +3835,7 @@ public static partial class AgcExports
|
||||
var indexed = op is
|
||||
ItDrawIndex2 or
|
||||
ItDrawIndexOffset2 or
|
||||
ItDrawIndexIndirect or
|
||||
ItDrawIndexIndirectMulti;
|
||||
ItDrawIndexIndirect;
|
||||
state.SawIndexedDraw |= indexed;
|
||||
TryTranslateGuestDraw(ctx, gpuState, state, indexCount, indexed);
|
||||
}
|
||||
@@ -4293,7 +4195,6 @@ public static partial class AgcExports
|
||||
op is ItDispatchDirect or ItDispatchIndirect ||
|
||||
op is ItDrawIndirect or
|
||||
ItDrawIndexIndirect or
|
||||
ItDrawIndexIndirectMulti or
|
||||
ItDrawIndex2 or
|
||||
ItDrawIndexAuto or
|
||||
ItDrawIndexMultiAuto or
|
||||
@@ -6263,24 +6164,8 @@ public static partial class AgcExports
|
||||
}
|
||||
}
|
||||
|
||||
private const int IndirectArgsFlushTimeoutMilliseconds = 250;
|
||||
|
||||
private static void FlushGpuWorkForIndirectArgs(SubmittedGpuState gpuState)
|
||||
{
|
||||
var pending = gpuState.WorkSequence;
|
||||
if (pending == 0 || pending > long.MaxValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GuestGpu.Current.WaitForGuestWork(
|
||||
(long)pending,
|
||||
IndirectArgsFlushTimeoutMilliseconds);
|
||||
}
|
||||
|
||||
private static bool TryReadSubmittedDrawCount(
|
||||
CpuContext ctx,
|
||||
SubmittedGpuState gpuState,
|
||||
SubmittedDcbState state,
|
||||
ulong packetAddress,
|
||||
uint packetLength,
|
||||
@@ -6311,89 +6196,17 @@ public static partial class AgcExports
|
||||
|
||||
drawCount = (control >> 21) & 0x7FFu;
|
||||
return true;
|
||||
case ItDrawIndexIndirectMulti when packetLength >= 8 &&
|
||||
state.IndirectArgsAddress != 0:
|
||||
if (!TryReadUInt32(ctx, packetAddress + 4, out var multiOffset) ||
|
||||
!TryReadUInt32(ctx, packetAddress + 20, out var multiDraws) ||
|
||||
!TryReadUInt32(ctx, packetAddress + 24, out var multiStride))
|
||||
case ItDrawIndirect or ItDrawIndexIndirect
|
||||
when packetLength >= 5 && state.IndirectArgsAddress != 0:
|
||||
if (!TryReadUInt32(ctx, packetAddress + 4, out var dataOffset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (multiStride < DrawIndexedIndirectArgsSize)
|
||||
{
|
||||
multiStride = DrawIndexedIndirectArgsSize;
|
||||
}
|
||||
|
||||
var multiTotal = 0UL;
|
||||
var multiCapped = multiDraws == 0
|
||||
? DrawIndexedIndirectMaxScan
|
||||
: Math.Min(multiDraws, 4096u);
|
||||
for (var draw = 0u; draw < multiCapped; draw++)
|
||||
{
|
||||
if (!TryReadUInt32(
|
||||
ctx,
|
||||
state.IndirectArgsAddress + multiOffset + ((ulong)draw * multiStride),
|
||||
out var subCount))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (subCount == 0 && multiDraws == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
multiTotal += subCount;
|
||||
}
|
||||
|
||||
var multiProbe = Interlocked.Increment(ref _indirectMultiProbeCount);
|
||||
if (multiProbe <= 12 || multiProbe % 250 == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] agc.draw_multi#{multiProbe} args=0x{state.IndirectArgsAddress:X} " +
|
||||
$"off=0x{multiOffset:X} draws={multiDraws} stride={multiStride} " +
|
||||
$"total={multiTotal}");
|
||||
}
|
||||
|
||||
drawCount = (uint)Math.Min(multiTotal, uint.MaxValue);
|
||||
return drawCount != 0;
|
||||
case ItDrawIndirect or ItDrawIndexIndirect:
|
||||
var probe = Interlocked.Increment(ref _indirectDrawProbeCount);
|
||||
if (!TryReadUInt32(ctx, packetAddress + 4, out var dataOffset))
|
||||
{
|
||||
dataOffset = 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
var readable = packetLength >= 5 &&
|
||||
state.IndirectArgsAddress != 0 &&
|
||||
dataOffset != 0xFFFFFFFFu;
|
||||
var resolved = readable &&
|
||||
TryReadUInt32(
|
||||
ctx,
|
||||
state.IndirectArgsAddress + dataOffset,
|
||||
out drawCount);
|
||||
if (probe <= 12 || probe % 100 == 0)
|
||||
{
|
||||
var dump = string.Empty;
|
||||
for (var word = 0; word < 8; word++)
|
||||
{
|
||||
dump += TryReadUInt32(
|
||||
ctx,
|
||||
state.IndirectArgsAddress + dataOffset + ((ulong)word * 4),
|
||||
out var raw)
|
||||
? $" {raw}"
|
||||
: " ?";
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] agc.draw_indirect#{probe} op=0x{op:X} len={packetLength} " +
|
||||
$"args=0x{state.IndirectArgsAddress:X} off=0x{dataOffset:X} " +
|
||||
$"resolved={resolved} count={drawCount} words:{dump}");
|
||||
}
|
||||
|
||||
return resolved;
|
||||
return TryReadUInt32(
|
||||
ctx,
|
||||
state.IndirectArgsAddress + dataOffset,
|
||||
out drawCount);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -8243,61 +8056,6 @@ public static partial class AgcExports
|
||||
source.Format == destination.Format;
|
||||
}
|
||||
|
||||
private static readonly HashSet<ulong> _renderTargetAddresses = new();
|
||||
private static readonly HashSet<ulong> _sampledRenderTargets = new();
|
||||
private static readonly object _renderTargetProbeGate = new();
|
||||
private static long _renderTargetSampleTraceCount;
|
||||
private static long _indirectDrawProbeCount;
|
||||
private static long _indirectDrawEmitCount;
|
||||
private static long _indirectDrawEmitRejectCount;
|
||||
private static long _indirectMultiProbeCount;
|
||||
|
||||
private static void NoteRenderTargetAddress(ulong address)
|
||||
{
|
||||
if (address == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_renderTargetProbeGate)
|
||||
{
|
||||
if (_renderTargetAddresses.Count < 512)
|
||||
{
|
||||
_renderTargetAddresses.Add(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void NoteSampledAddress(ulong address, uint format = 0, uint numberType = 0)
|
||||
{
|
||||
if (address == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool firstTime;
|
||||
int distinctTargets;
|
||||
lock (_renderTargetProbeGate)
|
||||
{
|
||||
if (!_renderTargetAddresses.Contains(address))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
firstTime = _sampledRenderTargets.Add(address);
|
||||
distinctTargets = _renderTargetAddresses.Count;
|
||||
}
|
||||
|
||||
var count = Interlocked.Increment(ref _renderTargetSampleTraceCount);
|
||||
if (firstTime || count % 2000 == 0)
|
||||
{
|
||||
var gpuResident = GuestGpu.Current.IsGpuGuestImageAvailable(address, format, numberType);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] agc.rt_sampled#{count} addr=0x{address:X} first={firstTime} " +
|
||||
$"gpu_resident={gpuResident} fmt={format}/{numberType} known_targets={distinctTargets}");
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<RenderTargetDescriptor> GetRenderTargets(
|
||||
IReadOnlyDictionary<uint, uint> registers,
|
||||
bool includeMaskedTargets = false)
|
||||
@@ -8324,13 +8082,6 @@ public static partial class AgcExports
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targets.Exists(existing => existing.Address == address))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NoteRenderTargetAddress(address);
|
||||
|
||||
targets.Add(new RenderTargetDescriptor(
|
||||
slot,
|
||||
address,
|
||||
@@ -8341,21 +8092,6 @@ public static partial class AgcExports
|
||||
(attrib3 >> 14) & 0x1Fu));
|
||||
}
|
||||
|
||||
if (targets.Count > 1 &&
|
||||
targets.Select(t => t.Address).Distinct().Count() != targets.Count)
|
||||
{
|
||||
var dupCount = Interlocked.Increment(ref _duplicateTargetTraceCount);
|
||||
if (dupCount <= 12 || dupCount % 500 == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] agc.rt_duplicate#{dupCount} has_mask={hasTargetMask} " +
|
||||
$"mask=0x{targetMask:X8} slots=[" +
|
||||
string.Join(",", targets.Select(t =>
|
||||
$"{t.Slot}:0x{t.Address:X}:m{(targetMask >> ((int)t.Slot * 4)) & 0xFu}")) +
|
||||
"]");
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
@@ -9595,7 +9331,6 @@ public static partial class AgcExports
|
||||
descriptor.Format,
|
||||
descriptor.NumberType))
|
||||
{
|
||||
NoteSampledAddress(descriptor.Address, descriptor.Format, descriptor.NumberType);
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
@@ -9675,7 +9410,6 @@ public static partial class AgcExports
|
||||
$"tile={descriptor.TileMode} mip={mipLevel}");
|
||||
}
|
||||
|
||||
NoteSampledAddress(descriptor.Address, descriptor.Format, descriptor.NumberType);
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
@@ -9736,7 +9470,6 @@ public static partial class AgcExports
|
||||
descriptor.Type,
|
||||
textureDepth)))
|
||||
{
|
||||
NoteSampledAddress(descriptor.Address, descriptor.Format, descriptor.NumberType);
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
@@ -9799,8 +9532,7 @@ public static partial class AgcExports
|
||||
|
||||
if (readAllLayers)
|
||||
{
|
||||
NoteSampledAddress(descriptor.Address, descriptor.Format, descriptor.NumberType);
|
||||
texture = new GuestDrawTexture(
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
descriptor.Height,
|
||||
@@ -9862,8 +9594,7 @@ public static partial class AgcExports
|
||||
|
||||
if (uploadedLayers == arrayLayers)
|
||||
{
|
||||
NoteSampledAddress(descriptor.Address, descriptor.Format, descriptor.NumberType);
|
||||
texture = new GuestDrawTexture(
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
descriptor.Height,
|
||||
@@ -9966,8 +9697,7 @@ public static partial class AgcExports
|
||||
if (IsGpuDetileEquation(gpuDetileParams.Equation) &&
|
||||
(long)elementsWide * elementsHigh * bytesPerElement <= source.Length)
|
||||
{
|
||||
NoteSampledAddress(descriptor.Address, descriptor.Format, descriptor.NumberType);
|
||||
texture = new GuestDrawTexture(
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
descriptor.Height,
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Ime;
|
||||
|
||||
public static class ImeDialogExports
|
||||
{
|
||||
private const int StatusNone = 0;
|
||||
private const int StatusRunning = 1;
|
||||
private const int StatusFinished = 2;
|
||||
|
||||
private const int EndStatusOk = 0;
|
||||
|
||||
private const ulong ParamMaxTextLengthOffset = 0x24;
|
||||
private const ulong ParamInputTextBufferOffset = 0x28;
|
||||
|
||||
private const int ImeDialogErrorInvalidAddress = unchecked((int)0x80BC0001);
|
||||
|
||||
private const string DefaultInputText = "Sharp";
|
||||
|
||||
private static readonly object _gate = new();
|
||||
private static int _status = StatusNone;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "NUeBrN7hzf0",
|
||||
ExportName = "sceImeDialogInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceImeDialog")]
|
||||
public static int ImeDialogInit(CpuContext ctx)
|
||||
{
|
||||
var parameterAddress = ctx[CpuRegister.Rdi];
|
||||
if (parameterAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, ImeDialogErrorInvalidAddress);
|
||||
}
|
||||
|
||||
TryWriteInputText(ctx, parameterAddress);
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
_status = StatusFinished;
|
||||
}
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "IADmD4tScBY",
|
||||
ExportName = "sceImeDialogGetStatus",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceImeDialog")]
|
||||
public static int ImeDialogGetStatus(CpuContext ctx)
|
||||
{
|
||||
int status;
|
||||
lock (_gate)
|
||||
{
|
||||
status = _status;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)(long)status);
|
||||
return status;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "x01jxu+vxlc",
|
||||
ExportName = "sceImeDialogGetResult",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceImeDialog")]
|
||||
public static int ImeDialogGetResult(CpuContext ctx)
|
||||
{
|
||||
var resultAddress = ctx[CpuRegister.Rdi];
|
||||
if (resultAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, ImeDialogErrorInvalidAddress);
|
||||
}
|
||||
|
||||
Span<byte> result = stackalloc byte[8];
|
||||
result.Clear();
|
||||
BinaryPrimitives.WriteInt32LittleEndian(result, EndStatusOk);
|
||||
ctx.Memory.TryWrite(resultAddress, result);
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "oBmw4xrmfKs",
|
||||
ExportName = "sceImeDialogAbort",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceImeDialog")]
|
||||
public static int ImeDialogAbort(CpuContext ctx)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_status = StatusFinished;
|
||||
}
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "gyTyVn+bXMw",
|
||||
ExportName = "sceImeDialogTerm",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceImeDialog")]
|
||||
public static int ImeDialogTerm(CpuContext ctx)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_status = StatusNone;
|
||||
}
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static void TryWriteInputText(CpuContext ctx, ulong parameterAddress)
|
||||
{
|
||||
Span<byte> field = stackalloc byte[8];
|
||||
if (!ctx.Memory.TryRead(parameterAddress + ParamInputTextBufferOffset, field))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bufferAddress = BinaryPrimitives.ReadUInt64LittleEndian(field);
|
||||
if (bufferAddress == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var maxTextLength = 16u;
|
||||
Span<byte> lengthField = stackalloc byte[4];
|
||||
if (ctx.Memory.TryRead(parameterAddress + ParamMaxTextLengthOffset, lengthField))
|
||||
{
|
||||
var declared = BinaryPrimitives.ReadUInt32LittleEndian(lengthField);
|
||||
if (declared is > 0 and <= 256)
|
||||
{
|
||||
maxTextLength = declared;
|
||||
}
|
||||
}
|
||||
|
||||
var text = Environment.GetEnvironmentVariable("SHARPEMU_DEFAULT_PROFILE");
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
text = DefaultInputText;
|
||||
}
|
||||
|
||||
if ((uint)text.Length > maxTextLength)
|
||||
{
|
||||
text = text[..(int)maxTextLength];
|
||||
}
|
||||
|
||||
var encoded = Encoding.Unicode.GetBytes(text);
|
||||
Span<byte> payload = stackalloc byte[encoded.Length + 2];
|
||||
encoded.CopyTo(payload);
|
||||
payload[^2] = 0;
|
||||
payload[^1] = 0;
|
||||
|
||||
if (ctx.Memory.TryWrite(bufferAddress, payload))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] ime.dialog_autofill buf=0x{bufferAddress:X16} " +
|
||||
$"max={maxTextLength} text=\"{text}\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)(long)result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -33,17 +33,12 @@ public static class SaveDataDialogExports
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogInitialize(CpuContext ctx)
|
||||
{
|
||||
var previous = Interlocked.CompareExchange(
|
||||
ref _status,
|
||||
StatusInitialized,
|
||||
StatusNone);
|
||||
if (previous == StatusNone || previous == StatusFinished)
|
||||
if (Interlocked.CompareExchange(ref _status, StatusInitialized, StatusNone) != StatusNone)
|
||||
{
|
||||
Interlocked.Exchange(ref _status, StatusInitialized);
|
||||
return ctx.SetReturn(ErrorOk);
|
||||
return ctx.SetReturn(ErrorAlreadyInitialized);
|
||||
}
|
||||
|
||||
return ctx.SetReturn(ErrorAlreadyInitialized);
|
||||
return ctx.SetReturn(ErrorOk);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
|
||||
@@ -1046,50 +1046,14 @@ internal static unsafe class VulkanVideoPresenter
|
||||
targets.Count > 8 ||
|
||||
AnyRenderTargetInvalid(targets))
|
||||
{
|
||||
var dropCount = Interlocked.Increment(ref _offscreenDropTraceCount);
|
||||
if (dropCount <= 16 || dropCount % 500 == 0)
|
||||
{
|
||||
var detail = targets.Count == 0
|
||||
? "<no targets>"
|
||||
: string.Join(
|
||||
" ",
|
||||
targets.Select(t => $"0x{t.Address:X}:{t.Width}x{t.Height}"));
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] vk.offscreen_drop#{dropCount} spirv={pixelSpirv.Length} " +
|
||||
$"mrt={targets.Count} vs=0x{shaderAddress:X16} {detail}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var firstTarget = targets[0];
|
||||
if (RenderTargetsMismatchedOrAliased(targets, firstTarget))
|
||||
{
|
||||
var skipCount = Interlocked.Increment(ref _mrtSkipTraceCount);
|
||||
if (skipCount <= 16 || skipCount % 200 == 0)
|
||||
{
|
||||
var aliased = false;
|
||||
for (var i = 0; i < targets.Count && !aliased; i++)
|
||||
{
|
||||
for (var j = i + 1; j < targets.Count; j++)
|
||||
{
|
||||
if (targets[i].Address == targets[j].Address)
|
||||
{
|
||||
aliased = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var detail = string.Join(
|
||||
" ",
|
||||
targets.Select(t =>
|
||||
$"0x{t.Address:X}:{t.Width}x{t.Height}:f{t.Format}/{t.NumberType}"));
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] vk.mrt_skip#{skipCount} mrt={targets.Count} " +
|
||||
$"aliased={aliased} vs=0x{shaderAddress:X16} {detail}");
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] Vulkan skipped MRT draw with mismatched dimensions or aliased targets.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1334,8 +1298,6 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
}
|
||||
|
||||
private static long _mrtSkipTraceCount;
|
||||
private static long _offscreenDropTraceCount;
|
||||
private static long _perfDrawCount;
|
||||
private static long _perfDrawTicks;
|
||||
private static long _perfPipelineCreations;
|
||||
@@ -1872,22 +1834,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
if (!_availableGuestImages.TryGetValue(address, out var availableFormat))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (availableFormat == guestFormat)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryDecodeRenderTargetFormat(
|
||||
(availableFormat >> 8) & 0x1FFu,
|
||||
availableFormat & 0xFFu,
|
||||
out var resident) &&
|
||||
TryDecodeRenderTargetFormat(format, numberType, out var requested) &&
|
||||
Presenter.IsCompatibleViewFormat(resident.Format, requested.Format);
|
||||
return _availableGuestImages.TryGetValue(address, out var availableFormat) &&
|
||||
availableFormat == guestFormat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12127,7 +12075,6 @@ internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Vulkan skipped storage render-target feedback loop " +
|
||||
$"vs=0x{work.ShaderAddress:X16} " +
|
||||
$"targets={string.Join(',', work.Targets.Where(target => target.Address != 0).Select(target => $"0x{target.Address:X16}"))}; " +
|
||||
"sampled aliases use ordered snapshots");
|
||||
ReturnPooledGuestData(work.Draw);
|
||||
|
||||
Reference in New Issue
Block a user