Compare commits

..

5 Commits

Author SHA1 Message Date
Coxxs
df40a69872 Fix headless mode (ryubing/ryujinx!146)
See merge request ryubing/ryujinx!146
2025-09-10 11:43:50 -05:00
LotP
b000f91dad Memory changes 2.2.1 (ryubing/ryujinx!144)
See merge request ryubing/ryujinx!144
2025-09-06 13:51:08 -05:00
LotP
a60b2a0ba3 Memory changes 2.2 (ryubing/ryujinx!143)
See merge request ryubing/ryujinx!143
2025-09-06 11:10:55 -05:00
Hack茶ん
4c9b48b754 Update Korean translation (ryubing/ryujinx!141)
See merge request ryubing/ryujinx!141
2025-09-05 13:06:59 -05:00
Neo
3bd7d5904e Compat: New Entries + Minor Adjustments (ryubing/ryujinx!140)
See merge request ryubing/ryujinx!140
2025-09-05 03:50:49 -05:00
17 changed files with 248 additions and 302 deletions

View File

@@ -779,7 +779,7 @@
"he_IL": "",
"it_IT": "Scansiona un Amiibo (da file .bin)",
"ja_JP": "",
"ko_KR": "Amiibo 스캔(에서)",
"ko_KR": "Amiibo 스캔(.bin에서)",
"no_NO": "Skann en Amiibo (fra bin fil)",
"pl_PL": "",
"pt_BR": "Escaneie um Amiibo (de um .bin)",
@@ -829,7 +829,7 @@
"he_IL": "התקן קושחה (.XCI או .ZIP)",
"it_IT": "Installa firmware (.XCI o .ZIP)",
"ja_JP": "ファームウェアをインストール(.XCI または .ZIP",
"ko_KR": "펌웨어 (.XCI 또는 .ZIP) 설치",
"ko_KR": "펌웨어(.XCI 또는 .ZIP) 설치",
"no_NO": "Installer firmware (.XCI eller .ZIP)",
"pl_PL": "Zainstaluj firmware (.XCI lub .ZIP)",
"pt_BR": "Instalar firmware (.XCI ou .ZIP)",
@@ -854,7 +854,7 @@
"he_IL": "התקן קושחה (תיקייה)",
"it_IT": "Installa firmware (cartella)",
"ja_JP": "ファームウェアをインストール(フォルダー)",
"ko_KR": "펌웨어 (폴더) 설치",
"ko_KR": "펌웨어(폴더) 설치",
"no_NO": "Installer firmware (mappe)",
"pl_PL": "Zainstaluj firmware (katalog)",
"pt_BR": "Instalar firmware (diretório)",
@@ -904,7 +904,7 @@
"he_IL": "",
"it_IT": "Installa chiavi (.KEYS)",
"ja_JP": "",
"ko_KR": "키 설치 (.KEYS)",
"ko_KR": "키 설치(.KEYS)",
"no_NO": "Installer nøkler (.KEYS)",
"pl_PL": "",
"pt_BR": "Instalar chaves (.KEYS)",
@@ -929,7 +929,7 @@
"he_IL": "",
"it_IT": "Installa chiavi (cartella)",
"ja_JP": "",
"ko_KR": "키 (폴더) 설치",
"ko_KR": "키(폴더) 설치",
"no_NO": "Installer nøkler (mappe)",
"pl_PL": "",
"pt_BR": "Instalar chaves (diretório)",
@@ -1104,7 +1104,7 @@
"he_IL": "",
"it_IT": "Dimensione finestra",
"ja_JP": "",
"ko_KR": "윈도 창",
"ko_KR": "창 크기",
"no_NO": "Vindu størrelse",
"pl_PL": "",
"pt_BR": "Tamanho da Janela",
@@ -1604,7 +1604,7 @@
"he_IL": "",
"it_IT": "Versione: {0}",
"ja_JP": "バージョン: {0}",
"ko_KR": "버전: {0}",
"ko_KR": "버전 : {0}",
"no_NO": "Versjon: {0}",
"pl_PL": "Wersja: {0}",
"pt_BR": "Versão: {0}",
@@ -1629,7 +1629,7 @@
"he_IL": "",
"it_IT": "Tempo di gioco:",
"ja_JP": "プレイ時間:",
"ko_KR": "플레이 타임:",
"ko_KR": "플레이 시간 :",
"no_NO": "Spilletid:",
"pl_PL": "Czas w grze:",
"pt_BR": "Tempo de Jogo:",
@@ -1654,7 +1654,7 @@
"he_IL": "",
"it_IT": "Ultima partita:",
"ja_JP": "最終プレイ日時:",
"ko_KR": "마지막 플레이:",
"ko_KR": "마지막 플레이 :",
"no_NO": "Sist Spilt:",
"pl_PL": "Ostatnio grane:",
"pt_BR": "Última vez Jogado:",
@@ -1679,7 +1679,7 @@
"he_IL": "",
"it_IT": "Estensione: {0}",
"ja_JP": "ファイル拡張子: {0}",
"ko_KR": "파일 확장자: {0}",
"ko_KR": "파일 확장자 : {0}",
"no_NO": "Fil Eks.: {0}",
"pl_PL": "Rozszerzenie pliku: {0}",
"pt_BR": "Extensão: {0}",
@@ -1704,7 +1704,7 @@
"he_IL": "",
"it_IT": "Dimensione file: {0}",
"ja_JP": "ファイルサイズ: {0}",
"ko_KR": "파일 크기: {0}",
"ko_KR": "파일 크기 : {0}",
"no_NO": "Fil Størrelse: {0}",
"pl_PL": "Rozmiar pliku: {0}",
"pt_BR": "Tamanho: {0}",
@@ -1879,7 +1879,7 @@
"he_IL": "",
"it_IT": "Titolo: A-Z",
"ja_JP": "タイトルA-Z",
"ko_KR": "제목: A-Z",
"ko_KR": "타이틀 : A-Z",
"no_NO": "Tittel: A-Z",
"pl_PL": "Tytuł: A-Z",
"pt_BR": "Título: A-Z",
@@ -1904,7 +1904,7 @@
"he_IL": "",
"it_IT": "Titolo: Z-A",
"ja_JP": "タイトルZ-A",
"ko_KR": "제목: Z-A",
"ko_KR": "타이틀 : Z-A",
"no_NO": "Tittel: Z-A",
"pl_PL": "Tytuł: Z-A",
"pt_BR": "Título: Z-A",
@@ -1929,7 +1929,7 @@
"he_IL": "",
"it_IT": "Stato: Disabilitato",
"ja_JP": "ステータス:無効",
"ko_KR": "상태: 비활성화됨",
"ko_KR": "상태 : 비활성화됨",
"no_NO": "Status: Deaktivert",
"pl_PL": "Status: Wyłączony",
"pt_BR": "Status: Desativado",
@@ -1954,7 +1954,7 @@
"he_IL": "סטטוס: עולה",
"it_IT": "Stato: Crescente",
"ja_JP": "ステータス:昇順",
"ko_KR": "상태: 오름차순",
"ko_KR": "상태 : 오름차순",
"no_NO": "Status: Stigende",
"pl_PL": "Stan: Rosnący",
"pt_BR": "Status: Crescente",
@@ -1979,7 +1979,7 @@
"he_IL": "סטטוס: יורד",
"it_IT": "Stato: Decrescente",
"ja_JP": "ステータス:降順",
"ko_KR": "상태: 내림차순",
"ko_KR": "상태 : 내림차순",
"no_NO": "Status: Synkende",
"pl_PL": "Stan: Malejący",
"pt_BR": "Status: Decrescente",
@@ -2354,7 +2354,7 @@
"he_IL": "ניקוי מטמון הצללות",
"it_IT": "Elimina la cache degli shader",
"ja_JP": "シェーダーキャッシュを破棄",
"ko_KR": "퍼지 셰이더 캐시",
"ko_KR": "셰이더 캐시 제거",
"no_NO": "Tøm shader cache",
"pl_PL": "Wyczyść pamięć podręczną cieni",
"pt_BR": "Limpar Cache de Shader",
@@ -4804,7 +4804,7 @@
"he_IL": "",
"it_IT": "PPTC a basso consumo energetico",
"ja_JP": "",
"ko_KR": "저전력 PPTC 캐시",
"ko_KR": "저전력 PPTC",
"no_NO": "PPTC med lavt strømforbruk",
"pl_PL": "",
"pt_BR": "PPTC com Baixo Consumo de Energia",
@@ -5904,7 +5904,7 @@
"he_IL": "רישום",
"it_IT": "Log",
"ja_JP": "ロギング",
"ko_KR": "로그 기록",
"ko_KR": "로그",
"no_NO": "",
"pl_PL": "Dziennik zdarzeń",
"pt_BR": "Log",
@@ -10054,7 +10054,7 @@
"he_IL": "",
"it_IT": "Tast. num. Invio",
"ja_JP": "",
"ko_KR": "키패드 입력",
"ko_KR": "키패드 엔터",
"no_NO": "Numerisk Enter",
"pl_PL": "",
"pt_BR": "",
@@ -17854,7 +17854,7 @@
"he_IL": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}",
"it_IT": "Errore nell'eliminazione della cache degli shader a {0}: {1}",
"ja_JP": "シェーダーキャッシュの破棄エラー {0}: {1}",
"ko_KR": "{0}에서 셰이더 캐시를 삭제하는 중 오류 발생 : {1}",
"ko_KR": "{0}에서 셰이더 캐시 제거, 오류 발생 : {1}",
"no_NO": "Feil under tømming av shader cache ved {0}: {1}",
"pl_PL": "Błąd podczas czyszczenia cache shaderów w {0}: {1}",
"pt_BR": "Erro ao limpar o cache do shader em {0}: {1}",
@@ -18054,7 +18054,7 @@
"he_IL": "ריוג'ינקס לא הצליחה למצוא קושחה מותקנת",
"it_IT": "Ryujinx non è riuscito a trovare alcun firmware installato",
"ja_JP": "インストールされたファームウェアが見つかりませんでした",
"ko_KR": "설치된 펌웨어를 찾을 수 없음",
"ko_KR": "Ryujinx가 설치된 펌웨어를 찾을 수 없음",
"no_NO": "Ryujinx kunne ikke finne noen fastvare installert",
"pl_PL": "Ryujinx nie mógł znaleźć żadnego zainstalowanego firmware'u",
"pt_BR": "Ryujinx não conseguiu encontrar nenhum Firmware instalado",
@@ -18079,7 +18079,7 @@
"he_IL": "ריוג'ינקס לא הצליחה לנתח את הקושחה שסופקה. זה נגרם בדרך כלל על ידי מפתחות לא עדכניים.",
"it_IT": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.",
"ja_JP": "ファームウェアをパーズできませんでした.通常,古いキーが原因です.",
"ko_KR": "Ryujinx가 제공된 펌웨어를 구문 분석하지 못했습니다. 이는 일반적으로 오래된 키로 인해 발생합니다.",
"ko_KR": "Ryujinx가 제공된 펌웨어를 구문 분석하지 못했습니다. 일반적으로 오래된 키로 인해 발생합니다.",
"no_NO": "Ryujinx klarte ikke å analysere levert fastvare. Dette er vanligvis forårsaket av utdaterte nøkler.",
"pl_PL": "Ryujinx nie był w stanie zparsować dostarczonego firmware'u. Jest to zwykle spowodowane nieaktualnymi kluczami.",
"pt_BR": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.",
@@ -18854,7 +18854,7 @@
"he_IL": "מצב עגינה מוגדר. כדאי ששליטה ניידת תהיה מושבתת.",
"it_IT": "Modalità TV attivata. Gli input della modalità portatile dovrebbero essere disabilitati.",
"ja_JP": "ドッキングモードが設定されています. 携帯コントロールは無効にする必要があります.",
"ko_KR": "도킹 모드가 설정되었습니다. 휴대용 제어 기능을 비활성화해야 합니다.",
"ko_KR": "도킹 모드가 설정되었습니다. 핸드헬드 제어 기능을 비활성화해야 합니다.",
"no_NO": "Docked modus. Håndholdt kontroll skal være deaktivert.",
"pl_PL": "Ustawiony tryb zadokowany. Sterowanie przenośne powinno być wyłączone.",
"pt_BR": "Modo TV definido. O controle portátil deve ser desabilitado.",
@@ -19104,7 +19104,7 @@
"he_IL": "שגיאת ממשק.",
"it_IT": "Errore dell'API.",
"ja_JP": "API エラー.",
"ko_KR": "API 오류.",
"ko_KR": "API 오류입니다.",
"no_NO": "API feil.",
"pl_PL": "Błąd API.",
"pt_BR": "Erro de API.",
@@ -20279,7 +20279,7 @@
"he_IL": "נהל הרחבות משחק עבור {0} ({1})",
"it_IT": "Gestisci DLC per {0} ({1})",
"ja_JP": "DLC 管理",
"ko_KR": "{0} ({1})의 내려받기 가능한 콘텐츠 관리",
"ko_KR": "{0} ({1})의 DLC 관리",
"no_NO": "Behandle nedlastbart innhold for {0} ({1})",
"pl_PL": "Menedżer Zawartości do Pobrania",
"pt_BR": "Gerenciar conteúdo para download para {0} ({1})",
@@ -20904,7 +20904,7 @@
"he_IL": "",
"it_IT": "{0} nuovo/i DLC aggiunto/i",
"ja_JP": "",
"ko_KR": "{0}개의 새로운 내려받기 가능한 콘텐츠가 추가됨",
"ko_KR": "{0}개의 새로운 DLC가 추가됨",
"no_NO": "{0} nytt nedlastbart innhold lagt til",
"pl_PL": "",
"pt_BR": "{0} novo(s) conteúdo(s) para download adicionado(s)",

View File

@@ -978,7 +978,7 @@
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
01008CB01E52E000,"DOOM + DOOM II",opengl;ldn-untested;LAN,playable,2024-09-12 07:06:01
010029D00E740000,"DOOM 3",crash,menus,2024-08-03 05:25:47
010029D00E740000,"DOOM 3",crash;slow,menus,2024-08-03 05:25:47
01005D700E742000,"DOOM 64",nvdec;vulkan,playable,2020-10-13 23:47:28
0100D4F00DD02000,"DOOM II (Classic)",nvdec;online,playable,2021-06-03 20:10:01
0100B1A00D8CE000,"DOOM® Eternal",gpu;slow;nvdec;online-broken,ingame,2024-08-28 15:57:17
@@ -1097,7 +1097,7 @@
0100F9600E746000,"ESP Ra.De. Psi",audio;slow,ingame,2024-03-07 15:05:08
010073000FE18000,"Esports powerful pro yakyuu 2020",gpu;crash;Needs More Attention,ingame,2024-04-29 05:34:14
01004F9012FD8000,"Estranged: The Departure",nvdec;UE4,playable,2022-10-24 10:37:58
010018f01e0a0000,"Eternights",,playable,2025-07-30 12:10:24
010018F01E0A0000,"Eternights",,playable,2025-07-30 12:10:24
0100CB900B498000,"Eternum Ex",,playable,2021-01-13 20:28:32
010092501EB2C000,"Europa (Demo)",gpu;crash;UE4,ingame,2024-04-23 10:47:12
01007BE0160D6000,"EVE ghost enemies",gpu,ingame,2023-01-14 03:13:30
@@ -1243,7 +1243,7 @@
010003F00BD48000,"Friday the 13th: Killer Puzzle",,playable,2021-01-28 01:33:38
010092A00C4B6000,"Friday the 13th: The Game Ultimate Slasher Edition",nvdec;online-broken;UE4,playable,2022-09-06 17:33:27
0100F200178F4000,"FRONT MISSION 1st: Remake",,playable,2023-06-09 07:44:24
0100c4e018a24000,"FRONT MISSION 2: Remake",,playable,2025-07-30 12:11:23
0100C4E018A24000,"FRONT MISSION 2: Remake",,playable,2025-07-30 12:11:23
01007E6019872000,"FRONT MISSION 3: Remake",,playable,2025-07-30 12:12:02
0100861012474000,"Frontline Zed",,playable,2020-10-03 12:55:59
0100B5300B49A000,"Frost",,playable,2022-07-27 12:00:36
@@ -1889,7 +1889,7 @@
010097800EA20000,"Monster Energy Supercross - The Official Videogame 3",UE4;audout;nvdec;online,playable,2021-06-14 12:37:54
0100E9900ED74000,"Monster Farm",32-bit;nvdec,playable,2021-05-05 19:29:13
0100770008DD8000,"Monster Hunter Generations Ultimate™",32-bit;online-broken;ldn-works,playable,2024-03-18 14:35:36
0100B04011742000,"Monster Hunter Rise",gpu;slow;crash;nvdec;online-broken;Needs Update;ldn-works,ingame,2024-08-24 11:04:59
0100B04011742000,"MONSTER HUNTER RISE",gpu;slow;crash;nvdec;online-broken;Needs Update;ldn-works,ingame,2024-08-24 11:04:59
010093A01305C000,"Monster Hunter Rise Demo",online-broken;ldn-works;demo,playable,2022-10-18 23:04:17
0100E21011446000,"Monster Hunter Stories 2: Wings of Ruin",services,ingame,2022-07-10 19:27:30
010042501329E000,"MONSTER HUNTER STORIES 2: WINGS OF RUIN Trial Version",demo,playable,2022-11-13 22:20:26
@@ -2314,7 +2314,7 @@
010077B00BDD8000,"Professional Farmer: Nintendo Switch™ Edition",slow,playable,2020-12-16 13:38:19
010018300C83A000,"Professor Lupo and his Horrible Pets",,playable,2020-06-12 00:08:45
0100D1F0132F6000,"Professor Lupo: Ocean",,playable,2021-04-14 16:33:33
0100c3a017834000,"Prodeus",,playable,2025-07-30 12:07:52
0100C3A017834000,"Prodeus",,playable,2025-07-30 12:07:52
0100BBD00976C000,"Project Highrise: Architect's Edition",,playable,2022-08-10 17:19:12
0100ACE00DAB6000,"Project Nimbus: Complete Edition",nvdec;UE4;vulkan-backend-bug,playable,2022-08-10 17:35:43
01002980140F6000,"Project TRIANGLE STRATEGY™ Debut Demo",UE4;demo,playable,2022-10-24 21:40:27
@@ -2580,6 +2580,7 @@
0100C610154CA000,"Shadowrun: Hong Kong - Extended Edition",gpu;Needs Update,ingame,2022-10-04 20:53:09
010000000EEF0000,"Shadows 2: Perfidia",,playable,2020-08-07 12:43:46
0100AD700CBBE000,"Shadows of Adam",,playable,2021-01-11 13:35:58
010037A01F96C000,"Shadows of the Damned: Hella Remastered",,playable,2025-09-05 11:34:32
01002A800C064000,"Shadowverse Champions Battle",,playable,2022-10-02 22:59:29
01003B90136DA000,"Shadowverse: Champion's Battle",crash,nothing,2023-03-06 00:31:50
0100820013612000,"Shady Part of Me",,playable,2022-10-20 11:31:55
@@ -2978,6 +2979,7 @@
0100EBA01548E000,"The Cruel King and the Great Hero",gpu;services,ingame,2022-12-02 07:02:08
010051800E922000,"The Dark Crystal: Age of Resistance Tactics",,playable,2020-08-11 13:43:41
01003DE00918E000,"The Darkside Detective",,playable,2020-06-03 22:16:18
010032B015D66000,"The DioField Chronicle",,playable,2025-09-05 11:35:50
01000A10041EA000,"The Elder Scrolls V: Skyrim",gpu;crash,ingame,2024-07-14 03:21:31
01004A9006B84000,"The End Is Nigh",,playable,2020-06-01 11:26:45
0100CA100489C000,"The Escapists 2",nvdec,playable,2020-09-24 12:31:31
1 title_id game_name labels status last_updated
978 0100416004C00000 DOOM gpu;slow;nvdec;online-broken ingame 2024-09-23 15:40:07
979 010018900DD00000 DOOM (1993) nvdec;online-broken menus 2022-09-06 13:32:19
980 01008CB01E52E000 DOOM + DOOM II opengl;ldn-untested;LAN playable 2024-09-12 07:06:01
981 010029D00E740000 DOOM 3 crash crash;slow menus 2024-08-03 05:25:47
982 01005D700E742000 DOOM 64 nvdec;vulkan playable 2020-10-13 23:47:28
983 0100D4F00DD02000 DOOM II (Classic) nvdec;online playable 2021-06-03 20:10:01
984 0100B1A00D8CE000 DOOM® Eternal gpu;slow;nvdec;online-broken ingame 2024-08-28 15:57:17
1097 0100F9600E746000 ESP Ra.De. Psi audio;slow ingame 2024-03-07 15:05:08
1098 010073000FE18000 Esports powerful pro yakyuu 2020 gpu;crash;Needs More Attention ingame 2024-04-29 05:34:14
1099 01004F9012FD8000 Estranged: The Departure nvdec;UE4 playable 2022-10-24 10:37:58
1100 010018f01e0a0000 010018F01E0A0000 Eternights playable 2025-07-30 12:10:24
1101 0100CB900B498000 Eternum Ex playable 2021-01-13 20:28:32
1102 010092501EB2C000 Europa (Demo) gpu;crash;UE4 ingame 2024-04-23 10:47:12
1103 01007BE0160D6000 EVE ghost enemies gpu ingame 2023-01-14 03:13:30
1243 010003F00BD48000 Friday the 13th: Killer Puzzle playable 2021-01-28 01:33:38
1244 010092A00C4B6000 Friday the 13th: The Game Ultimate Slasher Edition nvdec;online-broken;UE4 playable 2022-09-06 17:33:27
1245 0100F200178F4000 FRONT MISSION 1st: Remake playable 2023-06-09 07:44:24
1246 0100c4e018a24000 0100C4E018A24000 FRONT MISSION 2: Remake playable 2025-07-30 12:11:23
1247 01007E6019872000 FRONT MISSION 3: Remake playable 2025-07-30 12:12:02
1248 0100861012474000 Frontline Zed playable 2020-10-03 12:55:59
1249 0100B5300B49A000 Frost playable 2022-07-27 12:00:36
1889 010097800EA20000 Monster Energy Supercross - The Official Videogame 3 UE4;audout;nvdec;online playable 2021-06-14 12:37:54
1890 0100E9900ED74000 Monster Farm 32-bit;nvdec playable 2021-05-05 19:29:13
1891 0100770008DD8000 Monster Hunter Generations Ultimate™ 32-bit;online-broken;ldn-works playable 2024-03-18 14:35:36
1892 0100B04011742000 Monster Hunter Rise MONSTER HUNTER RISE gpu;slow;crash;nvdec;online-broken;Needs Update;ldn-works ingame 2024-08-24 11:04:59
1893 010093A01305C000 Monster Hunter Rise Demo online-broken;ldn-works;demo playable 2022-10-18 23:04:17
1894 0100E21011446000 Monster Hunter Stories 2: Wings of Ruin services ingame 2022-07-10 19:27:30
1895 010042501329E000 MONSTER HUNTER STORIES 2: WINGS OF RUIN Trial Version demo playable 2022-11-13 22:20:26
2314 010077B00BDD8000 Professional Farmer: Nintendo Switch™ Edition slow playable 2020-12-16 13:38:19
2315 010018300C83A000 Professor Lupo and his Horrible Pets playable 2020-06-12 00:08:45
2316 0100D1F0132F6000 Professor Lupo: Ocean playable 2021-04-14 16:33:33
2317 0100c3a017834000 0100C3A017834000 Prodeus playable 2025-07-30 12:07:52
2318 0100BBD00976C000 Project Highrise: Architect's Edition playable 2022-08-10 17:19:12
2319 0100ACE00DAB6000 Project Nimbus: Complete Edition nvdec;UE4;vulkan-backend-bug playable 2022-08-10 17:35:43
2320 01002980140F6000 Project TRIANGLE STRATEGY™ Debut Demo UE4;demo playable 2022-10-24 21:40:27
2580 0100C610154CA000 Shadowrun: Hong Kong - Extended Edition gpu;Needs Update ingame 2022-10-04 20:53:09
2581 010000000EEF0000 Shadows 2: Perfidia playable 2020-08-07 12:43:46
2582 0100AD700CBBE000 Shadows of Adam playable 2021-01-11 13:35:58
2583 010037A01F96C000 Shadows of the Damned: Hella Remastered playable 2025-09-05 11:34:32
2584 01002A800C064000 Shadowverse Champions Battle playable 2022-10-02 22:59:29
2585 01003B90136DA000 Shadowverse: Champion's Battle crash nothing 2023-03-06 00:31:50
2586 0100820013612000 Shady Part of Me playable 2022-10-20 11:31:55
2979 0100EBA01548E000 The Cruel King and the Great Hero gpu;services ingame 2022-12-02 07:02:08
2980 010051800E922000 The Dark Crystal: Age of Resistance Tactics playable 2020-08-11 13:43:41
2981 01003DE00918E000 The Darkside Detective playable 2020-06-03 22:16:18
2982 010032B015D66000 The DioField Chronicle playable 2025-09-05 11:35:50
2983 01000A10041EA000 The Elder Scrolls V: Skyrim gpu;crash ingame 2024-07-14 03:21:31
2984 01004A9006B84000 The End Is Nigh playable 2020-06-01 11:26:45
2985 0100CA100489C000 The Escapists 2 nvdec playable 2020-09-24 12:31:31

View File

@@ -97,7 +97,7 @@ namespace Ryujinx.Graphics.Device
uint alignedOffset = index * RegisterSize;
DebugWrite(alignedOffset, data);
GetRefIntAlignedUncheck(index) = data;
SetIntAlignedUncheck(index, data);
_writeCallbacks[index]?.Invoke(data);
}
@@ -112,9 +112,7 @@ namespace Ryujinx.Graphics.Device
uint alignedOffset = index * RegisterSize;
DebugWrite(alignedOffset, data);
ref int storage = ref GetRefIntAlignedUncheck(index);
changed = storage != data;
storage = data;
changed = SetIntAlignedUncheckChanged(index, data);
_writeCallbacks[index]?.Invoke(data);
}
@@ -154,5 +152,24 @@ namespace Ryujinx.Graphics.Device
{
return ref Unsafe.Add(ref Unsafe.As<TState, int>(ref State), (nint)index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetIntAlignedUncheck(ulong index, int data)
{
Unsafe.Add(ref Unsafe.As<TState, int>(ref State), (nint)index) = data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool SetIntAlignedUncheckChanged(ulong index, int data)
{
ref int val = ref Unsafe.Add(ref Unsafe.As<TState, int>(ref State), (nint)index);
if (val == data)
{
return false;
}
val = data;
return true;
}
}
}

View File

@@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong size,
BufferStage stage,
bool sparseCompatible,
List<Buffer> baseBuffers)
RangeItem<Buffer>[] baseBuffers)
{
_context = context;
_physicalMemory = physicalMemory;
@@ -128,18 +128,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
List<IRegionHandle> baseHandles = null;
if (baseBuffers.Count != 0)
if (baseBuffers.Length != 0)
{
baseHandles = new List<IRegionHandle>();
foreach (Buffer buffer in baseBuffers)
foreach (RangeItem<Buffer> item in baseBuffers)
{
if (buffer._useGranular)
if (item.Value._useGranular)
{
baseHandles.AddRange((buffer._memoryTrackingGranular.GetHandles()));
baseHandles.AddRange((item.Value._memoryTrackingGranular.GetHandles()));
}
else
{
baseHandles.Add(buffer._memoryTracking);
baseHandles.Add(item.Value._memoryTracking);
}
}
}

View File

@@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
@@ -56,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="parent">Parent buffer</param>
/// <param name="stage">Initial buffer stage</param>
/// <param name="baseBuffers">Buffers to inherit state from</param>
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, List<Buffer> baseBuffers)
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem<Buffer>[] baseBuffers)
{
_size = (int)parent.Size;
_systemMemoryType = context.Capabilities.MemoryType;
@@ -72,7 +73,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
BufferStage storageFlags = stage & BufferStage.StorageMask;
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers.Count == 0)
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers.Length == 0)
{
_desiredType = BufferBackingType.DeviceMemory;
}
@@ -100,11 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
// TODO: Might be nice to force atomic access to be device local for any stage.
}
if (baseBuffers.Count != 0)
if (baseBuffers.Length != 0)
{
foreach (Buffer buffer in baseBuffers)
foreach (RangeItem<Buffer> item in baseBuffers)
{
CombineState(buffer.BackingState);
CombineState(item.Value.BackingState);
}
}
}

View File

@@ -81,13 +81,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
MemoryRange subRange = range.GetSubRange(index);
_buffers.Lock.EnterReadLock();
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(subRange.Address, subRange.Size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
RangeItem<Buffer> current = first;
while (last != null && current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
current.Value.Unmapped(subRange.Address, subRange.Size);
current = current.Next;
overlaps[i].Value.Unmapped(subRange.Address, subRange.Size);
}
_buffers.Lock.ExitReadLock();
@@ -489,10 +487,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param>
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{
Buffer newBuffer = null;
_buffers.Lock.EnterWriteLock();
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(address, size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (first is not null)
if (overlaps.Length != 0)
{
// The buffer already exists. We can just return the existing buffer
// if the buffer we need is fully contained inside the overlapping buffer.
@@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong endAddress = address + size;
if (first.Address > address || first.EndAddress < endAddress)
if (overlaps[0].Address > address || overlaps[0].EndAddress < endAddress)
{
bool anySparseCompatible = false;
@@ -515,52 +515,60 @@ namespace Ryujinx.Graphics.Gpu.Memory
// sequential memory.
// Allowing for 2 pages (rather than just one) is necessary to catch cases where the
// range crosses a page, and after alignment, ends having a size of 2 pages.
if (first == last &&
address >= first.Address &&
endAddress - first.EndAddress <= BufferAlignmentSize * 2)
if (overlaps.Length == 1 &&
address >= overlaps[0].Address &&
endAddress - overlaps[0].EndAddress <= BufferAlignmentSize * 2)
{
// Try to grow the buffer by 1.5x of its current size.
// This improves performance in the cases where the buffer is resized often by small amounts.
ulong existingSize = first.Value.Size;
ulong existingSize = overlaps[0].Value.Size;
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
size = Math.Max(size, growthSize);
endAddress = address + size;
(first, last) = _buffers.FindOverlaps(address, size);
overlaps = _buffers.FindOverlapsAsSpan(address, size);
}
address = Math.Min(address, first.Address);
endAddress = Math.Max(endAddress, last.EndAddress);
List<Buffer> overlaps = [];
address = Math.Min(address, overlaps[0].Address);
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
RangeItem<Buffer> current = first;
while (current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
anySparseCompatible |= current.Value.SparseCompatible;
overlaps.Add(current.Value);
_buffers.Remove(current.Value);
current = current.Next;
anySparseCompatible |= overlaps[i].Value.SparseCompatible;
}
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
_buffers.Lock.ExitWriteLock();
ulong newSize = endAddress - address;
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps);
_buffers.Add(newBuffer);
newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray);
}
else
{
_buffers.Lock.ExitWriteLock();
}
}
else
{
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
_buffers.Add(buffer);
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
}
_buffers.Lock.ExitWriteLock();
}
/// <summary>
@@ -575,67 +583,74 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
Buffer newBuffer = null;
_buffers.Lock.EnterWriteLock();
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(address, size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (first is not null)
if (overlaps.Length != 0)
{
// If the buffer already exists, make sure if covers the entire range,
// and make sure it is properly aligned, otherwise sparse mapping may fail.
ulong endAddress = address + size;
if (first.Address > address ||
first.EndAddress < endAddress ||
(first.Address & (alignment - 1)) != 0 ||
(!first.Value.SparseCompatible && sparseAligned))
if (overlaps[0].Address > address ||
overlaps[0].EndAddress < endAddress ||
(overlaps[0].Address & (alignment - 1)) != 0 ||
(!overlaps[0].Value.SparseCompatible && sparseAligned))
{
// We need to make sure the new buffer is properly aligned.
// However, after the range is aligned, it is possible that it
// overlaps more buffers, so try again after each extension
// and ensure we cover all overlaps.
RangeItem<Buffer> oldFirst;
endAddress = Math.Max(endAddress, last.EndAddress);
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
int oldOverlapCount;
do
{
address = Math.Min(address, first.Address);
address = Math.Min(address, overlaps[0].Address);
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
address &= ~(alignment - 1);
oldFirst = first;
(first, last) = _buffers.FindOverlaps(address, endAddress - address);
oldOverlapCount = overlaps.Length;
overlaps = _buffers.FindOverlapsAsSpan(address, endAddress - address);
}
while (oldFirst != first);
while (oldOverlapCount != overlaps.Length);
ulong newSize = endAddress - address;
List<Buffer> overlaps = [];
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
RangeItem<Buffer> current = first;
while (current != last.Next)
{
overlaps.Add(current.Value);
_buffers.Remove(current.Value);
current = current.Next;
}
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps);
_buffers.Lock.ExitWriteLock();
_buffers.Add(newBuffer);
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
}
else
{
_buffers.Lock.ExitWriteLock();
}
}
else
{
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
_buffers.Add(buffer);
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
}
_buffers.Lock.ExitWriteLock();
}
/// <summary>
@@ -648,13 +663,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="overlaps">Buffers overlapping the range</param>
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, List<Buffer> overlaps)
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem<Buffer>[] overlaps)
{
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
for (int index = 0; index < overlaps.Count; index++)
for (int index = 0; index < overlaps.Length; index++)
{
Buffer buffer = overlaps[index];
Buffer buffer = overlaps[index].Value;
int dstOffset = (int)(buffer.Address - newBuffer.Address);
@@ -882,7 +897,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
MemoryRange subRange = range.GetSubRange(i);
Buffer subBuffer = _buffers.FindOverlapFast(subRange.Address, subRange.Size).Value;
Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
@@ -930,7 +945,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0)
{
buffer = _buffers.FindOverlapFast(address, size).Value;
buffer = _buffers.FindOverlap(address, size).Value;
buffer.CopyFromDependantVirtualBuffers();
buffer.SynchronizeMemory(address, size);
@@ -980,7 +995,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (size != 0)
{
Buffer buffer = _buffers.FindOverlapFast(address, size).Value;
Buffer buffer = _buffers.FindOverlap(address, size).Value;
if (copyBackVirtual)
{

View File

@@ -80,8 +80,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
private BufferMigration _source;
private BufferModifiedRangeList _migrationTarget;
private List<RangeItem<BufferModifiedRange>> _overlaps;
/// <summary>
/// Whether the modified range list has any entries or not.
@@ -108,7 +106,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context = context;
_parent = parent;
_flushAction = flushAction;
_overlaps = [];
}
/// <summary>
@@ -122,12 +119,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
Lock.EnterReadLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
BufferModifiedRange overlap = current.Value;
BufferModifiedRange overlap = overlaps[i].Value;
if (overlap.Address > address)
{
@@ -138,7 +134,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Remaining region is after this overlap.
size -= overlap.EndAddress - address;
address = overlap.EndAddress;
current = current.Next;
}
Lock.ExitReadLock();
@@ -158,12 +153,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size of the modified region in bytes</param>
public void SignalModified(ulong address, ulong size)
{
// We may overlap with some existing modified regions. They must be cut into by the new entry.
Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
ulong endAddress = address + size;
ulong syncNumber = _context.SyncNumber;
// We may overlap with some existing modified regions. They must be cut into by the new entry.
Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
if (first is null)
{
@@ -172,8 +166,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
return;
}
if (first == last)
{
if (first.Address == address && first.EndAddress == endAddress)
@@ -260,19 +252,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
{
Lock.EnterReadLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
BufferModifiedRange overlap = current.Value;
BufferModifiedRange overlap = overlaps[i].Value;
if (overlap.SyncNumber == syncNumber)
{
rangeAction(overlap.Address, overlap.Size);
}
current = current.Next;
}
Lock.ExitReadLock();
@@ -288,22 +277,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
// We use the non-span method here because keeping the lock will cause a deadlock.
Lock.EnterReadLock();
_overlaps.Clear();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
{
_overlaps.Add(current);
current = current.Next;
}
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size);
Lock.ExitReadLock();
for (int i = 0; i < _overlaps.Count; i++)
for (int i = 0; i < overlaps.Length; i++)
{
BufferModifiedRange overlap = _overlaps[i].Value;
BufferModifiedRange overlap = overlaps[i].Value;
rangeAction(overlap.Address, overlap.Size);
}
}
@@ -404,8 +383,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong endAddress = address + size;
ulong currentSync = _context.SyncNumber;
List<RangeItem<BufferModifiedRange>> overlaps = [];
// Range list must be consistent for this operation
if (_migrationTarget != null)
{
@@ -416,16 +393,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
Lock.EnterWriteLock();
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
{
overlaps.Add(current);
current = current.Next;
}
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size);
int rangeCount = overlaps.Count;
int rangeCount = overlaps.Length;
if (rangeCount == 0)
{
@@ -503,6 +473,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ranges._migrationTarget = this;
Lock.EnterWriteLock();
foreach (BufferModifiedRange range in inheritRanges)
{
Add(range);
@@ -582,7 +553,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
ulong endAddress = address + size;
Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
if (first is null)
{

View File

@@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong originalVa = gpuVa;
_virtualRanges.Lock.EnterWriteLock();
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlaps(gpuVa, size);
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
if (first is not null)
{

View File

@@ -865,7 +865,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
lock (_threadingLock)
{
thread.ProcessListNode = _threads.AddLast(thread);
_threads.AddLast(thread.ProcessListNode);
}
}
@@ -1227,7 +1227,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
thread.Context.RequestInterrupt();
if (!thread.DebugHalt.WaitOne(TimeSpan.FromMilliseconds(50)))
if (!thread.DebugHalt.Wait(TimeSpan.FromMilliseconds(50)))
{
Logger.Warning?.Print(LogClass.Kernel, $"Failed to suspend thread {thread.ThreadUid} in time.");
}

View File

@@ -13,16 +13,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
Monitor.Exit(mutex);
currentThread.Withholder = threadList;
currentThread.Reschedule(ThreadSchedState.Paused);
currentThread.WithholderNode = threadList.AddLast(currentThread);
if (currentThread.TerminationRequested)
{
threadList.Remove(currentThread.WithholderNode);
currentThread.Reschedule(ThreadSchedState.Running);
currentThread.Withholder = null;
@@ -31,6 +23,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
else
{
currentThread.Withholder = threadList;
currentThread.Reschedule(ThreadSchedState.Paused);
threadList.AddLast(currentThread.WithholderNode);
if (timeout > 0)
{
context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);

View File

@@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// even if they are not scheduled on guest cores.
if (currentThread != null && !currentThread.IsSchedulable && currentThread.Context.Running)
{
currentThread.SchedulerWaitEvent.WaitOne();
currentThread.SchedulerWaitEvent.Wait();
}
}
}

View File

@@ -194,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return;
}
thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread);
SuggestedQueue(prio, core).AddFirst(thread.SiblingsPerCore[core]);
_suggestedPrioritiesPerCore[core] |= 1L << prio;
}
@@ -223,7 +223,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return;
}
thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread);
ScheduledQueue(prio, core).AddLast(thread.SiblingsPerCore[core]);
_scheduledPrioritiesPerCore[core] |= 1L << prio;
}
@@ -235,7 +235,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return;
}
thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread);
ScheduledQueue(prio, core).AddFirst(thread.SiblingsPerCore[core]);
_scheduledPrioritiesPerCore[core] |= 1L << prio;
}
@@ -251,7 +251,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
queue.Remove(thread.SiblingsPerCore[core]);
thread.SiblingsPerCore[core] = queue.AddLast(thread);
queue.AddLast(thread.SiblingsPerCore[core]);
return queue.First.Value;
}

View File

@@ -318,11 +318,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (nextThread == null)
{
ActivateIdleThread();
currentThread.SchedulerWaitEvent.WaitOne();
currentThread.SchedulerWaitEvent.Wait();
}
else
{
WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
nextThread.SchedulerWaitEvent.Set();
currentThread.SchedulerWaitEvent.Wait();
}
}
else

View File

@@ -39,9 +39,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public const int MaxWaitSyncObjects = 64;
private ManualResetEvent _schedulerWaitEvent;
private ManualResetEventSlim _schedulerWaitEvent;
public ManualResetEvent SchedulerWaitEvent => _schedulerWaitEvent;
public ManualResetEventSlim SchedulerWaitEvent => _schedulerWaitEvent;
public Thread HostThread { get; private set; }
@@ -93,6 +93,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private LinkedListNode<KThread> _mutexWaiterNode;
private readonly LinkedList<KThread> _pinnedWaiters;
private LinkedListNode<KThread> _pinnedWaiterNode;
public KThread MutexOwner { get; private set; }
@@ -135,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly Lock _activityOperationLock = new();
internal readonly ManualResetEvent DebugHalt = new(false);
internal readonly ManualResetEventSlim DebugHalt = new(false);
public KThread(KernelContext context) : base(context)
{
@@ -144,8 +145,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
for (int i = 0; i < SiblingsPerCore.Length; i++)
{
SiblingsPerCore[i] = new LinkedListNode<KThread>(this);
}
_mutexWaiters = [];
_pinnedWaiters = [];
WithholderNode = new LinkedListNode<KThread>(this);
ProcessListNode = new LinkedListNode<KThread>(this);
_mutexWaiterNode = new LinkedListNode<KThread>(this);
_pinnedWaiterNode = new LinkedListNode<KThread>(this);
}
public Result Initialize(
@@ -631,7 +642,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
break;
}
_pinnedWaiters.AddLast(currentThread);
_pinnedWaiters.AddLast(_pinnedWaiterNode);
currentThread.Reschedule(ThreadSchedState.Paused);
}
@@ -848,7 +859,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return KernelResult.ThreadTerminating;
}
_pinnedWaiters.AddLast(currentThread);
_pinnedWaiters.AddLast(_pinnedWaiterNode);
currentThread.Reschedule(ThreadSchedState.Paused);
}
@@ -1262,7 +1273,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
if (_schedulerWaitEvent == null)
{
ManualResetEvent schedulerWaitEvent = new(false);
ManualResetEventSlim schedulerWaitEvent = new(false);
if (Interlocked.Exchange(ref _schedulerWaitEvent, schedulerWaitEvent) == null)
{
@@ -1277,7 +1288,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private void ThreadStart()
{
_schedulerWaitEvent.WaitOne();
_schedulerWaitEvent.Wait();
DebugHalt.Reset();
KernelStatic.SetKernelContext(KernelContext, this);

View File

@@ -12,9 +12,6 @@ namespace Ryujinx.Memory.Range
/// <typeparam name="T">Type of the range.</typeparam>
public unsafe class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange
{
private readonly Dictionary<ulong, RangeItem<T>> _quickAccess = new(AddressEqualityComparer.Comparer);
private readonly Dictionary<ulong, RangeItem<T>> _fastQuickAccess = new(AddressEqualityComparer.Comparer);
public readonly ReaderWriterLockSlim Lock = new();
/// <summary>
@@ -44,8 +41,6 @@ namespace Ryujinx.Memory.Range
RangeItem<T> rangeItem = new(item);
Insert(index, rangeItem);
_quickAccess.Add(item.Address, rangeItem);
}
/// <summary>
@@ -71,15 +66,7 @@ namespace Ryujinx.Memory.Range
Items[index + 1].Previous = rangeItem;
}
foreach (ulong addr in Items[index].QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
Items[index] = rangeItem;
_quickAccess[item.Address] = rangeItem;
return true;
}
@@ -108,19 +95,8 @@ namespace Ryujinx.Memory.Range
Items[index + 1].Previous = rangeItem;
}
foreach (ulong addr in item.QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
Items[index] = rangeItem;
if (item.Address != rangeItem.Address)
_quickAccess.Remove(item.Address);
_quickAccess[rangeItem.Address] = rangeItem;
return true;
}
@@ -196,14 +172,6 @@ namespace Ryujinx.Memory.Range
if (index >= 0 && Items[index].Value.Equals(item))
{
_quickAccess.Remove(item.Address);
foreach (ulong addr in Items[index].QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
RemoveAt(index);
return true;
@@ -232,16 +200,6 @@ namespace Ryujinx.Memory.Range
(int startIndex, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
for (int i = startIndex; i < endIndex; i++)
{
_quickAccess.Remove(Items[i].Address);
foreach (ulong addr in Items[i].QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
}
if (endIndex < Count)
{
Items[endIndex].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
@@ -279,13 +237,6 @@ namespace Ryujinx.Memory.Range
while (Items[endIndex] is not null && Items[endIndex].Address < address + size)
{
_quickAccess.Remove(Items[endIndex].Address);
foreach (ulong addr in Items[endIndex].QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
if (endIndex == Count - 1)
{
break;
@@ -321,8 +272,6 @@ namespace Ryujinx.Memory.Range
{
Lock.EnterWriteLock();
Count = 0;
_quickAccess.Clear();
_fastQuickAccess.Clear();
Lock.ExitWriteLock();
}
@@ -344,7 +293,7 @@ namespace Ryujinx.Memory.Range
// So we need to return both the split 0-1 and 1-2 ranges.
Lock.EnterWriteLock();
(RangeItem<T> first, RangeItem<T> last) = FindOverlaps(address, size);
(RangeItem<T> first, RangeItem<T> last) = FindOverlapsAsNodes(address, size);
list = new List<T>();
if (first is null)
@@ -436,11 +385,6 @@ namespace Ryujinx.Memory.Range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlap(ulong address, ulong size)
{
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap))
{
return overlap;
}
int index = BinarySearchLeftEdge(address, address + size);
if (index < 0)
@@ -448,12 +392,6 @@ namespace Ryujinx.Memory.Range
return null;
}
if (Items[index].Address < address)
{
_quickAccess.TryAdd(address, Items[index]);
Items[index].QuickAccessAddresses.Add(address);
}
return Items[index];
}
@@ -466,28 +404,12 @@ namespace Ryujinx.Memory.Range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
{
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap) || _fastQuickAccess.TryGetValue(address, out overlap))
{
return overlap;
}
int index = BinarySearch(address, address + size);
if (index < 0)
{
return null;
}
if (Items[index].Address < address)
{
_quickAccess.TryAdd(address, Items[index]);
}
else
{
_fastQuickAccess.TryAdd(address, Items[index]);
}
Items[index].QuickAccessAddresses.Add(address);
return Items[index];
}
@@ -499,18 +421,8 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param>
/// <returns>The first and last overlapping items, or null if none are found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (RangeItem<T>, RangeItem<T>) FindOverlaps(ulong address, ulong size)
public (RangeItem<T>, RangeItem<T>) FindOverlapsAsNodes(ulong address, ulong size)
{
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap))
{
if (overlap.Next is null || overlap.Next.Address >= address + size)
{
return (overlap, overlap);
}
return (overlap, Items[BinarySearchRightEdge(address, address + size)]);
}
(int index, int endIndex) = BinarySearchEdges(address, address + size);
if (index < 0)
@@ -518,13 +430,45 @@ namespace Ryujinx.Memory.Range
return (null, null);
}
if (Items[index].Address < address)
return (Items[index], Items[endIndex - 1]);
}
public RangeItem<T>[] FindOverlapsAsArray(ulong address, ulong size)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
RangeItem<T>[] result;
if (index < 0)
{
_quickAccess.TryAdd(address, Items[index]);
Items[index].QuickAccessAddresses.Add(address);
result = [];
}
else
{
result = new RangeItem<T>[endIndex - index];
Array.Copy(Items, index, result, 0, endIndex - index);
}
return (Items[index], Items[endIndex - 1]);
return result;
}
public Span<RangeItem<T>> FindOverlapsAsSpan(ulong address, ulong size)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
Span<RangeItem<T>> result;
if (index < 0)
{
result = [];
}
else
{
result = Items.AsSpan().Slice(index, endIndex - index);
}
return result;
}
public override IEnumerator<T> GetEnumerator()

View File

@@ -1,4 +1,5 @@
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
namespace Ryujinx.Memory.Tracking
@@ -79,12 +80,10 @@ namespace Ryujinx.Memory.Tracking
{
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock();
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(va, size);
RangeItem<VirtualRegion> current = first;
while (last != null && current != last.Next)
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++)
{
VirtualRegion region = current.Value;
VirtualRegion region = overlaps[i].Value;
// If the region has been fully remapped, signal that it has been mapped again.
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
@@ -94,7 +93,6 @@ namespace Ryujinx.Memory.Tracking
}
region.UpdateProtection();
current = current.Next;
}
regions.Lock.ExitReadLock();
}
@@ -118,15 +116,11 @@ namespace Ryujinx.Memory.Tracking
{
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock();
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(va, size);
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
RangeItem<VirtualRegion> current = first;
while (last != null && current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
VirtualRegion region = current.Value;
region.SignalMappingChanged(false);
current = current.Next;
overlaps[i].Value.SignalMappingChanged(false);
}
regions.Lock.ExitReadLock();
}
@@ -303,21 +297,13 @@ namespace Ryujinx.Memory.Tracking
lock (TrackingLock)
{
NonOverlappingRangeList<VirtualRegion> regions = guest ? _guestVirtualRegions : _virtualRegions;
List<RangeItem<VirtualRegion>> overlaps = [];
// We use the non-span method here because keeping the lock will cause a deadlock.
regions.Lock.EnterReadLock();
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(address, size);
RangeItem<VirtualRegion> current = first;
while (last != null && current != last.Next)
{
overlaps.Add(current);
current = current.Next;
}
RangeItem<VirtualRegion>[] overlaps = regions.FindOverlapsAsArray(address, size);
regions.Lock.ExitReadLock();
if (first is null && !precise)
if (overlaps.Length == 0 && !precise)
{
if (_memoryManager.IsRangeMapped(address, size))
{
@@ -338,7 +324,7 @@ namespace Ryujinx.Memory.Tracking
size += (ulong)_pageSize;
}
for (int i = 0; i < overlaps.Count; i++)
for (int i = 0; i < overlaps.Length; i++)
{
VirtualRegion region = overlaps[i].Value;

View File

@@ -428,7 +428,7 @@ namespace Ryujinx.Headless
[Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enables the GDB stub so that a developer can attach a debugger to the emulated process.")]
public bool EnableGdbStub { get; set; }
[Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
[Option("gdb-stub-port", Required = false, Default = (ushort)55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
public ushort GdbStubPort { get; set; }
[Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]