Compare commits
52 Commits
Canary-1.2
...
615683c73d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
615683c73d | ||
|
|
9c226dcc7a | ||
|
|
30a534edcd | ||
|
|
1d88771d1b | ||
|
|
4e8157688e | ||
|
|
5085af0050 | ||
|
|
2c8edaf89e | ||
|
|
965fb9dd5f | ||
|
|
e2a5e69f4c | ||
|
|
7f27b791f8 | ||
|
|
aa8ba8b503 | ||
|
|
a4211fec33 | ||
|
|
54b233dd78 | ||
|
|
3ca8618f5f | ||
|
|
d1da937fce | ||
|
|
4a8f98126f | ||
|
|
e55629a908 | ||
|
|
c638a7daf8 | ||
|
|
5e5e180fea | ||
|
|
131fe71205 | ||
|
|
6af388c623 | ||
|
|
45cec4e7cf | ||
|
|
479b38f035 | ||
|
|
3ecc7819cc | ||
|
|
4b1d94ccd8 | ||
|
|
4ae9f1c0d2 | ||
|
|
717851985e | ||
|
|
bd08a111a8 | ||
|
|
1972a47f39 | ||
|
|
222ceb818b | ||
|
|
b0fcc5bee1 | ||
|
|
820e8f7375 | ||
|
|
e8a7d5b0b7 | ||
|
|
fafb99c702 | ||
|
|
df9e6e4812 | ||
|
|
566f3d079a | ||
|
|
d7707d4176 | ||
|
|
7a9b62884a | ||
|
|
de9faf183a | ||
|
|
0bf7c5dfa2 | ||
|
|
11bc32d98e | ||
|
|
063430ea16 | ||
|
|
65f08caaa3 | ||
|
|
f225b18c05 | ||
|
|
d8549f687b | ||
|
|
5ab50680b4 | ||
|
|
a0edc5c2b0 | ||
|
|
158ea7b4d6 | ||
|
|
8bc3de8303 | ||
|
|
c812106611 | ||
|
|
11e4d8f970 | ||
|
|
774edb7b29 |
@@ -332,6 +332,7 @@
|
|||||||
0100E680149DC000,"Arcaea",,playable,2023-03-16 19:31:21
|
0100E680149DC000,"Arcaea",,playable,2023-03-16 19:31:21
|
||||||
01003C2010C78000,"Archaica: The Path Of Light",crash,nothing,2020-10-16 13:22:26
|
01003C2010C78000,"Archaica: The Path Of Light",crash,nothing,2020-10-16 13:22:26
|
||||||
01004DA012976000,"Area 86",,playable,2020-12-16 16:45:52
|
01004DA012976000,"Area 86",,playable,2020-12-16 16:45:52
|
||||||
|
01008d8006a6a000,"Arena of Valor",crash,boots,2025-02-03 22:19:34
|
||||||
0100691013C46000,"ARIA CHRONICLE",,playable,2022-11-16 13:50:55
|
0100691013C46000,"ARIA CHRONICLE",,playable,2022-11-16 13:50:55
|
||||||
0100D4A00B284000,"ARK: Survival Evolved",gpu;nvdec;online-broken;UE4;ldn-untested,ingame,2024-04-16 00:53:56
|
0100D4A00B284000,"ARK: Survival Evolved",gpu;nvdec;online-broken;UE4;ldn-untested,ingame,2024-04-16 00:53:56
|
||||||
0100C56012C96000,"Arkanoid vs. Space Invaders",services,ingame,2021-01-21 12:50:30
|
0100C56012C96000,"Arkanoid vs. Space Invaders",services,ingame,2021-01-21 12:50:30
|
||||||
@@ -426,6 +427,7 @@
|
|||||||
0100E48013A34000,"Balan Wonderworld Demo",gpu;services;UE4;demo,ingame,2023-02-16 20:05:07
|
0100E48013A34000,"Balan Wonderworld Demo",gpu;services;UE4;demo,ingame,2023-02-16 20:05:07
|
||||||
0100CD801CE5E000,"Balatro",,ingame,2024-04-21 02:01:53
|
0100CD801CE5E000,"Balatro",,ingame,2024-04-21 02:01:53
|
||||||
010010A00DA48000,"Baldur's Gate and Baldur's Gate II: Enhanced Editions",32-bit,playable,2022-09-12 23:52:15
|
010010A00DA48000,"Baldur's Gate and Baldur's Gate II: Enhanced Editions",32-bit,playable,2022-09-12 23:52:15
|
||||||
|
0100fd1014726000,"Baldur's Gate: Dark Alliance",ldn-untested,ingame,2025-02-03 22:21:00
|
||||||
0100BC400FB64000,"Balthazar's Dream",,playable,2022-09-13 00:13:22
|
0100BC400FB64000,"Balthazar's Dream",,playable,2022-09-13 00:13:22
|
||||||
01008D30128E0000,"Bamerang",,playable,2022-10-26 00:29:39
|
01008D30128E0000,"Bamerang",,playable,2022-10-26 00:29:39
|
||||||
010013C010C5C000,"Banner of the Maid",,playable,2021-06-14 15:23:37
|
010013C010C5C000,"Banner of the Maid",,playable,2021-06-14 15:23:37
|
||||||
@@ -528,6 +530,7 @@
|
|||||||
01005950022EC000,"Blade Strangers",nvdec,playable,2022-07-17 19:02:43
|
01005950022EC000,"Blade Strangers",nvdec,playable,2022-07-17 19:02:43
|
||||||
0100DF0011A6A000,"Bladed Fury",,playable,2022-10-26 11:36:26
|
0100DF0011A6A000,"Bladed Fury",,playable,2022-10-26 11:36:26
|
||||||
0100CFA00CC74000,"Blades of Time",deadlock;online,boots,2022-07-17 19:19:58
|
0100CFA00CC74000,"Blades of Time",deadlock;online,boots,2022-07-17 19:19:58
|
||||||
|
01003d700dd8a000,"Blades",,boots,2025-02-03 22:22:00
|
||||||
01006CC01182C000,"Blair Witch",nvdec;UE4,playable,2022-10-01 14:06:16
|
01006CC01182C000,"Blair Witch",nvdec;UE4,playable,2022-10-01 14:06:16
|
||||||
010039501405E000,"Blanc",gpu;slow,ingame,2023-02-22 14:00:13
|
010039501405E000,"Blanc",gpu;slow,ingame,2023-02-22 14:00:13
|
||||||
0100698009C6E000,"Blasphemous",nvdec,playable,2021-03-01 12:15:31
|
0100698009C6E000,"Blasphemous",nvdec,playable,2021-03-01 12:15:31
|
||||||
@@ -955,7 +958,7 @@
|
|||||||
010012800EBAE000,"Disney TSUM TSUM FESTIVAL",crash,menus,2020-07-14 14:05:28
|
010012800EBAE000,"Disney TSUM TSUM FESTIVAL",crash,menus,2020-07-14 14:05:28
|
||||||
01009740120FE000,"DISTRAINT 2",,playable,2020-09-03 16:08:12
|
01009740120FE000,"DISTRAINT 2",,playable,2020-09-03 16:08:12
|
||||||
010075B004DD2000,"DISTRAINT: Deluxe Edition",,playable,2020-06-15 23:42:24
|
010075B004DD2000,"DISTRAINT: Deluxe Edition",,playable,2020-06-15 23:42:24
|
||||||
010027400CDC6000,"Divinity: Original Sin 2 - Definitive Edition",services;crash;online-broken;regression,menus,2023-08-13 17:20:03
|
010027400CDC6000,"Divinity: Original Sin 2 - Definitive Edition",services;crash;online-broken;regression,ingame,2025-02-03 22:12:30
|
||||||
01001770115C8000,"Dodo Peak",nvdec;UE4,playable,2022-10-04 16:13:05
|
01001770115C8000,"Dodo Peak",nvdec;UE4,playable,2022-10-04 16:13:05
|
||||||
010077B0100DA000,"Dogurai",,playable,2020-10-04 02:40:16
|
010077B0100DA000,"Dogurai",,playable,2020-10-04 02:40:16
|
||||||
010048100D51A000,"Dokapon Up! Mugen no Roulette",gpu;Needs Update,menus,2022-12-08 19:39:10
|
010048100D51A000,"Dokapon Up! Mugen no Roulette",gpu;Needs Update,menus,2022-12-08 19:39:10
|
||||||
@@ -1654,7 +1657,7 @@
|
|||||||
0100A73006E74000,"Legendary Eleven",,playable,2021-06-08 12:09:03
|
0100A73006E74000,"Legendary Eleven",,playable,2021-06-08 12:09:03
|
||||||
0100A7700B46C000,"Legendary Fishing",online,playable,2021-04-14 15:08:46
|
0100A7700B46C000,"Legendary Fishing",online,playable,2021-04-14 15:08:46
|
||||||
0100739018020000,"LEGO® 2K Drive",gpu;ldn-works,ingame,2024-04-09 02:05:12
|
0100739018020000,"LEGO® 2K Drive",gpu;ldn-works,ingame,2024-04-09 02:05:12
|
||||||
01003A30012C0000,"LEGO® CITY Undercover",nvdec,playable,2024-09-30 08:44:27
|
010085500130a000,"LEGO® CITY Undercover",nvdec,playable,2024-09-30 08:44:27
|
||||||
010070D009FEC000,"LEGO® DC Super-Villains",,playable,2021-05-27 18:10:37
|
010070D009FEC000,"LEGO® DC Super-Villains",,playable,2021-05-27 18:10:37
|
||||||
010052A00B5D2000,"LEGO® Harry Potter™ Collection",crash,ingame,2024-01-31 10:28:07
|
010052A00B5D2000,"LEGO® Harry Potter™ Collection",crash,ingame,2024-01-31 10:28:07
|
||||||
010073C01AF34000,"LEGO® Horizon Adventures™",vulkan-backend-bug;opengl-backend-bug;UE4,ingame,2025-01-07 04:24:56
|
010073C01AF34000,"LEGO® Horizon Adventures™",vulkan-backend-bug;opengl-backend-bug;UE4,ingame,2025-01-07 04:24:56
|
||||||
@@ -1913,6 +1916,7 @@
|
|||||||
010073E008E6E000,"Mugsters",,playable,2021-01-28 17:57:17
|
010073E008E6E000,"Mugsters",,playable,2021-01-28 17:57:17
|
||||||
0100A8400471A000,"MUJO",,playable,2020-05-08 16:31:04
|
0100A8400471A000,"MUJO",,playable,2020-05-08 16:31:04
|
||||||
0100211005E94000,"Mulaka",,playable,2021-01-28 18:07:20
|
0100211005E94000,"Mulaka",,playable,2021-01-28 18:07:20
|
||||||
|
01008e2013fb4000,"Multi Quiz",ldn-untested,ingame,2025-02-03 22:26:00
|
||||||
010038B00B9AE000,"Mummy Pinball",,playable,2022-08-05 16:08:11
|
010038B00B9AE000,"Mummy Pinball",,playable,2022-08-05 16:08:11
|
||||||
01008E200C5C2000,"Muse Dash",,playable,2020-06-06 14:41:29
|
01008E200C5C2000,"Muse Dash",,playable,2020-06-06 14:41:29
|
||||||
010035901046C000,"Mushroom Quest",,playable,2020-05-17 13:07:08
|
010035901046C000,"Mushroom Quest",,playable,2020-05-17 13:07:08
|
||||||
@@ -2028,6 +2032,7 @@
|
|||||||
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
|
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
|
||||||
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
|
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
|
||||||
0100C9A00ECE6000,"Nintendo 64™ – Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
|
0100C9A00ECE6000,"Nintendo 64™ – Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
|
||||||
|
0100e0601c632000,"Nintendo 64™ – Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
|
||||||
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
|
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
|
||||||
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
|
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
|
||||||
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
|
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
|
||||||
@@ -2532,7 +2537,7 @@
|
|||||||
0100C3E00B700000,"SEGA AGES Space Harrier",,playable,2021-01-11 12:57:40
|
0100C3E00B700000,"SEGA AGES Space Harrier",,playable,2021-01-11 12:57:40
|
||||||
010054400D2E6000,"SEGA AGES Virtua Racing",online-broken,playable,2023-01-29 17:08:39
|
010054400D2E6000,"SEGA AGES Virtua Racing",online-broken,playable,2023-01-29 17:08:39
|
||||||
01001E700AC60000,"SEGA AGES Wonder Boy: Monster Land",online,playable,2021-05-05 16:28:25
|
01001E700AC60000,"SEGA AGES Wonder Boy: Monster Land",online,playable,2021-05-05 16:28:25
|
||||||
0100B3C014BDA000,"SEGA Genesis™ – Nintendo Switch Online",crash;regression,nothing,2022-04-11 07:27:21
|
0100B3C014BDA000,"SEGA Genesis™ – Nintendo Switch Online",crash;regression,ingame,2025-02-03 22:13:30
|
||||||
0100F7300B24E000,"SEGA Mega Drive Classics",online,playable,2021-01-05 11:08:00
|
0100F7300B24E000,"SEGA Mega Drive Classics",online,playable,2021-01-05 11:08:00
|
||||||
01009840046BC000,"Semispheres",,playable,2021-01-06 23:08:31
|
01009840046BC000,"Semispheres",,playable,2021-01-06 23:08:31
|
||||||
0100D1800D902000,"SENRAN KAGURA Peach Ball",,playable,2021-06-03 15:12:10
|
0100D1800D902000,"SENRAN KAGURA Peach Ball",,playable,2021-06-03 15:12:10
|
||||||
@@ -2964,6 +2969,7 @@
|
|||||||
0100C38004DCC000,"The Flame In The Flood: Complete Edition",gpu;nvdec;UE4,ingame,2022-08-22 16:23:49
|
0100C38004DCC000,"The Flame In The Flood: Complete Edition",gpu;nvdec;UE4,ingame,2022-08-22 16:23:49
|
||||||
010007700D4AC000,"The Forbidden Arts",,playable,2021-01-26 16:26:24
|
010007700D4AC000,"The Forbidden Arts",,playable,2021-01-26 16:26:24
|
||||||
010030700CBBC000,"The friends of Ringo Ishikawa",,playable,2022-08-22 16:33:17
|
010030700CBBC000,"The friends of Ringo Ishikawa",,playable,2022-08-22 16:33:17
|
||||||
|
0100b620139d8000,"The Game of Life 2",ldn-untested,ingame,2025-02-03 22:30:00
|
||||||
01006350148DA000,"The Gardener and the Wild Vines",gpu,ingame,2024-04-29 16:32:10
|
01006350148DA000,"The Gardener and the Wild Vines",gpu,ingame,2024-04-29 16:32:10
|
||||||
0100B13007A6A000,"The Gardens Between",,playable,2021-01-29 16:16:53
|
0100B13007A6A000,"The Gardens Between",,playable,2021-01-29 16:16:53
|
||||||
010036E00FB20000,"The Great Ace Attorney Chronicles",,playable,2023-06-22 21:26:29
|
010036E00FB20000,"The Great Ace Attorney Chronicles",,playable,2023-06-22 21:26:29
|
||||||
@@ -2981,6 +2987,8 @@
|
|||||||
010015D003EE4000,"The Jackbox Party Pack 2",online-working,playable,2022-08-22 18:23:40
|
010015D003EE4000,"The Jackbox Party Pack 2",online-working,playable,2022-08-22 18:23:40
|
||||||
0100CC80013D6000,"The Jackbox Party Pack 3",slow;online-working,playable,2022-08-22 18:41:06
|
0100CC80013D6000,"The Jackbox Party Pack 3",slow;online-working,playable,2022-08-22 18:41:06
|
||||||
0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34
|
0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34
|
||||||
|
01006fe0096ac000,"The Jackbox Party Pack 5",ldn-untested,boots,2025-02-03 22:32:00
|
||||||
|
01005a400db52000,"The Jackbox Party Pack 6",ldn-untested,boots,2025-02-03 22:32:00
|
||||||
010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41
|
010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41
|
||||||
01006BC00B188000,"The Journey Down: Chapter Three",nvdec,playable,2021-02-24 13:45:27
|
01006BC00B188000,"The Journey Down: Chapter Three",nvdec,playable,2021-02-24 13:45:27
|
||||||
01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13
|
01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13
|
||||||
@@ -3159,6 +3167,7 @@
|
|||||||
010055E00CA68000,"Trine 4: The Nightmare Prince",gpu,nothing,2025-01-07 05:47:46
|
010055E00CA68000,"Trine 4: The Nightmare Prince",gpu,nothing,2025-01-07 05:47:46
|
||||||
0100D9000A930000,"Trine Enchanted Edition",ldn-untested;nvdec,playable,2021-06-03 11:28:15
|
0100D9000A930000,"Trine Enchanted Edition",ldn-untested;nvdec,playable,2021-06-03 11:28:15
|
||||||
01002D7010A54000,"Trinity Trigger",crash,ingame,2023-03-03 03:09:09
|
01002D7010A54000,"Trinity Trigger",crash,ingame,2023-03-03 03:09:09
|
||||||
|
010020700a5e0000,"TRIVIAL PURSUIT Live!",ldn-untested,ingame,2025-02-03 22:35:00
|
||||||
0100868013FFC000,"TRIVIAL PURSUIT Live! 2",,boots,2022-12-19 00:04:33
|
0100868013FFC000,"TRIVIAL PURSUIT Live! 2",,boots,2022-12-19 00:04:33
|
||||||
0100F78002040000,"Troll and I™",gpu;nvdec,ingame,2021-06-04 16:58:50
|
0100F78002040000,"Troll and I™",gpu;nvdec,ingame,2021-06-04 16:58:50
|
||||||
0100145011008000,"Trollhunters: Defenders of Arcadia",gpu;nvdec,ingame,2020-11-30 13:27:09
|
0100145011008000,"Trollhunters: Defenders of Arcadia",gpu;nvdec,ingame,2020-11-30 13:27:09
|
||||||
@@ -3208,6 +3217,7 @@
|
|||||||
0100AB2010B4C000,"Unlock The King",,playable,2020-09-01 13:58:27
|
0100AB2010B4C000,"Unlock The King",,playable,2020-09-01 13:58:27
|
||||||
0100A3E011CB0000,"Unlock the King 2",,playable,2021-06-15 20:43:55
|
0100A3E011CB0000,"Unlock the King 2",,playable,2021-06-15 20:43:55
|
||||||
01005AA00372A000,"UNO® for Nintendo Switch",nvdec;ldn-untested,playable,2022-07-28 14:49:47
|
01005AA00372A000,"UNO® for Nintendo Switch",nvdec;ldn-untested,playable,2022-07-28 14:49:47
|
||||||
|
0100b6e012ebe000,"UNO",ldn-untested,ingame,2025-02-03 22:40:00
|
||||||
0100E5D00CC0C000,"Unravel Two",nvdec,playable,2024-05-23 15:45:05
|
0100E5D00CC0C000,"Unravel Two",nvdec,playable,2024-05-23 15:45:05
|
||||||
010001300CC4A000,"Unruly Heroes",,playable,2021-01-07 18:09:31
|
010001300CC4A000,"Unruly Heroes",,playable,2021-01-07 18:09:31
|
||||||
0100B410138C0000,"Unspottable",,playable,2022-10-25 19:28:49
|
0100B410138C0000,"Unspottable",,playable,2022-10-25 19:28:49
|
||||||
@@ -3372,6 +3382,7 @@
|
|||||||
0100F47016F26000,"Yomawari 3",,playable,2022-05-10 08:26:51
|
0100F47016F26000,"Yomawari 3",,playable,2022-05-10 08:26:51
|
||||||
010012F00B6F2000,"Yomawari: The Long Night Collection",,playable,2022-09-03 14:36:59
|
010012F00B6F2000,"Yomawari: The Long Night Collection",,playable,2022-09-03 14:36:59
|
||||||
0100CC600ABB2000,"Yonder: The Cloud Catcher Chronicles (Retail Only)",,playable,2021-01-28 14:06:25
|
0100CC600ABB2000,"Yonder: The Cloud Catcher Chronicles (Retail Only)",,playable,2021-01-28 14:06:25
|
||||||
|
0100534009ff2000,"Yonder: The Cloud Catcher Chronicles",,playable,2025-02-03 22:19:13
|
||||||
0100BE50042F6000,"Yono and the Celestial Elephants",,playable,2021-01-28 18:23:58
|
0100BE50042F6000,"Yono and the Celestial Elephants",,playable,2021-01-28 18:23:58
|
||||||
0100F110029C8000,"Yooka-Laylee",,playable,2021-01-28 14:21:45
|
0100F110029C8000,"Yooka-Laylee",,playable,2021-01-28 14:21:45
|
||||||
010022F00DA66000,"Yooka-Laylee and the Impossible Lair",,playable,2021-03-05 17:32:21
|
010022F00DA66000,"Yooka-Laylee and the Impossible Lair",,playable,2021-03-05 17:32:21
|
||||||
|
|||||||
|
@@ -47,7 +47,8 @@ namespace Ryujinx.Common
|
|||||||
"01006f8002326000", // Animal Crossings: New Horizons
|
"01006f8002326000", // Animal Crossings: New Horizons
|
||||||
"01009bf0072d4000", // Captain Toad: Treasure Tracker
|
"01009bf0072d4000", // Captain Toad: Treasure Tracker
|
||||||
"01009510001ca000", // Fast RMX
|
"01009510001ca000", // Fast RMX
|
||||||
"01005CA01580E000", // Persona 5 Royale
|
"01005CA01580E000", // Persona 5 Royal
|
||||||
|
"0100b880154fc000", // Persona 5 The Royal (Japan)
|
||||||
"010015100b514000", // Super Mario Bros. Wonder
|
"010015100b514000", // Super Mario Bros. Wonder
|
||||||
"0100000000010000", // Super Mario Odyssey
|
"0100000000010000", // Super Mario Odyssey
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||||||
|
|
||||||
// Make sure all pending uniform buffer data is written to memory.
|
// Make sure all pending uniform buffer data is written to memory.
|
||||||
_3dEngine.FlushUboDirty();
|
_3dEngine.FlushUboDirty();
|
||||||
|
|
||||||
uint qmdAddress = _state.State.SendPcasA;
|
uint qmdAddress = _state.State.SendPcasA;
|
||||||
|
|
||||||
ComputeQmd qmd = _channel.MemoryManager.Read<ComputeQmd>((ulong)qmdAddress << 8);
|
ComputeQmd qmd = _channel.MemoryManager.Read<ComputeQmd>((ulong)qmdAddress << 8);
|
||||||
@@ -106,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||||||
ulong shaderGpuVa = ((ulong)_state.State.SetProgramRegionAAddressUpper << 32) | _state.State.SetProgramRegionB;
|
ulong shaderGpuVa = ((ulong)_state.State.SetProgramRegionAAddressUpper << 32) | _state.State.SetProgramRegionB;
|
||||||
|
|
||||||
shaderGpuVa += (uint)qmd.ProgramOffset;
|
shaderGpuVa += (uint)qmd.ProgramOffset;
|
||||||
|
|
||||||
|
ShaderCache shaderCache = memoryManager.GetBackingMemory(shaderGpuVa).ShaderCache;
|
||||||
|
|
||||||
int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize;
|
int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize;
|
||||||
|
|
||||||
@@ -142,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||||||
sharedMemorySize,
|
sharedMemorySize,
|
||||||
_channel.BufferManager.HasUnalignedStorageBuffers);
|
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||||
|
|
||||||
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
CachedShaderProgram cs = shaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||||
|
|
||||||
@@ -156,10 +158,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||||||
{
|
{
|
||||||
BufferDescriptor sb = info.SBuffers[index];
|
BufferDescriptor sb = info.SBuffers[index];
|
||||||
|
|
||||||
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
|
(PhysicalMemory physical, ulong sbDescAddress) = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
|
||||||
sbDescAddress += (ulong)sb.SbCbOffset * 4;
|
sbDescAddress += (ulong)sb.SbCbOffset * 4;
|
||||||
|
|
||||||
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
SbDescriptor sbDescriptor = physical.Read<SbDescriptor>(sbDescAddress);
|
||||||
|
|
||||||
uint size;
|
uint size;
|
||||||
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
|
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
|
||||||
@@ -187,7 +189,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||||||
sharedMemorySize,
|
sharedMemorySize,
|
||||||
_channel.BufferManager.HasUnalignedStorageBuffers);
|
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||||
|
|
||||||
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
cs = shaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
_channel.TextureManager.RefreshModifiedTextures();
|
_channel.TextureManager.RefreshModifiedTextures();
|
||||||
_3dEngine.CreatePendingSyncs();
|
_3dEngine.CreatePendingSyncs();
|
||||||
_3dEngine.FlushUboDirty();
|
_3dEngine.FlushUboDirty();
|
||||||
|
|
||||||
|
PhysicalMemory srcPhysical = memoryManager.GetBackingMemory(srcGpuVa);
|
||||||
|
PhysicalMemory dstPhysical = memoryManager.GetBackingMemory(dstGpuVa);
|
||||||
|
|
||||||
if (copy2D)
|
if (copy2D)
|
||||||
{
|
{
|
||||||
// Buffer to texture copy.
|
// Buffer to texture copy.
|
||||||
@@ -293,7 +296,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
|
|
||||||
if (completeSource && completeDest && !srcLinear && isIdentityRemap)
|
if (completeSource && completeDest && !srcLinear && isIdentityRemap)
|
||||||
{
|
{
|
||||||
Image.Texture source = memoryManager.Physical.TextureCache.FindTexture(
|
Image.Texture source = srcPhysical.TextureCache.FindTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
srcGpuVa,
|
srcGpuVa,
|
||||||
srcBpp,
|
srcBpp,
|
||||||
@@ -309,7 +312,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
{
|
{
|
||||||
source.SynchronizeMemory();
|
source.SynchronizeMemory();
|
||||||
|
|
||||||
Image.Texture target = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
Image.Texture target = dstPhysical.TextureCache.FindOrCreateTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
source.Info.FormatInfo,
|
source.Info.FormatInfo,
|
||||||
dstGpuVa,
|
dstGpuVa,
|
||||||
@@ -339,7 +342,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
|
|
||||||
if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap)
|
if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap)
|
||||||
{
|
{
|
||||||
Image.Texture target = memoryManager.Physical.TextureCache.FindTexture(
|
Image.Texture target = dstPhysical.TextureCache.FindTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
dstGpuVa,
|
dstGpuVa,
|
||||||
dstBpp,
|
dstBpp,
|
||||||
@@ -462,6 +465,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
BufferCache bufferCache = dstPhysical.BufferCache;
|
||||||
if (remap &&
|
if (remap &&
|
||||||
_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.ConstA &&
|
_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.ConstA &&
|
||||||
_state.State.SetRemapComponentsDstY == SetRemapComponentsDst.ConstA &&
|
_state.State.SetRemapComponentsDstY == SetRemapComponentsDst.ConstA &&
|
||||||
@@ -472,7 +476,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
_state.State.SetRemapComponentsComponentSize == SetRemapComponentsComponentSize.Four)
|
_state.State.SetRemapComponentsComponentSize == SetRemapComponentsComponentSize.Four)
|
||||||
{
|
{
|
||||||
// Fast path for clears when remap is enabled.
|
// Fast path for clears when remap is enabled.
|
||||||
memoryManager.Physical.BufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
|
bufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -492,7 +496,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size);
|
BufferCache.CopyBuffer(_context,memoryManager, srcGpuVa, dstGpuVa, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
|||||||
// Right now the copy code at the bottom assumes that it is used on both which might be incorrect.
|
// Right now the copy code at the bottom assumes that it is used on both which might be incorrect.
|
||||||
if (!_isLinear)
|
if (!_isLinear)
|
||||||
{
|
{
|
||||||
Image.Texture target = memoryManager.Physical.TextureCache.FindTexture(
|
Image.Texture target = memoryManager.GetBackingMemory(_dstGpuVa).TextureCache.FindTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
_dstGpuVa,
|
_dstGpuVa,
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
|
|
||||||
ulong indirectBufferGpuVa = count.GpuVa;
|
ulong indirectBufferGpuVa = count.GpuVa;
|
||||||
|
|
||||||
BufferCache bufferCache = _processor.MemoryManager.Physical.BufferCache;
|
BufferCache bufferCache = _processor.MemoryManager.GetBackingMemory(indirectBufferGpuVa).BufferCache;
|
||||||
|
|
||||||
bool useBuffer = bufferCache.CheckModified(_processor.MemoryManager, indirectBufferGpuVa, IndirectIndexedDataEntrySize, out ulong indirectBufferAddress);
|
bool useBuffer = bufferCache.CheckModified(_processor.MemoryManager, indirectBufferGpuVa, IndirectIndexedDataEntrySize, out ulong indirectBufferAddress);
|
||||||
|
|
||||||
@@ -394,6 +394,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
|
|
||||||
_processor.ThreedClass.DrawIndirect(
|
_processor.ThreedClass.DrawIndirect(
|
||||||
topology,
|
topology,
|
||||||
|
bufferCache,
|
||||||
|
null,
|
||||||
new MultiRange(indirectBufferAddress, IndirectIndexedDataEntrySize),
|
new MultiRange(indirectBufferAddress, IndirectIndexedDataEntrySize),
|
||||||
default,
|
default,
|
||||||
1,
|
1,
|
||||||
@@ -491,22 +493,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BufferCache indirectBufferCache = _processor.MemoryManager.GetBackingMemory(indirectBufferGpuVa).BufferCache;
|
||||||
BufferCache bufferCache = _processor.MemoryManager.Physical.BufferCache;
|
BufferCache parameterBufferCache = _processor.MemoryManager.GetBackingMemory(parameterBufferGpuVa).BufferCache;
|
||||||
|
|
||||||
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
|
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
|
||||||
|
|
||||||
MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
|
MultiRange indirectBufferRange = indirectBufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
|
||||||
MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
|
MultiRange parameterBufferRange = parameterBufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
|
||||||
|
|
||||||
_processor.ThreedClass.DrawIndirect(
|
_processor.ThreedClass.DrawIndirect(
|
||||||
topology,
|
topology,
|
||||||
|
indirectBufferCache,
|
||||||
|
parameterBufferCache,
|
||||||
indirectBufferRange,
|
indirectBufferRange,
|
||||||
parameterBufferRange,
|
parameterBufferRange,
|
||||||
maxDrawCount,
|
maxDrawCount,
|
||||||
stride,
|
stride,
|
||||||
indexCount,
|
indexCount,
|
||||||
Threed.IndirectDrawType.DrawIndexedIndirectCount);
|
IndirectDrawType.DrawIndexedIndirectCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class VtgAsComputeContext : IDisposable
|
class VtgAsComputeContext : IDisposable
|
||||||
{
|
{
|
||||||
|
private const int DummyBufferSize = 16;
|
||||||
|
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -46,7 +48,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
format.GetBytesPerElement(),
|
1,
|
||||||
format,
|
format,
|
||||||
DepthStencilMode.Depth,
|
DepthStencilMode.Depth,
|
||||||
Target.TextureBuffer,
|
Target.TextureBuffer,
|
||||||
@@ -519,6 +521,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size, write);
|
return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size, write);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the range for a dummy 16 bytes buffer, filled with zeros.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Dummy buffer range</returns>
|
||||||
|
public BufferRange GetDummyBufferRange()
|
||||||
|
{
|
||||||
|
if (_dummyBuffer == BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory);
|
||||||
|
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BufferRange(_dummyBuffer, 0, DummyBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the range for a sequential index buffer, with ever incrementing index values.
|
/// Gets the range for a sequential index buffer, with ever incrementing index values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
{
|
{
|
||||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||||
|
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +163,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
{
|
{
|
||||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||||
|
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int vbStride = vertexBuffer.UnpackStride();
|
int vbStride = vertexBuffer.UnpackStride();
|
||||||
ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count);
|
ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count);
|
||||||
|
|
||||||
|
ulong oldVbSize = vbSize;
|
||||||
|
|
||||||
ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset();
|
ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset();
|
||||||
int componentSize = format.GetScalarSize();
|
int componentSize = format.GetScalarSize();
|
||||||
|
|
||||||
@@ -196,11 +200,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
|
|
||||||
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
||||||
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
||||||
_context.Renderer.Pipeline.SetUniformBuffers([new BufferAssignment(vertexInfoBinding, vertexInfoRange)]);
|
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||||
|
|
||||||
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||||
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: true);
|
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: true);
|
||||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexDataRange)]);
|
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||||
|
|
||||||
_vacContext.VertexInfoBufferUpdater.Commit();
|
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||||
|
|
||||||
@@ -228,7 +232,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
|
|
||||||
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
||||||
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
||||||
_context.Renderer.Pipeline.SetUniformBuffers([new BufferAssignment(vertexInfoBinding, vertexInfoRange)]);
|
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||||
|
|
||||||
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||||
|
|
||||||
@@ -246,11 +250,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: true);
|
BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: true);
|
||||||
BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: true);
|
BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: true);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetStorageBuffers([
|
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[]
|
||||||
|
{
|
||||||
new BufferAssignment(vertexDataBinding, vertexDataRange),
|
new BufferAssignment(vertexDataBinding, vertexDataRange),
|
||||||
new BufferAssignment(geometryVbBinding, vertexBuffer),
|
new BufferAssignment(geometryVbBinding, vertexBuffer),
|
||||||
new BufferAssignment(geometryIbBinding, indexBuffer)
|
new BufferAssignment(geometryIbBinding, indexBuffer),
|
||||||
]);
|
});
|
||||||
|
|
||||||
_context.Renderer.Pipeline.DispatchCompute(
|
_context.Renderer.Pipeline.DispatchCompute(
|
||||||
BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize),
|
BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize),
|
||||||
@@ -294,7 +299,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
|
|
||||||
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||||
_context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt);
|
_context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt);
|
||||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexBuffer)]);
|
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexBuffer) });
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetPrimitiveRestart(true, -1);
|
_context.Renderer.Pipeline.SetPrimitiveRestart(true, -1);
|
||||||
_context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive));
|
_context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive));
|
||||||
@@ -309,7 +314,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false);
|
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexDataRange)]);
|
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||||
_context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0);
|
_context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,6 +345,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
return maxOutputVertices / verticesPerPrimitive;
|
return maxOutputVertices / verticesPerPrimitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds a dummy buffer as vertex buffer into a buffer texture.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reservations">Shader resource binding reservations</param>
|
||||||
|
/// <param name="index">Buffer texture index</param>
|
||||||
|
/// <param name="format">Buffer texture format</param>
|
||||||
|
private readonly void SetDummyBufferTexture(ResourceReservations reservations, int index, Format format)
|
||||||
|
{
|
||||||
|
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||||
|
bufferTexture.SetStorage(_vacContext.GetDummyBufferRange());
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Binds a vertex buffer into a buffer texture.
|
/// Binds a vertex buffer into a buffer texture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -352,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
{
|
{
|
||||||
MemoryManager memoryManager = _channel.MemoryManager;
|
MemoryManager memoryManager = _channel.MemoryManager;
|
||||||
|
|
||||||
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
|
BufferRange range = memoryManager.GetBackingMemory(address).BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
|
||||||
|
|
||||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||||
bufferTexture.SetStorage(range);
|
bufferTexture.SetStorage(range);
|
||||||
@@ -394,7 +413,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
|||||||
MemoryManager memoryManager = _channel.MemoryManager;
|
MemoryManager memoryManager = _channel.MemoryManager;
|
||||||
|
|
||||||
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
||||||
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(
|
BufferRange range = memoryManager.GetBackingMemory(address).BufferCache.GetBufferRange(
|
||||||
memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
|
memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
|
||||||
BufferStage.IndexBuffer);
|
BufferStage.IndexBuffer);
|
||||||
misalignedOffset = (int)misalign >> shift;
|
misalignedOffset = (int)misalign >> shift;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
// State associated with direct uniform buffer updates.
|
// State associated with direct uniform buffer updates.
|
||||||
// This state is used to attempt to batch together consecutive updates.
|
// This state is used to attempt to batch together consecutive updates.
|
||||||
|
private ulong _ubBeginGpuAddress = 0;
|
||||||
private ulong _ubBeginCpuAddress = 0;
|
private ulong _ubBeginCpuAddress = 0;
|
||||||
private ulong _ubFollowUpAddress = 0;
|
private ulong _ubFollowUpAddress = 0;
|
||||||
private ulong _ubByteCount = 0;
|
private ulong _ubByteCount = 0;
|
||||||
@@ -113,12 +114,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
if (_ubFollowUpAddress != 0)
|
if (_ubFollowUpAddress != 0)
|
||||||
{
|
{
|
||||||
MemoryManager memoryManager = _channel.MemoryManager;
|
MemoryManager memoryManager = _channel.MemoryManager;
|
||||||
|
PhysicalMemory physicalMemory = memoryManager.GetBackingMemory(_ubBeginGpuAddress);
|
||||||
|
|
||||||
Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
|
Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
|
||||||
|
|
||||||
if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
|
if (physicalMemory.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
|
||||||
{
|
{
|
||||||
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
|
physicalMemory.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ubFollowUpAddress = 0;
|
_ubFollowUpAddress = 0;
|
||||||
|
|||||||
@@ -641,6 +641,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
public void DrawIndirect(
|
public void DrawIndirect(
|
||||||
ThreedClass engine,
|
ThreedClass engine,
|
||||||
PrimitiveTopology topology,
|
PrimitiveTopology topology,
|
||||||
|
BufferCache indirectBufferCache,
|
||||||
|
BufferCache parameterBufferCache,
|
||||||
MultiRange indirectBufferRange,
|
MultiRange indirectBufferRange,
|
||||||
MultiRange parameterBufferRange,
|
MultiRange parameterBufferRange,
|
||||||
int maxDrawCount,
|
int maxDrawCount,
|
||||||
@@ -662,8 +664,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalMemory memory = _channel.MemoryManager.Physical;
|
|
||||||
|
|
||||||
bool hasCount = (drawType & IndirectDrawType.Count) != 0;
|
bool hasCount = (drawType & IndirectDrawType.Count) != 0;
|
||||||
bool indexed = (drawType & IndirectDrawType.Indexed) != 0;
|
bool indexed = (drawType & IndirectDrawType.Indexed) != 0;
|
||||||
|
|
||||||
@@ -684,8 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
if (hasCount)
|
if (hasCount)
|
||||||
{
|
{
|
||||||
BufferRange indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
BufferRange indirectBuffer = indirectBufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||||
BufferRange parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
|
BufferRange parameterBuffer = parameterBufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
|
||||||
|
|
||||||
if (indexed)
|
if (indexed)
|
||||||
{
|
{
|
||||||
@@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
BufferRange indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
BufferRange indirectBuffer = indirectBufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||||
|
|
||||||
if (indexed)
|
if (indexed)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -381,10 +381,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
{
|
{
|
||||||
BufferDescriptor sb = info.SBuffers[index];
|
BufferDescriptor sb = info.SBuffers[index];
|
||||||
|
|
||||||
ulong sbDescAddress = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
|
(PhysicalMemory physical, ulong sbDescAddress) = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
|
||||||
sbDescAddress += (ulong)sb.SbCbOffset * 4;
|
sbDescAddress += (ulong)sb.SbCbOffset * 4;
|
||||||
|
|
||||||
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
SbDescriptor sbDescriptor = physical.Read<SbDescriptor>(sbDescAddress);
|
||||||
|
|
||||||
uint size;
|
uint size;
|
||||||
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
|
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
|
||||||
@@ -505,7 +505,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
rtNoAlphaMask |= 1u << index;
|
rtNoAlphaMask |= 1u << index;
|
||||||
}
|
}
|
||||||
|
|
||||||
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
TextureCache colorTextureCache = memoryManager.GetBackingMemory(colorState.Address.Pack()).TextureCache;
|
||||||
|
|
||||||
|
Image.Texture color = colorTextureCache.FindOrCreateTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
colorState,
|
colorState,
|
||||||
_vtgWritesRtLayer || layered,
|
_vtgWritesRtLayer || layered,
|
||||||
@@ -513,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
samplesInX,
|
samplesInX,
|
||||||
samplesInY,
|
samplesInY,
|
||||||
sizeHint);
|
sizeHint);
|
||||||
|
|
||||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
|
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
|
||||||
|
|
||||||
if (color != null)
|
if (color != null)
|
||||||
@@ -543,8 +545,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
{
|
{
|
||||||
RtDepthStencilState dsState = _state.State.RtDepthStencilState;
|
RtDepthStencilState dsState = _state.State.RtDepthStencilState;
|
||||||
Size3D dsSize = _state.State.RtDepthStencilSize;
|
Size3D dsSize = _state.State.RtDepthStencilSize;
|
||||||
|
TextureCache dsTextureCache = memoryManager.GetBackingMemory(dsState.Address.Pack()).TextureCache;
|
||||||
depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
|
||||||
|
depthStencil = dsTextureCache.FindOrCreateTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
dsState,
|
dsState,
|
||||||
dsSize,
|
dsSize,
|
||||||
@@ -1409,8 +1412,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateShaderState()
|
private void UpdateShaderState()
|
||||||
{
|
{
|
||||||
ShaderCache shaderCache = _channel.MemoryManager.Physical.ShaderCache;
|
|
||||||
|
|
||||||
_vtgWritesRtLayer = false;
|
_vtgWritesRtLayer = false;
|
||||||
|
|
||||||
ShaderAddresses addresses = new();
|
ShaderAddresses addresses = new();
|
||||||
@@ -1433,6 +1434,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
? _state.State.TexturePoolState.MaximumId
|
? _state.State.TexturePoolState.MaximumId
|
||||||
: _state.State.SamplerPoolState.MaximumId;
|
: _state.State.SamplerPoolState.MaximumId;
|
||||||
|
|
||||||
|
// Shader stages on different address spaces are not supported right now,
|
||||||
|
// but it should never happen in practice anyway.
|
||||||
|
ShaderCache shaderCache = _channel.MemoryManager.GetBackingMemory(addresses.VertexB).ShaderCache;
|
||||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(
|
CachedShaderProgram gs = shaderCache.GetGraphicsShader(
|
||||||
ref _state.State,
|
ref _state.State,
|
||||||
ref _pipeline,
|
ref _pipeline,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
|||||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
@@ -804,6 +805,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// Performs a indirect draw, with parameters from a GPU buffer.
|
/// Performs a indirect draw, with parameters from a GPU buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="topology">Primitive topology</param>
|
/// <param name="topology">Primitive topology</param>
|
||||||
|
/// <param name="indirectBufferCache">Buffer cache owning the buffer with the draw parameters</param>
|
||||||
|
/// <param name="parameterBufferCache">Buffer cache owning the buffer with the draw count</param>
|
||||||
/// <param name="indirectBufferRange">Memory range of the buffer with the draw parameters, such as count, first index, etc</param>
|
/// <param name="indirectBufferRange">Memory range of the buffer with the draw parameters, such as count, first index, etc</param>
|
||||||
/// <param name="parameterBufferRange">Memory range of the buffer with the draw count</param>
|
/// <param name="parameterBufferRange">Memory range of the buffer with the draw count</param>
|
||||||
/// <param name="maxDrawCount">Maximum number of draws that can be made</param>
|
/// <param name="maxDrawCount">Maximum number of draws that can be made</param>
|
||||||
@@ -812,6 +815,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// <param name="drawType">Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count</param>
|
/// <param name="drawType">Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count</param>
|
||||||
public void DrawIndirect(
|
public void DrawIndirect(
|
||||||
PrimitiveTopology topology,
|
PrimitiveTopology topology,
|
||||||
|
BufferCache indirectBufferCache,
|
||||||
|
BufferCache parameterBufferCache,
|
||||||
MultiRange indirectBufferRange,
|
MultiRange indirectBufferRange,
|
||||||
MultiRange parameterBufferRange,
|
MultiRange parameterBufferRange,
|
||||||
int maxDrawCount,
|
int maxDrawCount,
|
||||||
@@ -819,7 +824,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
int indexCount,
|
int indexCount,
|
||||||
IndirectDrawType drawType)
|
IndirectDrawType drawType)
|
||||||
{
|
{
|
||||||
_drawManager.DrawIndirect(this, topology, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType);
|
_drawManager.DrawIndirect(this, topology, indirectBufferCache, parameterBufferCache, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -233,6 +233,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
|||||||
|
|
||||||
TwodTexture dstCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetDstFormat);
|
TwodTexture dstCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetDstFormat);
|
||||||
TwodTexture srcCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetSrcFormat);
|
TwodTexture srcCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetSrcFormat);
|
||||||
|
|
||||||
|
TextureCache srcTextureCache = memoryManager.GetBackingMemory(srcCopyTexture.Address.Pack()).TextureCache;
|
||||||
|
TextureCache dstTextureCache = memoryManager.GetBackingMemory(dstCopyTexture.Address.Pack()).TextureCache;
|
||||||
|
|
||||||
long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac;
|
long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac;
|
||||||
long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac;
|
long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac;
|
||||||
@@ -305,7 +308,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
|||||||
// are the same, as we can't blit between different depth formats.
|
// are the same, as we can't blit between different depth formats.
|
||||||
bool srcDepthAlias = srcCopyTexture.Format == dstCopyTexture.Format;
|
bool srcDepthAlias = srcCopyTexture.Format == dstCopyTexture.Format;
|
||||||
|
|
||||||
Image.Texture srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
Image.Texture srcTexture = srcTextureCache.FindOrCreateTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
srcCopyTexture,
|
srcCopyTexture,
|
||||||
offset,
|
offset,
|
||||||
@@ -326,7 +329,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryManager.Physical.TextureCache.Lift(srcTexture);
|
srcTextureCache.Lift(srcTexture);
|
||||||
|
|
||||||
// When the source texture that was found has a depth format,
|
// When the source texture that was found has a depth format,
|
||||||
// we must enforce the target texture also has a depth format,
|
// we must enforce the target texture also has a depth format,
|
||||||
@@ -342,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
|||||||
dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
||||||
}
|
}
|
||||||
|
|
||||||
Image.Texture dstTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
Image.Texture dstTexture = dstTextureCache.FindOrCreateTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
dstCopyTexture,
|
dstCopyTexture,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -58,22 +58,24 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
public void BindMemory(MemoryManager memoryManager)
|
public void BindMemory(MemoryManager memoryManager)
|
||||||
{
|
{
|
||||||
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, memoryManager ?? throw new ArgumentNullException(nameof(memoryManager)));
|
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, memoryManager ?? throw new ArgumentNullException(nameof(memoryManager)));
|
||||||
|
if (oldMemoryManager == memoryManager)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
memoryManager.Physical.IncrementReferenceCount();
|
memoryManager.AttachToChannel(BufferManager.Rebind);
|
||||||
|
|
||||||
if (oldMemoryManager != null)
|
if (oldMemoryManager != null)
|
||||||
{
|
{
|
||||||
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
|
oldMemoryManager.DetachFromChannel(BufferManager.Rebind);
|
||||||
oldMemoryManager.Physical.DecrementReferenceCount();
|
|
||||||
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryManager.Physical.BufferCache.NotifyBuffersModified += BufferManager.Rebind;
|
|
||||||
memoryManager.MemoryUnmapped += MemoryUnmappedHandler;
|
memoryManager.MemoryUnmapped += MemoryUnmappedHandler;
|
||||||
|
|
||||||
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
||||||
TextureManager.ReloadPools();
|
TextureManager.ReloadPools();
|
||||||
memoryManager.Physical.BufferCache.QueuePrune();
|
memoryManager.QueuePrune();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -86,7 +88,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
TextureManager.ReloadPools();
|
TextureManager.ReloadPools();
|
||||||
|
|
||||||
MemoryManager memoryManager = Volatile.Read(ref _memoryManager);
|
MemoryManager memoryManager = Volatile.Read(ref _memoryManager);
|
||||||
memoryManager?.Physical.BufferCache.QueuePrune();
|
memoryManager?.QueuePrune();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -141,8 +143,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
|
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
|
||||||
if (oldMemoryManager != null)
|
if (oldMemoryManager != null)
|
||||||
{
|
{
|
||||||
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
|
oldMemoryManager.DetachFromChannel(BufferManager.Rebind);
|
||||||
oldMemoryManager.Physical.DecrementReferenceCount();
|
|
||||||
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
|||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Gpu.Shader;
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -172,7 +173,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
|
throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MemoryManager(physicalMemory, cpuMemorySize);
|
return new MemoryManager(this, physicalMemory, cpuMemorySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -197,7 +198,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// <param name="pid">ID of the process that owns <paramref name="cpuMemory"/></param>
|
/// <param name="pid">ID of the process that owns <paramref name="cpuMemory"/></param>
|
||||||
/// <param name="cpuMemory">Virtual memory owned by the process</param>
|
/// <param name="cpuMemory">Virtual memory owned by the process</param>
|
||||||
/// <exception cref="ArgumentException">Thrown if <paramref name="pid"/> was already registered</exception>
|
/// <exception cref="ArgumentException">Thrown if <paramref name="pid"/> was already registered</exception>
|
||||||
public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory)
|
public void RegisterProcess(ulong pid, IVirtualMemoryManagerTracked cpuMemory)
|
||||||
{
|
{
|
||||||
PhysicalMemory physicalMemory = new(this, cpuMemory);
|
PhysicalMemory physicalMemory = new(this, cpuMemory);
|
||||||
if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory))
|
if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||||
/// <param name="bindingsArrayCache">Cache of texture array bindings</param>
|
/// <param name="bindingsArrayCache">Cache of texture array bindings</param>
|
||||||
/// <returns>The found or newly created texture pool</returns>
|
/// <returns>The found or newly created texture pool</returns>
|
||||||
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
|
public T FindOrCreate(GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
|
||||||
{
|
{
|
||||||
// Remove old entries from the cache, if possible.
|
// Remove old entries from the cache, if possible.
|
||||||
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
|
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
|
||||||
@@ -99,7 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If not found, create a new one.
|
// If not found, create a new one.
|
||||||
pool = CreatePool(_context, channel, address, maximumId);
|
pool = CreatePool(_context, channel, physicalMemory, address, maximumId);
|
||||||
|
|
||||||
pool.CacheNode = _pools.AddLast(pool);
|
pool.CacheNode = _pools.AddLast(pool);
|
||||||
pool.CacheTimestamp = _currentTimestamp;
|
pool.CacheTimestamp = _currentTimestamp;
|
||||||
@@ -112,9 +113,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">GPU context that the pool belongs to</param>
|
/// <param name="context">GPU context that the pool belongs to</param>
|
||||||
/// <param name="channel">GPU channel that the pool belongs to</param>
|
/// <param name="channel">GPU channel that the pool belongs to</param>
|
||||||
|
/// <param name="physicalMemory">GPU backing memory of the pool</param>
|
||||||
/// <param name="address">Address of the pool in guest memory</param>
|
/// <param name="address">Address of the pool in guest memory</param>
|
||||||
/// <param name="maximumId">Maximum ID of the pool (equal to maximum minus one)</param>
|
/// <param name="maximumId">Maximum ID of the pool (equal to maximum minus one)</param>
|
||||||
protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId);
|
protected abstract T CreatePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId);
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -20,11 +22,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">GPU context that the sampler pool belongs to</param>
|
/// <param name="context">GPU context that the sampler pool belongs to</param>
|
||||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||||
|
/// <param name="physicalMemory">GPU backing memory of the pool</param>
|
||||||
/// <param name="address">Address of the sampler pool in guest memory</param>
|
/// <param name="address">Address of the sampler pool in guest memory</param>
|
||||||
/// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
|
/// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
|
||||||
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
|
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId)
|
||||||
{
|
{
|
||||||
return new SamplerPool(context, channel.MemoryManager.Physical, address, maximumId);
|
return new SamplerPool(context, physicalMemory, address, maximumId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -660,6 +660,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
|
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
|
||||||
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
|
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
|
||||||
|
BufferCache bufferCache = null;
|
||||||
|
|
||||||
for (int index = 0; index < length; index++)
|
for (int index = 0; index < length; index++)
|
||||||
{
|
{
|
||||||
@@ -673,7 +674,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, bindingInfo.FormatInfo, out texture);
|
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, bindingInfo.FormatInfo, out texture);
|
||||||
|
bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
|
||||||
if (texture != null)
|
if (texture != null)
|
||||||
{
|
{
|
||||||
entry.Textures[texture] = texture.InvalidatedSequence;
|
entry.Textures[texture] = texture.InvalidatedSequence;
|
||||||
@@ -702,11 +703,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
// to ensure we're not using a old buffer that was already deleted.
|
// to ensure we're not using a old buffer that was already deleted.
|
||||||
if (isImage)
|
if (isImage)
|
||||||
{
|
{
|
||||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
|
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, bufferCache, texture.Range, bindingInfo, index); }
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
|
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isImage)
|
else if (isImage)
|
||||||
@@ -797,11 +797,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
|
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(textureBufferBounds.Physical.GetSpan(textureBufferBounds.Range));
|
||||||
|
|
||||||
if (separateSamplerBuffer)
|
if (separateSamplerBuffer)
|
||||||
{
|
{
|
||||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
|
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(samplerBufferBounds.Physical.GetSpan(samplerBufferBounds.Range));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -828,11 +828,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
|
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(textureBufferBounds.Physical.GetSpan(textureBufferBounds.Range));
|
||||||
|
|
||||||
if (separateSamplerBuffer)
|
if (separateSamplerBuffer)
|
||||||
{
|
{
|
||||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
|
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(samplerBufferBounds.Physical.GetSpan(samplerBufferBounds.Range));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -901,16 +900,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||||
{
|
{
|
||||||
|
BufferCache bufferCache = textureBufferBounds.BufferCache;
|
||||||
|
|
||||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||||
// to ensure we're not using a old buffer that was already deleted.
|
// to ensure we're not using a old buffer that was already deleted.
|
||||||
if (isImage)
|
if (isImage)
|
||||||
{
|
{
|
||||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
|
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
|
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isImage)
|
else if (isImage)
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
|
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
|
||||||
|
|
||||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
|
||||||
cachedTextureBufferIndex = textureBufferIndex;
|
cachedTextureBufferIndex = textureBufferIndex;
|
||||||
|
|
||||||
if (samplerBufferIndex == textureBufferIndex)
|
if (samplerBufferIndex == textureBufferIndex)
|
||||||
@@ -410,7 +410,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
|
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
|
||||||
|
|
||||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
|
||||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,7 +524,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||||
// to ensure we're not using a old buffer that was already deleted.
|
// to ensure we're not using a old buffer that was already deleted.
|
||||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false);
|
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
|
||||||
|
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, bufferCache, texture.Range, bindingInfo, false);
|
||||||
|
|
||||||
// Cache is not used for buffer texture, it must always rebind.
|
// Cache is not used for buffer texture, it must always rebind.
|
||||||
state.CachedTexture = null;
|
state.CachedTexture = null;
|
||||||
@@ -659,7 +660,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||||
// to ensure we're not using a old buffer that was already deleted.
|
// to ensure we're not using a old buffer that was already deleted.
|
||||||
|
|
||||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true);
|
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
|
||||||
|
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, bufferCache, texture.Range, bindingInfo, true);
|
||||||
|
|
||||||
// Cache is not used for buffer texture, it must always rebind.
|
// Cache is not used for buffer texture, it must always rebind.
|
||||||
state.CachedTexture = null;
|
state.CachedTexture = null;
|
||||||
@@ -715,9 +717,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
|
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
|
||||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||||
|
|
||||||
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(poolGpuVa);
|
||||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||||
|
|
||||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
|
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, maximumId, _bindingsArrayCache);
|
||||||
|
|
||||||
TextureDescriptor descriptor;
|
TextureDescriptor descriptor;
|
||||||
|
|
||||||
@@ -751,12 +754,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
||||||
|
|
||||||
ulong textureBufferAddress = _isCompute
|
(PhysicalMemory texturePhysicalMemory, ulong textureBufferAddress) = _isCompute
|
||||||
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
|
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
|
||||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
|
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
|
||||||
|
|
||||||
int handle = textureBufferAddress != MemoryManager.PteUnmapped
|
int handle = textureBufferAddress != MemoryManager.PteUnmapped
|
||||||
? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
|
? texturePhysicalMemory.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
|
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
|
||||||
@@ -771,12 +774,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
|
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
|
||||||
{
|
{
|
||||||
ulong samplerBufferAddress = _isCompute
|
(PhysicalMemory samplerPhysicalMemory, ulong samplerBufferAddress) = _isCompute
|
||||||
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
|
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
|
||||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
|
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
|
||||||
|
|
||||||
samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped
|
samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped
|
||||||
? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
|
? samplerPhysicalMemory.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -813,7 +816,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (poolAddress != MemoryManager.PteUnmapped)
|
if (poolAddress != MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(_texturePoolGpuVa);
|
||||||
|
texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
|
||||||
_texturePool = texturePool;
|
_texturePool = texturePool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -824,7 +828,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (poolAddress != MemoryManager.PteUnmapped)
|
if (poolAddress != MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(_samplerPoolGpuVa);
|
||||||
|
samplerPool = _samplerPoolCache.FindOrCreate(_channel, physical, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
|
||||||
_samplerPool = samplerPool;
|
_samplerPool = samplerPool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Gpu.Shader;
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
@@ -385,8 +386,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
|
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
|
||||||
{
|
{
|
||||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||||
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(poolAddress);
|
||||||
|
|
||||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
|
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, maximumId, _bindingsArrayCache);
|
||||||
|
|
||||||
return texturePool;
|
return texturePool;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,9 +160,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||||
|
/// <param name="physicalMemory">Backing memory of the pool</param>
|
||||||
/// <param name="address">Address of the texture pool in guest memory</param>
|
/// <param name="address">Address of the texture pool in guest memory</param>
|
||||||
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
|
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
|
||||||
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
|
public TexturePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId) : base(context, physicalMemory, address, maximumId)
|
||||||
{
|
{
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
_aliasLists = new Dictionary<Texture, TextureAliasList>();
|
_aliasLists = new Dictionary<Texture, TextureAliasList>();
|
||||||
@@ -193,7 +194,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
MemoryManager memoryManager = _channel.MemoryManager;
|
||||||
|
TextureCache textureCache = memoryManager.GetBackingMemory(descriptor.UnpackAddress()).TextureCache;
|
||||||
|
texture = textureCache.FindOrCreateTexture(memoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||||
|
|
||||||
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
@@ -421,7 +424,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiRange range = _channel.MemoryManager.Physical.TextureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
|
TextureCache textureCache = _channel.MemoryManager.GetBackingMemory(address).TextureCache;
|
||||||
|
MultiRange range = textureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
|
||||||
|
|
||||||
// If the texture is not mapped at all, delete its reference.
|
// If the texture is not mapped at all, delete its reference.
|
||||||
|
|
||||||
@@ -446,7 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
if (!range.Equals(texture.Range))
|
if (!range.Equals(texture.Range))
|
||||||
{
|
{
|
||||||
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
|
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
|
||||||
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
|
if (!textureCache.UpdateMapping(texture, range))
|
||||||
{
|
{
|
||||||
// Texture could not be remapped due to a collision, just delete it.
|
// Texture could not be remapped due to a collision, just delete it.
|
||||||
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
|
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
|
||||||
@@ -481,6 +485,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="size">Size of the range being invalidated</param>
|
/// <param name="size">Size of the range being invalidated</param>
|
||||||
protected override void InvalidateRangeImpl(ulong address, ulong size)
|
protected override void InvalidateRangeImpl(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
|
MemoryManager memoryManager = _channel.MemoryManager;
|
||||||
ProcessDereferenceQueue();
|
ProcessDereferenceQueue();
|
||||||
|
|
||||||
ulong endAddress = address + size;
|
ulong endAddress = address + size;
|
||||||
@@ -505,7 +510,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (texture.HasOneReference())
|
if (texture.HasOneReference())
|
||||||
{
|
{
|
||||||
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
TextureCache textureCache = memoryManager.GetBackingMemory(descriptor.UnpackAddress()).TextureCache;
|
||||||
|
textureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Interlocked.Exchange(ref Items[id], null) != null)
|
if (Interlocked.Exchange(ref Items[id], null) != null)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -20,11 +22,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||||
|
/// <param name="physicalMemory">Backing memory of the pool</param>
|
||||||
/// <param name="address">Address of the texture pool in guest memory</param>
|
/// <param name="address">Address of the texture pool in guest memory</param>
|
||||||
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
|
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
|
||||||
protected override TexturePool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
|
protected override TexturePool CreatePool(
|
||||||
|
GpuContext context,
|
||||||
|
GpuChannel channel,
|
||||||
|
PhysicalMemory physicalMemory,
|
||||||
|
ulong address,
|
||||||
|
int maximumId)
|
||||||
{
|
{
|
||||||
return new TexturePool(context, channel, address, maximumId);
|
return new TexturePool(context, channel, physicalMemory, address, maximumId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
readonly struct BufferBounds : IEquatable<BufferBounds>
|
readonly struct BufferBounds : IEquatable<BufferBounds>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Physical memory backing the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public PhysicalMemory Physical { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer cache that owns the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public BufferCache BufferCache => Physical.BufferCache;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Physical memory ranges where the buffer is mapped.
|
/// Physical memory ranges where the buffer is mapped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -29,8 +39,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="range">Physical memory ranges where the buffer is mapped</param>
|
/// <param name="range">Physical memory ranges where the buffer is mapped</param>
|
||||||
/// <param name="flags">Buffer usage flags</param>
|
/// <param name="flags">Buffer usage flags</param>
|
||||||
public BufferBounds(MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
public BufferBounds(PhysicalMemory physical, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||||
{
|
{
|
||||||
|
Physical = physical;
|
||||||
Range = range;
|
Range = range;
|
||||||
Flags = flags;
|
Flags = flags;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -735,18 +735,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This does a GPU side copy.
|
/// This does a GPU side copy.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||||
/// <param name="srcVa">GPU virtual address of the copy source</param>
|
/// <param name="srcVa">GPU virtual address of the copy source</param>
|
||||||
/// <param name="dstVa">GPU virtual address of the copy destination</param>
|
/// <param name="dstVa">GPU virtual address of the copy destination</param>
|
||||||
/// <param name="size">Size in bytes of the copy</param>
|
/// <param name="size">Size in bytes of the copy</param>
|
||||||
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
public static void CopyBuffer(GpuContext context, MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
||||||
{
|
{
|
||||||
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
|
PhysicalMemory srcPhysical = memoryManager.GetBackingMemory(srcVa);
|
||||||
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
|
PhysicalMemory dstPhysical = memoryManager.GetBackingMemory(dstVa);
|
||||||
|
|
||||||
|
MultiRange srcRange = srcPhysical.BufferCache.TranslateAndCreateBuffer(memoryManager, srcVa, size, BufferStage.Copy);
|
||||||
|
MultiRange dstRange = dstPhysical.BufferCache.TranslateAndCreateBuffer(memoryManager, dstVa, size, BufferStage.Copy);
|
||||||
|
|
||||||
if (srcRange.Count == 1 && dstRange.Count == 1)
|
if (srcRange.Count == 1 && dstRange.Count == 1)
|
||||||
{
|
{
|
||||||
CopyBufferSingleRange(memoryManager, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
|
CopyBufferSingleRange(context, srcPhysical, dstPhysical, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -777,7 +781,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ulong dstSize = dstSubRange.Size - dstOffset;
|
ulong dstSize = dstSubRange.Size - dstOffset;
|
||||||
ulong copySize = Math.Min(srcSize, dstSize);
|
ulong copySize = Math.Min(srcSize, dstSize);
|
||||||
|
|
||||||
CopyBufferSingleRange(memoryManager, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
|
CopyBufferSingleRange(context, srcPhysical, dstPhysical, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
|
||||||
|
|
||||||
srcOffset += copySize;
|
srcOffset += copySize;
|
||||||
dstOffset += copySize;
|
dstOffset += copySize;
|
||||||
@@ -793,18 +797,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// This does a GPU side copy.
|
/// This does a GPU side copy.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||||
|
/// <param name="srcPhysical">Physical memory backing the source buffer.</param>
|
||||||
|
/// <param name="dstPhysical">Physical memory backing the destination buffer.</param>
|
||||||
/// <param name="srcAddress">Physical address of the copy source</param>
|
/// <param name="srcAddress">Physical address of the copy source</param>
|
||||||
/// <param name="dstAddress">Physical address of the copy destination</param>
|
/// <param name="dstAddress">Physical address of the copy destination</param>
|
||||||
/// <param name="size">Size in bytes of the copy</param>
|
/// <param name="size">Size in bytes of the copy</param>
|
||||||
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
|
private static void CopyBufferSingleRange(
|
||||||
|
GpuContext context,
|
||||||
|
PhysicalMemory srcPhysical,
|
||||||
|
PhysicalMemory dstPhysical,
|
||||||
|
ulong srcAddress,
|
||||||
|
ulong dstAddress,
|
||||||
|
ulong size)
|
||||||
{
|
{
|
||||||
Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
|
Buffer srcBuffer = srcPhysical.BufferCache.GetBuffer(srcAddress, size, BufferStage.Copy);
|
||||||
Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy);
|
Buffer dstBuffer = dstPhysical.BufferCache.GetBuffer(dstAddress, size, BufferStage.Copy);
|
||||||
|
|
||||||
int srcOffset = (int)(srcAddress - srcBuffer.Address);
|
int srcOffset = (int)(srcAddress - srcBuffer.Address);
|
||||||
int dstOffset = (int)(dstAddress - dstBuffer.Address);
|
int dstOffset = (int)(dstAddress - dstBuffer.Address);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.CopyBuffer(
|
context.Renderer.Pipeline.CopyBuffer(
|
||||||
srcBuffer.Handle,
|
srcBuffer.Handle,
|
||||||
dstBuffer.Handle,
|
dstBuffer.Handle,
|
||||||
srcOffset,
|
srcOffset,
|
||||||
@@ -820,7 +832,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
// Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU.
|
// Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU.
|
||||||
|
|
||||||
dstBuffer.ClearModified(dstAddress, size);
|
dstBuffer.ClearModified(dstAddress, size);
|
||||||
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
|
dstPhysical.WriteTrackedResource(dstAddress, srcPhysical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
|
dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
|
||||||
@@ -849,7 +861,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
|
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
|
||||||
|
|
||||||
memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
|
memoryManager.GetBackingMemory(gpuVa).FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
|
||||||
|
|
||||||
buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
|
buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,18 +66,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
Buffers = new BufferBounds[count];
|
Buffers = new BufferBounds[count];
|
||||||
Unaligned = new bool[count];
|
Unaligned = new bool[count];
|
||||||
|
|
||||||
Buffers.AsSpan().Fill(new BufferBounds(new MultiRange(MemoryManager.PteUnmapped, 0UL)));
|
Buffers.AsSpan().Fill(new BufferBounds(null, new MultiRange(MemoryManager.PteUnmapped, 0UL)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the region of a buffer at a given slot.
|
/// Sets the region of a buffer at a given slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">Buffer slot</param>
|
/// <param name="index">Buffer slot</param>
|
||||||
|
/// <param name="physical">Physical memory backing the buffer</param>
|
||||||
/// <param name="range">Physical memory regions where the buffer is mapped</param>
|
/// <param name="range">Physical memory regions where the buffer is mapped</param>
|
||||||
/// <param name="flags">Buffer usage flags</param>
|
/// <param name="flags">Buffer usage flags</param>
|
||||||
public void SetBounds(int index, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
public void SetBounds(int index, PhysicalMemory physical, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||||
{
|
{
|
||||||
Buffers[index] = new BufferBounds(range, flags);
|
Buffers[index] = new BufferBounds(physical, range, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -156,8 +157,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="type">Type of each index buffer element</param>
|
/// <param name="type">Type of each index buffer element</param>
|
||||||
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
|
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
|
||||||
{
|
{
|
||||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
|
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache;
|
||||||
|
MultiRange range = bufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
|
||||||
|
|
||||||
|
_indexBuffer.BufferCache = bufferCache;
|
||||||
_indexBuffer.Range = range;
|
_indexBuffer.Range = range;
|
||||||
_indexBuffer.Type = type;
|
_indexBuffer.Type = type;
|
||||||
|
|
||||||
@@ -186,11 +189,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param>
|
/// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param>
|
||||||
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
|
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
|
||||||
{
|
{
|
||||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
|
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache;
|
||||||
|
MultiRange range = bufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
|
||||||
|
|
||||||
_vertexBuffers[index].Range = range;
|
ref VertexBuffer vb = ref _vertexBuffers[index];
|
||||||
_vertexBuffers[index].Stride = stride;
|
|
||||||
_vertexBuffers[index].Divisor = divisor;
|
vb.BufferCache = bufferCache;
|
||||||
|
vb.Range = range;
|
||||||
|
vb.Stride = stride;
|
||||||
|
vb.Divisor = divisor;
|
||||||
|
|
||||||
_vertexBuffersDirty = true;
|
_vertexBuffersDirty = true;
|
||||||
|
|
||||||
@@ -213,9 +220,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="size">Size in bytes of the transform feedback buffer</param>
|
/// <param name="size">Size in bytes of the transform feedback buffer</param>
|
||||||
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
|
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
|
||||||
{
|
{
|
||||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||||
|
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
|
||||||
|
|
||||||
_transformFeedbackBuffers[index] = new BufferBounds(range);
|
_transformFeedbackBuffers[index] = new BufferBounds(physical, range);
|
||||||
_transformFeedbackBuffersDirty = true;
|
_transformFeedbackBuffersDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,11 +266,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
RecordStorageAlignment(_cpStorageBuffers, index, gpuVa);
|
RecordStorageAlignment(_cpStorageBuffers, index, gpuVa);
|
||||||
|
|
||||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||||
|
|
||||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||||
|
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
|
||||||
|
|
||||||
_cpStorageBuffers.SetBounds(index, range, flags);
|
_cpStorageBuffers.SetBounds(index, physical, range, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -282,16 +291,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
RecordStorageAlignment(buffers, index, gpuVa);
|
RecordStorageAlignment(buffers, index, gpuVa);
|
||||||
|
|
||||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||||
|
|
||||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||||
|
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
|
||||||
|
|
||||||
if (!buffers.Buffers[index].Range.Equals(range))
|
if (!buffers.Buffers[index].Range.Equals(range))
|
||||||
{
|
{
|
||||||
_gpStorageBuffersDirty = true;
|
_gpStorageBuffersDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffers.SetBounds(index, range, flags);
|
buffers.SetBounds(index, physical, range, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -303,9 +313,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="size">Size in bytes of the storage buffer</param>
|
/// <param name="size">Size in bytes of the storage buffer</param>
|
||||||
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
|
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
|
||||||
{
|
{
|
||||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||||
|
MultiRange range = physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
|
||||||
|
|
||||||
_cpUniformBuffers.SetBounds(index, range);
|
_cpUniformBuffers.SetBounds(index, physical, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -318,9 +329,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="size">Size in bytes of the storage buffer</param>
|
/// <param name="size">Size in bytes of the storage buffer</param>
|
||||||
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
|
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
|
||||||
{
|
{
|
||||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
|
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||||
|
MultiRange range = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
|
||||||
|
|
||||||
_gpUniformBuffers[stage].SetBounds(index, range);
|
_gpUniformBuffers[stage].SetBounds(index, physical, range);
|
||||||
_gpUniformBuffersDirty = true;
|
_gpUniformBuffersDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,9 +428,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">Index of the uniform buffer binding</param>
|
/// <param name="index">Index of the uniform buffer binding</param>
|
||||||
/// <returns>The uniform buffer address, or an undefined value if the buffer is not currently bound</returns>
|
/// <returns>The uniform buffer address, or an undefined value if the buffer is not currently bound</returns>
|
||||||
public ulong GetComputeUniformBufferAddress(int index)
|
public (PhysicalMemory, ulong) GetComputeUniformBufferAddress(int index)
|
||||||
{
|
{
|
||||||
return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address;
|
ref BufferBounds buffer = ref _cpUniformBuffers.Buffers[index];
|
||||||
|
return (buffer.Physical, buffer.Range.GetSubRange(0).Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -437,9 +450,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="stage">Index of the shader stage</param>
|
/// <param name="stage">Index of the shader stage</param>
|
||||||
/// <param name="index">Index of the uniform buffer binding</param>
|
/// <param name="index">Index of the uniform buffer binding</param>
|
||||||
/// <returns>The uniform buffer address, or an undefined value if the buffer is not currently bound</returns>
|
/// <returns>The uniform buffer address, or an undefined value if the buffer is not currently bound</returns>
|
||||||
public ulong GetGraphicsUniformBufferAddress(int stage, int index)
|
public (PhysicalMemory, ulong) GetGraphicsUniformBufferAddress(int stage, int index)
|
||||||
{
|
{
|
||||||
return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address;
|
ref BufferBounds buffer = ref _gpUniformBuffers[stage].Buffers[index];
|
||||||
|
return (buffer.Physical, buffer.Range.GetSubRange(0).Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -478,12 +492,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void CommitComputeBindings()
|
public void CommitComputeBindings()
|
||||||
{
|
{
|
||||||
BufferCache bufferCache = _channel.MemoryManager.Physical.BufferCache;
|
BindBuffers(_cpStorageBuffers, isStorage: true);
|
||||||
|
BindBuffers(_cpUniformBuffers, isStorage: false);
|
||||||
|
|
||||||
BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true);
|
CommitBufferTextureBindings();
|
||||||
BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false);
|
|
||||||
|
|
||||||
CommitBufferTextureBindings(bufferCache);
|
|
||||||
|
|
||||||
// Force rebind after doing compute work.
|
// Force rebind after doing compute work.
|
||||||
Rebind();
|
Rebind();
|
||||||
@@ -495,14 +507,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// Commit any queued buffer texture bindings.
|
/// Commit any queued buffer texture bindings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bufferCache">Buffer cache</param>
|
/// <param name="bufferCache">Buffer cache</param>
|
||||||
private void CommitBufferTextureBindings(BufferCache bufferCache)
|
private void CommitBufferTextureBindings()
|
||||||
{
|
{
|
||||||
if (_bufferTextures.Count > 0)
|
if (_bufferTextures.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (BufferTextureBinding binding in _bufferTextures)
|
foreach (BufferTextureBinding binding in _bufferTextures)
|
||||||
{
|
{
|
||||||
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||||
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
|
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
|
||||||
binding.Texture.SetStorage(range);
|
binding.Texture.SetStorage(range);
|
||||||
|
|
||||||
// The texture must be rebound to use the new storage if it was updated.
|
// The texture must be rebound to use the new storage if it was updated.
|
||||||
@@ -526,7 +538,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
foreach (BufferTextureArrayBinding<ITextureArray> binding in _bufferTextureArrays)
|
foreach (BufferTextureArrayBinding<ITextureArray> binding in _bufferTextureArrays)
|
||||||
{
|
{
|
||||||
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStage.None);
|
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStage.None);
|
||||||
binding.Texture.SetStorage(range);
|
binding.Texture.SetStorage(range);
|
||||||
|
|
||||||
textureArray[0] = binding.Texture;
|
textureArray[0] = binding.Texture;
|
||||||
@@ -536,7 +548,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
foreach (BufferTextureArrayBinding<IImageArray> binding in _bufferImageArrays)
|
foreach (BufferTextureArrayBinding<IImageArray> binding in _bufferImageArrays)
|
||||||
{
|
{
|
||||||
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||||
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
|
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
|
||||||
binding.Texture.SetStorage(range);
|
binding.Texture.SetStorage(range);
|
||||||
|
|
||||||
textureArray[0] = binding.Texture;
|
textureArray[0] = binding.Texture;
|
||||||
@@ -555,8 +567,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="indexed">True if the index buffer is in use</param>
|
/// <param name="indexed">True if the index buffer is in use</param>
|
||||||
public void CommitGraphicsBindings(bool indexed)
|
public void CommitGraphicsBindings(bool indexed)
|
||||||
{
|
{
|
||||||
BufferCache bufferCache = _channel.MemoryManager.Physical.BufferCache;
|
|
||||||
|
|
||||||
if (indexed)
|
if (indexed)
|
||||||
{
|
{
|
||||||
if (_indexBufferDirty || _rebind)
|
if (_indexBufferDirty || _rebind)
|
||||||
@@ -565,14 +575,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
if (!_indexBuffer.Range.IsUnmapped)
|
if (!_indexBuffer.Range.IsUnmapped)
|
||||||
{
|
{
|
||||||
BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
|
BufferRange buffer = _indexBuffer.BufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
|
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!_indexBuffer.Range.IsUnmapped)
|
else if (!_indexBuffer.Range.IsUnmapped)
|
||||||
{
|
{
|
||||||
bufferCache.SynchronizeBufferRange(_indexBuffer.Range);
|
_indexBuffer.BufferCache.SynchronizeBufferRange(_indexBuffer.Range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_rebind)
|
else if (_rebind)
|
||||||
@@ -597,7 +607,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
|
BufferRange buffer = vb.BufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
|
||||||
|
|
||||||
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
|
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
|
||||||
}
|
}
|
||||||
@@ -615,7 +625,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferCache.SynchronizeBufferRange(vb.Range);
|
vb.BufferCache.SynchronizeBufferRange(vb.Range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,7 +647,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
|
tfbs[index] = tfb.BufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
|
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
|
||||||
@@ -684,7 +694,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
|
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
|
||||||
|
|
||||||
buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
|
buffers[index] = new BufferAssignment(index, tfb.BufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,7 +712,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferCache.SynchronizeBufferRange(tfb.Range);
|
tfb.BufferCache.SynchronizeBufferRange(tfb.Range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,7 +720,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
_gpStorageBuffersDirty = false;
|
_gpStorageBuffersDirty = false;
|
||||||
|
|
||||||
BindBuffers(bufferCache, _gpStorageBuffers, isStorage: true);
|
BindBuffers(_gpStorageBuffers, isStorage: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -721,14 +731,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
_gpUniformBuffersDirty = false;
|
_gpUniformBuffersDirty = false;
|
||||||
|
|
||||||
BindBuffers(bufferCache, _gpUniformBuffers, isStorage: false);
|
BindBuffers(_gpUniformBuffers, isStorage: false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UpdateBuffers(_gpUniformBuffers);
|
UpdateBuffers(_gpUniformBuffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommitBufferTextureBindings(bufferCache);
|
CommitBufferTextureBindings();
|
||||||
|
|
||||||
_rebind = false;
|
_rebind = false;
|
||||||
|
|
||||||
@@ -742,7 +752,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="bindings">Buffer memory ranges to bind</param>
|
/// <param name="bindings">Buffer memory ranges to bind</param>
|
||||||
/// <param name="isStorage">True to bind as storage buffer, false to bind as uniform buffer</param>
|
/// <param name="isStorage">True to bind as storage buffer, false to bind as uniform buffer</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage)
|
private void BindBuffers(BuffersPerStage[] bindings, bool isStorage)
|
||||||
{
|
{
|
||||||
int rangesCount = 0;
|
int rangesCount = 0;
|
||||||
|
|
||||||
@@ -763,8 +773,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||||
BufferRange range = isStorage
|
BufferRange range = isStorage
|
||||||
? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
|
? bounds.BufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
|
||||||
: bufferCache.GetBufferRange(bounds.Range, bufferStage);
|
: bounds.BufferCache.GetBufferRange(bounds.Range, bufferStage);
|
||||||
|
|
||||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||||
}
|
}
|
||||||
@@ -780,11 +790,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bind respective buffer bindings on the host API.
|
/// Bind respective buffer bindings on the host API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bufferCache">Buffer cache holding the buffers for the specified ranges</param>
|
|
||||||
/// <param name="buffers">Buffer memory ranges to bind</param>
|
/// <param name="buffers">Buffer memory ranges to bind</param>
|
||||||
/// <param name="isStorage">True to bind as storage buffer, false to bind as uniform buffer</param>
|
/// <param name="isStorage">True to bind as storage buffer, false to bind as uniform buffer</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage)
|
private void BindBuffers(BuffersPerStage buffers, bool isStorage)
|
||||||
{
|
{
|
||||||
int rangesCount = 0;
|
int rangesCount = 0;
|
||||||
|
|
||||||
@@ -800,8 +809,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||||
BufferRange range = isStorage
|
BufferRange range = isStorage
|
||||||
? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
|
? bounds.BufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
|
||||||
: bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
|
: bounds.BufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
|
||||||
|
|
||||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||||
}
|
}
|
||||||
@@ -854,7 +863,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_channel.MemoryManager.Physical.BufferCache.SynchronizeBufferRange(bounds.Range);
|
bounds.BufferCache.SynchronizeBufferRange(bounds.Range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -871,13 +880,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
public void SetBufferTextureStorage(
|
public void SetBufferTextureStorage(
|
||||||
ShaderStage stage,
|
ShaderStage stage,
|
||||||
ITexture texture,
|
ITexture texture,
|
||||||
|
BufferCache bufferCache,
|
||||||
MultiRange range,
|
MultiRange range,
|
||||||
TextureBindingInfo bindingInfo,
|
TextureBindingInfo bindingInfo,
|
||||||
bool isImage)
|
bool isImage)
|
||||||
{
|
{
|
||||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||||
|
|
||||||
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, isImage));
|
_bufferTextures.Add(new BufferTextureBinding(stage, texture, bufferCache, range, bindingInfo, isImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -894,13 +904,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ShaderStage stage,
|
ShaderStage stage,
|
||||||
ITextureArray array,
|
ITextureArray array,
|
||||||
ITexture texture,
|
ITexture texture,
|
||||||
|
BufferCache bufferCache,
|
||||||
MultiRange range,
|
MultiRange range,
|
||||||
TextureBindingInfo bindingInfo,
|
TextureBindingInfo bindingInfo,
|
||||||
int index)
|
int index)
|
||||||
{
|
{
|
||||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||||
|
|
||||||
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index));
|
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, bufferCache, range, bindingInfo, index));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -917,13 +928,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ShaderStage stage,
|
ShaderStage stage,
|
||||||
IImageArray array,
|
IImageArray array,
|
||||||
ITexture texture,
|
ITexture texture,
|
||||||
|
BufferCache bufferCache,
|
||||||
MultiRange range,
|
MultiRange range,
|
||||||
TextureBindingInfo bindingInfo,
|
TextureBindingInfo bindingInfo,
|
||||||
int index)
|
int index)
|
||||||
{
|
{
|
||||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||||
|
|
||||||
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index));
|
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, bufferCache, range, bindingInfo, index));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ITexture Texture { get; }
|
public ITexture Texture { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer cache that owns the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public BufferCache BufferCache { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Physical ranges of memory where the buffer texture data is located.
|
/// Physical ranges of memory where the buffer texture data is located.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -39,18 +44,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="array">Array</param>
|
/// <param name="array">Array</param>
|
||||||
/// <param name="texture">Buffer texture</param>
|
/// <param name="texture">Buffer texture</param>
|
||||||
|
/// <param name="bufferCache">Buffer cache that owns the buffer</param>
|
||||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||||
/// <param name="bindingInfo">Binding info</param>
|
/// <param name="bindingInfo">Binding info</param>
|
||||||
/// <param name="index">Index of the binding on the array</param>
|
/// <param name="index">Index of the binding on the array</param>
|
||||||
public BufferTextureArrayBinding(
|
public BufferTextureArrayBinding(
|
||||||
T array,
|
T array,
|
||||||
ITexture texture,
|
ITexture texture,
|
||||||
|
BufferCache bufferCache,
|
||||||
MultiRange range,
|
MultiRange range,
|
||||||
TextureBindingInfo bindingInfo,
|
TextureBindingInfo bindingInfo,
|
||||||
int index)
|
int index)
|
||||||
{
|
{
|
||||||
Array = array;
|
Array = array;
|
||||||
Texture = texture;
|
Texture = texture;
|
||||||
|
BufferCache = bufferCache;
|
||||||
Range = range;
|
Range = range;
|
||||||
BindingInfo = bindingInfo;
|
BindingInfo = bindingInfo;
|
||||||
Index = index;
|
Index = index;
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ITexture Texture { get; }
|
public ITexture Texture { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer cache that owns the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public BufferCache BufferCache { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Physical ranges of memory where the buffer texture data is located.
|
/// Physical ranges of memory where the buffer texture data is located.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -40,18 +45,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="stage">Shader stage accessing the texture</param>
|
/// <param name="stage">Shader stage accessing the texture</param>
|
||||||
/// <param name="texture">Buffer texture</param>
|
/// <param name="texture">Buffer texture</param>
|
||||||
|
/// <param name="bufferCache">Buffer cache that owns the buffer</param>
|
||||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||||
/// <param name="bindingInfo">Binding info</param>
|
/// <param name="bindingInfo">Binding info</param>
|
||||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||||
public BufferTextureBinding(
|
public BufferTextureBinding(
|
||||||
ShaderStage stage,
|
ShaderStage stage,
|
||||||
ITexture texture,
|
ITexture texture,
|
||||||
|
BufferCache bufferCache,
|
||||||
MultiRange range,
|
MultiRange range,
|
||||||
TextureBindingInfo bindingInfo,
|
TextureBindingInfo bindingInfo,
|
||||||
bool isImage)
|
bool isImage)
|
||||||
{
|
{
|
||||||
Stage = stage;
|
Stage = stage;
|
||||||
Texture = texture;
|
Texture = texture;
|
||||||
|
BufferCache = bufferCache;
|
||||||
Range = range;
|
Range = range;
|
||||||
BindingInfo = bindingInfo;
|
BindingInfo = bindingInfo;
|
||||||
IsImage = isImage;
|
IsImage = isImage;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
struct IndexBuffer
|
struct IndexBuffer
|
||||||
{
|
{
|
||||||
|
public BufferCache BufferCache;
|
||||||
public MultiRange Range;
|
public MultiRange Range;
|
||||||
public IndexType Type;
|
public IndexType Type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
@@ -35,10 +36,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
public event EventHandler<UnmapEventArgs> MemoryUnmapped;
|
public event EventHandler<UnmapEventArgs> MemoryUnmapped;
|
||||||
|
|
||||||
/// <summary>
|
private readonly GpuContext _context;
|
||||||
/// Physical memory where the virtual memory is mapped into.
|
private readonly List<PhysicalMemory> _physicalMemoryList;
|
||||||
/// </summary>
|
private readonly Dictionary<PhysicalMemory, byte> _physicalMemoryMap;
|
||||||
internal PhysicalMemory Physical { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Virtual range cache.
|
/// Virtual range cache.
|
||||||
@@ -53,19 +53,65 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the GPU memory manager.
|
/// Creates a new instance of the GPU memory manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
/// <param name="physicalMemory">Physical memory that this memory manager will map into</param>
|
/// <param name="physicalMemory">Physical memory that this memory manager will map into</param>
|
||||||
/// <param name="cpuMemorySize">The amount of physical CPU Memory Avaiable on the device.</param>
|
/// <param name="cpuMemorySize">The amount of physical CPU Memory Avaiable on the device.</param>
|
||||||
internal MemoryManager(PhysicalMemory physicalMemory, ulong cpuMemorySize)
|
|
||||||
|
internal MemoryManager(GpuContext context, PhysicalMemory physicalMemory, ulong cpuMemorySize)
|
||||||
{
|
{
|
||||||
Physical = physicalMemory;
|
_context = context;
|
||||||
|
|
||||||
|
_physicalMemoryList = new List<PhysicalMemory>()
|
||||||
|
{
|
||||||
|
physicalMemory
|
||||||
|
};
|
||||||
|
|
||||||
|
_physicalMemoryMap = new Dictionary<PhysicalMemory, byte>
|
||||||
|
{
|
||||||
|
{ physicalMemory, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
VirtualRangeCache = new VirtualRangeCache(this);
|
VirtualRangeCache = new VirtualRangeCache(this);
|
||||||
CounterCache = new CounterCache();
|
CounterCache = new CounterCache();
|
||||||
_pageTable = new ulong[PtLvl0Size][];
|
_pageTable = new ulong[PtLvl0Size][];
|
||||||
MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler;
|
MemoryUnmapped += physicalMemory.TextureCache.MemoryUnmappedHandler;
|
||||||
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
MemoryUnmapped += physicalMemory.BufferCache.MemoryUnmappedHandler;
|
||||||
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
||||||
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
||||||
Physical.TextureCache.Initialize(cpuMemorySize);
|
physicalMemory.TextureCache.Initialize(cpuMemorySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches the memory manager to a new GPU channel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rebind">Action to be performed when the buffer cache changes</param>
|
||||||
|
internal void AttachToChannel(Action rebind)
|
||||||
|
{
|
||||||
|
PhysicalMemory physicalMemory = GetOwnPhysicalMemory();
|
||||||
|
|
||||||
|
physicalMemory.IncrementReferenceCount();
|
||||||
|
physicalMemory.BufferCache.NotifyBuffersModified += rebind;
|
||||||
|
physicalMemory.BufferCache.QueuePrune();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches the memory manager to a new GPU channel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rebind">Action that was performed when the buffer cache changed</param>
|
||||||
|
internal void DetachFromChannel(Action rebind)
|
||||||
|
{
|
||||||
|
PhysicalMemory physicalMemory = GetOwnPhysicalMemory();
|
||||||
|
|
||||||
|
physicalMemory.BufferCache.NotifyBuffersModified -= rebind;
|
||||||
|
physicalMemory.DecrementReferenceCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues a prune of invalid entries on the buffer cache.
|
||||||
|
/// </summary>
|
||||||
|
internal void QueuePrune()
|
||||||
|
{
|
||||||
|
GetOwnPhysicalMemory().BufferCache.QueuePrune();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -81,15 +127,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
if (IsContiguous(va, size))
|
if (IsContiguous(va, size))
|
||||||
{
|
{
|
||||||
ulong address = Translate(va);
|
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
if (tracked)
|
if (tracked)
|
||||||
{
|
{
|
||||||
return Physical.ReadTracked<T>(address);
|
return physicalMemory.ReadTracked<T>(address);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return Physical.Read<T>(address);
|
return physicalMemory.Read<T>(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -113,7 +159,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
if (IsContiguous(va, size))
|
if (IsContiguous(va, size))
|
||||||
{
|
{
|
||||||
return Physical.GetSpan(Translate(va), size, tracked);
|
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
|
return physicalMemory.GetSpan(address, size, tracked);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -138,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
bool isContiguous = true;
|
bool isContiguous = true;
|
||||||
int mappedSize;
|
int mappedSize;
|
||||||
|
|
||||||
if (ValidateAddress(va) && GetPte(va) != PteUnmapped && Physical.IsMapped(Translate(va)))
|
if (ValidateAddress(va) && IsMappedOnGpuAndPhysical(va))
|
||||||
{
|
{
|
||||||
ulong endVa = va + (ulong)size;
|
ulong endVa = va + (ulong)size;
|
||||||
ulong endVaAligned = (endVa + PageMask) & ~PageMask;
|
ulong endVaAligned = (endVa + PageMask) & ~PageMask;
|
||||||
@@ -151,7 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ulong nextVa = currentVa + PageSize;
|
ulong nextVa = currentVa + PageSize;
|
||||||
ulong nextPa = Translate(nextVa);
|
ulong nextPa = Translate(nextVa);
|
||||||
|
|
||||||
if (!ValidateAddress(nextVa) || GetPte(nextVa) == PteUnmapped || !Physical.IsMapped(nextPa))
|
if (!ValidateAddress(nextVa) || !IsMappedOnGpuAndPhysical(nextVa))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -180,7 +228,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
if (isContiguous)
|
if (isContiguous)
|
||||||
{
|
{
|
||||||
return Physical.GetSpan(Translate(va), mappedSize, tracked);
|
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
|
return physicalMemory.GetSpan(address, mappedSize, tracked);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -192,6 +242,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a page of memory is mapped on the GPU and its backing memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">GPU virtual address of the page</param>
|
||||||
|
/// <returns>True if mapped, false otherwise</returns>
|
||||||
|
private bool IsMappedOnGpuAndPhysical(ulong va)
|
||||||
|
{
|
||||||
|
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
|
if (address == PteUnmapped)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return physicalMemory.IsMapped(address);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads data from a possibly non-contiguous region of GPU mapped memory.
|
/// Reads data from a possibly non-contiguous region of GPU mapped memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -209,22 +276,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
if ((va & PageMask) != 0)
|
if ((va & PageMask) != 0)
|
||||||
{
|
{
|
||||||
ulong pa = Translate(va);
|
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||||
|
|
||||||
Physical.GetSpan(pa, size, tracked).CopyTo(data[..size]);
|
physicalMemory.GetSpan(pa, size, tracked).CopyTo(data[..size]);
|
||||||
|
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; offset < data.Length; offset += size)
|
for (; offset < data.Length; offset += size)
|
||||||
{
|
{
|
||||||
ulong pa = Translate(va + (ulong)offset);
|
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
|
||||||
|
|
||||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||||
|
|
||||||
Physical.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
|
physicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,15 +306,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
if (IsContiguous(va, size))
|
if (IsContiguous(va, size))
|
||||||
{
|
{
|
||||||
return Physical.GetWritableRegion(Translate(va), size, tracked);
|
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
|
return physicalMemory.GetWritableRegion(address, size, tracked);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
|
Memory<byte> memory = new byte[size];
|
||||||
|
|
||||||
ReadImpl(va, memoryOwner.Span, tracked);
|
GetSpan(va, size).CopyTo(memory.Span);
|
||||||
|
|
||||||
return new WritableRegion(this, va, memoryOwner, tracked);
|
return new WritableRegion(this, va, memory, tracked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +338,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="data">The data to be written</param>
|
/// <param name="data">The data to be written</param>
|
||||||
public void Write(ulong va, ReadOnlySpan<byte> data)
|
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
WriteImpl(va, data, Physical.Write);
|
WriteImpl(va, data, (physical, va, data) => physical.Write(va, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -279,7 +348,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="data">The data to be written</param>
|
/// <param name="data">The data to be written</param>
|
||||||
public void WriteTrackedResource(ulong va, ReadOnlySpan<byte> data)
|
public void WriteTrackedResource(ulong va, ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
WriteImpl(va, data, Physical.WriteTrackedResource);
|
WriteImpl(va, data, (physical, va, data) => physical.WriteTrackedResource(va, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -289,10 +358,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="data">The data to be written</param>
|
/// <param name="data">The data to be written</param>
|
||||||
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
WriteImpl(va, data, Physical.WriteUntracked);
|
WriteImpl(va, data, (physical, va, data) => physical.WriteUntracked(va, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
|
private delegate void WriteCallback(PhysicalMemory physicalMemory, ulong address, ReadOnlySpan<byte> data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes data to possibly non-contiguous GPU mapped memory.
|
/// Writes data to possibly non-contiguous GPU mapped memory.
|
||||||
@@ -304,7 +373,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
if (IsContiguous(va, data.Length))
|
if (IsContiguous(va, data.Length))
|
||||||
{
|
{
|
||||||
writeCallback(Translate(va), data);
|
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
|
writeCallback(physicalMemory, address, data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -312,22 +383,67 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
if ((va & PageMask) != 0)
|
if ((va & PageMask) != 0)
|
||||||
{
|
{
|
||||||
ulong pa = Translate(va);
|
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||||
|
|
||||||
writeCallback(pa, data[..size]);
|
writeCallback(physicalMemory, pa, data[..size]);
|
||||||
|
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; offset < data.Length; offset += size)
|
for (; offset < data.Length; offset += size)
|
||||||
{
|
{
|
||||||
ulong pa = Translate(va + (ulong)offset);
|
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
|
||||||
|
|
||||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||||
|
|
||||||
writeCallback(pa, data.Slice(offset, size));
|
writeCallback(physicalMemory, pa, data.Slice(offset, size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes data to GPU mapped memory, stopping at the first unmapped page at the memory region, if any.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">GPU virtual address to write the data into</param>
|
||||||
|
/// <param name="data">The data to be written</param>
|
||||||
|
public void WriteMapped(ulong va, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
if (IsContiguous(va, data.Length))
|
||||||
|
{
|
||||||
|
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
|
physicalMemory.Write(address, data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int offset = 0, size;
|
||||||
|
|
||||||
|
if ((va & PageMask) != 0)
|
||||||
|
{
|
||||||
|
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
|
||||||
|
|
||||||
|
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||||
|
|
||||||
|
if (pa != PteUnmapped && physicalMemory.IsMapped(pa))
|
||||||
|
{
|
||||||
|
physicalMemory.Write(pa, data[..size]);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; offset < data.Length; offset += size)
|
||||||
|
{
|
||||||
|
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
|
||||||
|
|
||||||
|
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||||
|
|
||||||
|
if (pa != PteUnmapped && physicalMemory.IsMapped(pa))
|
||||||
|
{
|
||||||
|
physicalMemory.Write(pa, data.Slice(offset, size));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,15 +475,51 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="size">Size in bytes of the mapping</param>
|
/// <param name="size">Size in bytes of the mapping</param>
|
||||||
/// <param name="kind">Kind of the resource located at the mapping</param>
|
/// <param name="kind">Kind of the resource located at the mapping</param>
|
||||||
public void Map(ulong pa, ulong va, ulong size, PteKind kind)
|
public void Map(ulong pa, ulong va, ulong size, PteKind kind)
|
||||||
|
{
|
||||||
|
MapImpl(pa, va, size, kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a given range of pages to the specified CPU virtual address from a different process.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// All addresses and sizes must be page aligned.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="pa">CPU virtual address to map into</param>
|
||||||
|
/// <param name="va">GPU virtual address to be mapped</param>
|
||||||
|
/// <param name="size">Size in bytes of the mapping</param>
|
||||||
|
/// <param name="kind">Kind of the resource located at the mapping</param>
|
||||||
|
/// <param name="ownedPid">PID of the process that owns the mapping</param>
|
||||||
|
public void MapForeign(ulong pa, ulong va, ulong size, PteKind kind, ulong ownedPid)
|
||||||
|
{
|
||||||
|
if (_context.PhysicalMemoryRegistry.TryGetValue(ownedPid, out PhysicalMemory physicalMemory))
|
||||||
|
{
|
||||||
|
MapImpl(pa, va, size, kind, physicalMemory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a given range of pages to the specified CPU virtual address.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// All addresses and sizes must be page aligned.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="pa">CPU virtual address to map into</param>
|
||||||
|
/// <param name="va">GPU virtual address to be mapped</param>
|
||||||
|
/// <param name="size">Size in bytes of the mapping</param>
|
||||||
|
/// <param name="kind">Kind of the resource located at the mapping</param>
|
||||||
|
/// <param name="physicalMemory">Optional physical memory to import for the mapping</param>
|
||||||
|
private void MapImpl(ulong pa, ulong va, ulong size, PteKind kind, PhysicalMemory physicalMemory = null)
|
||||||
{
|
{
|
||||||
lock (_pageTable)
|
lock (_pageTable)
|
||||||
{
|
{
|
||||||
UnmapEventArgs e = new(va, size);
|
UnmapEventArgs e = new(va, size);
|
||||||
MemoryUnmapped?.Invoke(this, e);
|
MemoryUnmapped?.Invoke(this, e);
|
||||||
|
byte pIndex = physicalMemory != null ? GetOrAddPhysicalMemory(physicalMemory) : (byte)0;
|
||||||
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
for (ulong offset = 0; offset < size; offset += PageSize)
|
||||||
{
|
{
|
||||||
SetPte(va + offset, PackPte(pa + offset, kind));
|
SetPte(va + offset, PackPte(pa + offset, pIndex, kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
RunRemapActions(e);
|
RunRemapActions(e);
|
||||||
@@ -418,12 +570,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
for (int page = 0; page < pages - 1; page++)
|
for (int page = 0; page < pages - 1; page++)
|
||||||
{
|
{
|
||||||
if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped)
|
ulong nextPte = GetPte(va + PageSize);
|
||||||
|
|
||||||
|
if (!ValidateAddress(va + PageSize) || nextPte == PteUnmapped)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Translate(va) + PageSize != Translate(va + PageSize))
|
if (GetPte(va) + PageSize != nextPte)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -457,7 +611,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
int pages = (int)((endVaRounded - va) / PageSize);
|
int pages = (int)((endVaRounded - va) / PageSize);
|
||||||
|
|
||||||
List<MemoryRange> regions = [];
|
List<MemoryRange> regions = new();
|
||||||
|
|
||||||
for (int page = 0; page < pages - 1; page++)
|
for (int page = 0; page < pages - 1; page++)
|
||||||
{
|
{
|
||||||
@@ -535,6 +689,49 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the backing memory for a given GPU virtual address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">GPU virtual address to get the backing memory from</param>
|
||||||
|
/// <returns>The backing memory for the specified GPU virtual address</returns>
|
||||||
|
internal PhysicalMemory GetBackingMemory(ulong va)
|
||||||
|
{
|
||||||
|
ulong pte = GetPte(va);
|
||||||
|
|
||||||
|
if (pte == PteUnmapped)
|
||||||
|
{
|
||||||
|
return GetOwnPhysicalMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _physicalMemoryList[UnpackPIndexFromPte(pte)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the backing memory that is owned by this GPU memory manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The backing memory owned by this memory manager</returns>
|
||||||
|
private PhysicalMemory GetOwnPhysicalMemory()
|
||||||
|
{
|
||||||
|
return _physicalMemoryList[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index for a given physical memory on the list, adding it to the list if needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="physicalMemory">Physical memory to get the index from</param>
|
||||||
|
/// <returns>The index of the physical memory on the list</returns>
|
||||||
|
private byte GetOrAddPhysicalMemory(PhysicalMemory physicalMemory)
|
||||||
|
{
|
||||||
|
if (!_physicalMemoryMap.TryGetValue(physicalMemory, out byte pIndex))
|
||||||
|
{
|
||||||
|
pIndex = checked((byte)_physicalMemoryList.Count);
|
||||||
|
_physicalMemoryList.Add(physicalMemory);
|
||||||
|
_physicalMemoryMap.Add(physicalMemory, pIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pIndex;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates a GPU virtual address.
|
/// Validates a GPU virtual address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -636,6 +833,28 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
return Math.Min(maxSize, va - startVa);
|
return Math.Min(maxSize, va - startVa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Translates a GPU virtual address to a CPU virtual address and the associated physical memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">GPU virtual address to be translated</param>
|
||||||
|
/// <returns>CPU virtual address with the physical memory, or <see cref="PteUnmapped"/> if unmapped</returns>
|
||||||
|
private (PhysicalMemory, ulong) TranslateWithPhysicalMemory(ulong va)
|
||||||
|
{
|
||||||
|
if (!ValidateAddress(va))
|
||||||
|
{
|
||||||
|
return (GetOwnPhysicalMemory(), PteUnmapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong pte = GetPte(va);
|
||||||
|
|
||||||
|
if (pte == PteUnmapped)
|
||||||
|
{
|
||||||
|
return (GetOwnPhysicalMemory(), PteUnmapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (_physicalMemoryList[UnpackPIndexFromPte(pte)], UnpackPaFromPte(pte) + (va & PageMask));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the kind of a given memory page.
|
/// Gets the kind of a given memory page.
|
||||||
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.
|
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.
|
||||||
@@ -659,6 +878,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
return UnpackKindFromPte(pte);
|
return UnpackKindFromPte(pte);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsForeignMapping(ulong va)
|
||||||
|
{
|
||||||
|
ulong pte = GetPte(va);
|
||||||
|
|
||||||
|
if (pte == PteUnmapped)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UnpackPIndexFromPte(pte) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Page Table entry for a given GPU virtual address.
|
/// Gets the Page Table entry for a given GPU virtual address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -704,11 +935,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// Creates a page table entry from a physical address and kind.
|
/// Creates a page table entry from a physical address and kind.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pa">Physical address</param>
|
/// <param name="pa">Physical address</param>
|
||||||
|
/// <param name="pIndex">Index of the physical memory on the list</param>
|
||||||
/// <param name="kind">Kind</param>
|
/// <param name="kind">Kind</param>
|
||||||
/// <returns>Page table entry</returns>
|
/// <returns>Page table entry</returns>
|
||||||
private static ulong PackPte(ulong pa, PteKind kind)
|
private static ulong PackPte(ulong pa, byte pIndex, PteKind kind)
|
||||||
{
|
{
|
||||||
return pa | ((ulong)kind << 56);
|
return pa | ((ulong)pIndex << 48) | ((ulong)kind << 56);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -721,6 +953,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
return (PteKind)(pte >> 56);
|
return (PteKind)(pte >> 56);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unpacks the physical memory index in the list from a page table entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pte">Page table entry</param>
|
||||||
|
/// <returns>Physical memory index</returns>
|
||||||
|
private static byte UnpackPIndexFromPte(ulong pte)
|
||||||
|
{
|
||||||
|
return (byte)(pte >> 48);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unpacks physical address from a page table entry.
|
/// Unpacks physical address from a page table entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -728,7 +970,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <returns>Physical address</returns>
|
/// <returns>Physical address</returns>
|
||||||
private static ulong UnpackPaFromPte(ulong pte)
|
private static ulong UnpackPaFromPte(ulong pte)
|
||||||
{
|
{
|
||||||
return pte & 0xffffffffffffffUL;
|
return pte & 0xffffffffffffUL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
struct VertexBuffer
|
struct VertexBuffer
|
||||||
{
|
{
|
||||||
|
public BufferCache BufferCache;
|
||||||
public MultiRange Range;
|
public MultiRange Range;
|
||||||
public int Stride;
|
public int Stride;
|
||||||
public int Divisor;
|
public int Divisor;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
@@ -66,11 +67,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint ConstantBuffer1Read(int offset)
|
public uint ConstantBuffer1Read(int offset)
|
||||||
{
|
{
|
||||||
ulong baseAddress = _compute
|
(PhysicalMemory physical, ulong baseAddress) = _compute
|
||||||
? _channel.BufferManager.GetComputeUniformBufferAddress(1)
|
? _channel.BufferManager.GetComputeUniformBufferAddress(1)
|
||||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(_stageIndex, 1);
|
: _channel.BufferManager.GetGraphicsUniformBufferAddress(_stageIndex, 1);
|
||||||
|
|
||||||
return _channel.MemoryManager.Physical.Read<uint>(baseAddress + (ulong)offset);
|
return physical.Read<uint>(baseAddress + (ulong)offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -733,15 +733,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
byte[] codeB,
|
byte[] codeB,
|
||||||
bool asCompute)
|
bool asCompute)
|
||||||
{
|
{
|
||||||
ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
(PhysicalMemory physical, ulong cb1DataAddress) = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||||
|
|
||||||
MemoryManager memoryManager = channel.MemoryManager;
|
MemoryManager memoryManager = channel.MemoryManager;
|
||||||
|
|
||||||
codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
|
codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
|
||||||
codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
|
codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
|
||||||
byte[] cb1DataA = ReadArray(memoryManager, cb1DataAddress, vertexA.Cb1DataSize);
|
byte[] cb1DataA = ReadArray(physical, cb1DataAddress, vertexA.Cb1DataSize);
|
||||||
byte[] cb1DataB = ReadArray(memoryManager, cb1DataAddress, currentStage.Cb1DataSize);
|
byte[] cb1DataB = ReadArray(physical, cb1DataAddress, currentStage.Cb1DataSize);
|
||||||
|
|
||||||
ShaderDumpPaths pathsA = default;
|
ShaderDumpPaths pathsA = default;
|
||||||
ShaderDumpPaths pathsB = default;
|
ShaderDumpPaths pathsB = default;
|
||||||
|
|
||||||
@@ -775,11 +775,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
{
|
{
|
||||||
MemoryManager memoryManager = channel.MemoryManager;
|
MemoryManager memoryManager = channel.MemoryManager;
|
||||||
|
|
||||||
ulong cb1DataAddress = context.Stage == ShaderStage.Compute
|
(PhysicalMemory physical, ulong cb1DataAddress) = context.Stage == ShaderStage.Compute
|
||||||
? channel.BufferManager.GetComputeUniformBufferAddress(1)
|
? channel.BufferManager.GetComputeUniformBufferAddress(1)
|
||||||
: channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1);
|
: channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1);
|
||||||
|
|
||||||
byte[] cb1Data = ReadArray(memoryManager, cb1DataAddress, context.Cb1DataSize);
|
byte[] cb1Data = ReadArray(physical, cb1DataAddress, context.Cb1DataSize);
|
||||||
code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
|
code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
|
||||||
|
|
||||||
ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
|
ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
|
||||||
@@ -793,18 +793,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads data from physical memory, returns an empty array if the memory is unmapped or size is 0.
|
/// Reads data from physical memory, returns an empty array if the memory is unmapped or size is 0.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="memoryManager">Memory manager with the physical memory to read from</param>
|
/// <param name="physicalMemory">Physical memory to read the data from, might be null</param>
|
||||||
/// <param name="address">Physical address of the region to read</param>
|
/// <param name="address">Physical address of the region to read</param>
|
||||||
/// <param name="size">Size in bytes of the data</param>
|
/// <param name="size">Size in bytes of the data</param>
|
||||||
/// <returns>An array with the data at the specified memory location</returns>
|
/// <returns>An array with the data at the specified memory location</returns>
|
||||||
private static byte[] ReadArray(MemoryManager memoryManager, ulong address, int size)
|
private static byte[] ReadArray(PhysicalMemory physicalMemory, ulong address, int size)
|
||||||
{
|
{
|
||||||
if (address == MemoryManager.PteUnmapped || size == 0)
|
if (address == MemoryManager.PteUnmapped || size == 0 || physicalMemory == null)
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return memoryManager.Physical.GetSpan(address, size).ToArray();
|
return physicalMemory.GetSpan(address, size).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -696,7 +696,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
{
|
{
|
||||||
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
|
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
|
||||||
|
|
||||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
|
||||||
cachedTextureBufferIndex = textureBufferIndex;
|
cachedTextureBufferIndex = textureBufferIndex;
|
||||||
|
|
||||||
if (samplerBufferIndex == textureBufferIndex)
|
if (samplerBufferIndex == textureBufferIndex)
|
||||||
@@ -710,7 +710,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
{
|
{
|
||||||
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
|
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
|
||||||
|
|
||||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
|
||||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,18 @@ using System.Threading;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu
|
namespace Ryujinx.Graphics.Gpu
|
||||||
{
|
{
|
||||||
|
using Texture = Image.Texture;
|
||||||
|
|
||||||
|
public record TextureData(int Width, int Height, byte[] Data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GPU image presentation window.
|
/// GPU image presentation window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Window
|
public class Window
|
||||||
{
|
{
|
||||||
|
private const int CaptureTextureWidth = 1280;
|
||||||
|
private const int CaptureTextureHeight = 720;
|
||||||
|
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -85,7 +92,21 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PresentedTexture
|
||||||
|
{
|
||||||
|
public readonly Texture Texture;
|
||||||
|
public readonly ImageCrop Crop;
|
||||||
|
|
||||||
|
public PresentedTexture(Texture texture, ImageCrop crop)
|
||||||
|
{
|
||||||
|
Texture = texture;
|
||||||
|
Crop = crop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly ConcurrentQueue<PresentationTexture> _frameQueue;
|
private readonly ConcurrentQueue<PresentationTexture> _frameQueue;
|
||||||
|
private PresentedTexture _lastPresentedTexture;
|
||||||
|
private ITexture _captureTexture;
|
||||||
|
|
||||||
private int _framesAvailable;
|
private int _framesAvailable;
|
||||||
|
|
||||||
@@ -188,6 +209,51 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TextureData GetLastPresentedData()
|
||||||
|
{
|
||||||
|
PresentedTexture pt = Volatile.Read(ref _lastPresentedTexture);
|
||||||
|
|
||||||
|
if (pt != null)
|
||||||
|
{
|
||||||
|
byte[] inputData = CaptureLastFrame(pt.Texture.HostTexture, pt.Crop);
|
||||||
|
|
||||||
|
int size = SizeCalculator.GetBlockLinearTextureSize(
|
||||||
|
CaptureTextureWidth,
|
||||||
|
CaptureTextureHeight,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
16,
|
||||||
|
1,
|
||||||
|
1).TotalSize;
|
||||||
|
|
||||||
|
byte[] data = new byte[size];
|
||||||
|
|
||||||
|
LayoutConverter.ConvertLinearToBlockLinear(data, CaptureTextureWidth, CaptureTextureHeight, CaptureTextureWidth * 4, 4, 16, inputData);
|
||||||
|
|
||||||
|
return new TextureData(CaptureTextureWidth, CaptureTextureHeight, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextureData(0, 0, Array.Empty<byte>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureData GetLastPresentedDataLinear()
|
||||||
|
{
|
||||||
|
PresentedTexture pt = Volatile.Read(ref _lastPresentedTexture);
|
||||||
|
|
||||||
|
if (pt != null)
|
||||||
|
{
|
||||||
|
byte[] inputData = CaptureLastFrame(pt.Texture.HostTexture, new ImageCrop());
|
||||||
|
|
||||||
|
return new TextureData(pt.Texture.Info.Width, pt.Texture.Info.Height, inputData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextureData(0, 0, Array.Empty<byte>());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Presents a texture on the queue.
|
/// Presents a texture on the queue.
|
||||||
/// If the queue is empty, then no texture is presented.
|
/// If the queue is empty, then no texture is presented.
|
||||||
@@ -205,6 +271,10 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
|
|
||||||
pt.Cache.Tick();
|
pt.Cache.Tick();
|
||||||
|
|
||||||
|
EnsureCaptureTexture();
|
||||||
|
|
||||||
|
Volatile.Write(ref _lastPresentedTexture, new PresentedTexture(texture, pt.Crop));
|
||||||
|
|
||||||
texture.SynchronizeMemory();
|
texture.SynchronizeMemory();
|
||||||
|
|
||||||
ImageCrop crop = new(
|
ImageCrop crop = new(
|
||||||
@@ -244,6 +314,96 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EnsureCaptureTexture()
|
||||||
|
{
|
||||||
|
if (_captureTexture == null)
|
||||||
|
{
|
||||||
|
_captureTexture = _context.Renderer.CreateTexture(new TextureCreateInfo(
|
||||||
|
1280,
|
||||||
|
720,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
Format.R8G8B8A8Unorm,
|
||||||
|
DepthStencilMode.Depth,
|
||||||
|
Target.Texture2D,
|
||||||
|
SwizzleComponent.Red,
|
||||||
|
SwizzleComponent.Green,
|
||||||
|
SwizzleComponent.Blue,
|
||||||
|
SwizzleComponent.Alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] CaptureLastFrame(ITexture lastFrame, ImageCrop crop)
|
||||||
|
{
|
||||||
|
int cropLeft, cropRight, cropTop, cropBottom;
|
||||||
|
|
||||||
|
if (crop.Left == 0 && crop.Right == 0)
|
||||||
|
{
|
||||||
|
cropLeft = 0;
|
||||||
|
cropRight = lastFrame.Width;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cropLeft = crop.Left;
|
||||||
|
cropRight = crop.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crop.Top == 0 && crop.Bottom == 0)
|
||||||
|
{
|
||||||
|
cropTop = 0;
|
||||||
|
cropBottom = lastFrame.Height;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cropTop = crop.Top;
|
||||||
|
cropBottom = crop.Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x1, y1, x2, y2;
|
||||||
|
|
||||||
|
if (crop.FlipX)
|
||||||
|
{
|
||||||
|
x1 = cropRight;
|
||||||
|
x2 = cropLeft;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x1 = cropLeft;
|
||||||
|
x2 = cropRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crop.FlipY)
|
||||||
|
{
|
||||||
|
y1 = cropBottom;
|
||||||
|
y2 = cropTop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
y1 = cropTop;
|
||||||
|
y2 = cropBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
Extents2D srcRegion = new(x1, y1, x2, y2);
|
||||||
|
Extents2D dstRegion = new(0, 0, CaptureTextureWidth, CaptureTextureHeight);
|
||||||
|
|
||||||
|
byte[] outputData = null;
|
||||||
|
|
||||||
|
_context.Renderer.BackgroundContextAction(() =>
|
||||||
|
{
|
||||||
|
lastFrame.CopyTo(_captureTexture, srcRegion, dstRegion, true);
|
||||||
|
|
||||||
|
using var data = _captureTexture.GetData();
|
||||||
|
|
||||||
|
outputData = data.Get().ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
return outputData;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicate that a frame on the queue is ready to be acquired.
|
/// Indicate that a frame on the queue is ready to be acquired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
private readonly ICpuContext _cpuContext;
|
private readonly ICpuContext _cpuContext;
|
||||||
private T _memoryManager;
|
private T _memoryManager;
|
||||||
|
|
||||||
public IVirtualMemoryManager AddressSpace => _memoryManager;
|
public IVirtualMemoryManagerTracked AddressSpace => _memoryManager;
|
||||||
|
|
||||||
public ulong AddressSpaceSize { get; }
|
public ulong AddressSpaceSize { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
internal ServerBase TimeServer { get; private set; }
|
internal ServerBase TimeServer { get; private set; }
|
||||||
internal ServerBase ViServer { get; private set; }
|
internal ServerBase ViServer { get; private set; }
|
||||||
internal ServerBase ViServerM { get; private set; }
|
internal ServerBase ViServerM { get; private set; }
|
||||||
internal ServerBase ViServerS { get; private set; }
|
internal ViServer ViServerS { get; private set; }
|
||||||
internal ServerBase LdnServer { get; private set; }
|
internal ServerBase LdnServer { get; private set; }
|
||||||
|
|
||||||
internal KSharedMemory HidSharedMem { get; private set; }
|
internal KSharedMemory HidSharedMem { get; private set; }
|
||||||
@@ -254,7 +254,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
TimeServer = new ServerBase(KernelContext, "TimeServer");
|
TimeServer = new ServerBase(KernelContext, "TimeServer");
|
||||||
ViServer = new ServerBase(KernelContext, "ViServerU");
|
ViServer = new ServerBase(KernelContext, "ViServerU");
|
||||||
ViServerM = new ServerBase(KernelContext, "ViServerM");
|
ViServerM = new ServerBase(KernelContext, "ViServerM");
|
||||||
ViServerS = new ServerBase(KernelContext, "ViServerS");
|
ViServerS = new ViServer(KernelContext, "ViServerS");
|
||||||
LdnServer = new ServerBase(KernelContext, "LdnServer");
|
LdnServer = new ServerBase(KernelContext, "LdnServer");
|
||||||
|
|
||||||
StartNewServices();
|
StartNewServices();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
{
|
{
|
||||||
interface IProcessContext : IDisposable
|
interface IProcessContext : IDisposable
|
||||||
{
|
{
|
||||||
IVirtualMemoryManager AddressSpace { get; }
|
IVirtualMemoryManagerTracked AddressSpace { get; }
|
||||||
|
|
||||||
ulong AddressSpaceSize { get; }
|
ulong AddressSpaceSize { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
private IProcessContextFactory _contextFactory;
|
private IProcessContextFactory _contextFactory;
|
||||||
public IProcessContext Context { get; private set; }
|
public IProcessContext Context { get; private set; }
|
||||||
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
|
public IVirtualMemoryManagerTracked CpuMemory => Context.AddressSpace;
|
||||||
|
|
||||||
public HleProcessDebugger Debugger { get; private set; }
|
public HleProcessDebugger Debugger { get; private set; }
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
{
|
{
|
||||||
class ProcessContext : IProcessContext
|
class ProcessContext : IProcessContext
|
||||||
{
|
{
|
||||||
public IVirtualMemoryManager AddressSpace { get; }
|
public IVirtualMemoryManagerTracked AddressSpace { get; }
|
||||||
|
|
||||||
public ulong AddressSpaceSize { get; }
|
public ulong AddressSpaceSize { get; }
|
||||||
|
|
||||||
public ProcessContext(IVirtualMemoryManager asManager, ulong addressSpaceSize)
|
public ProcessContext(IVirtualMemoryManagerTracked asManager, ulong addressSpaceSize)
|
||||||
{
|
{
|
||||||
AddressSpace = asManager;
|
AddressSpace = asManager;
|
||||||
AddressSpaceSize = addressSpaceSize;
|
AddressSpaceSize = addressSpaceSize;
|
||||||
|
|||||||
@@ -102,5 +102,34 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(22)]
|
||||||
|
// AcquireLastApplicationCaptureSharedBuffer() -> (b8, u32)
|
||||||
|
public ResultCode AcquireLastApplicationCaptureSharedBuffer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(1);
|
||||||
|
context.ResponseData.Write(context.Device.System.ViServerS.GetApplicationLastPresentedFrameHandle(context.Device.Gpu));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(26)]
|
||||||
|
|
||||||
|
// AcquireCallerAppletCaptureSharedBuffer() -> (b8, u32)
|
||||||
|
public ResultCode AcquireCallerAppletCaptureSharedBuffer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
// TODO: How does the handling for applets differ from the one for applications?
|
||||||
|
context.ResponseData.Write(1);
|
||||||
|
context.ResponseData.Write(context.Device.System.ViServerS.GetApplicationLastPresentedFrameHandle(context.Device.Gpu));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(27)]
|
||||||
|
public ResultCode ReleaseCallerAppletCaptureSharedBuffer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(2);
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,7 +225,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
public ResultCode CreateManagedDisplayLayer(ServiceCtx context)
|
public ResultCode CreateManagedDisplayLayer(ServiceCtx context)
|
||||||
{
|
{
|
||||||
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, _pid);
|
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, _pid);
|
||||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
|
||||||
|
|
||||||
context.ResponseData.Write(layerId);
|
context.ResponseData.Write(layerId);
|
||||||
|
|
||||||
@@ -236,18 +235,27 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
// IsSystemBufferSharingEnabled()
|
// IsSystemBufferSharingEnabled()
|
||||||
public ResultCode IsSystemBufferSharingEnabled(ServiceCtx context)
|
public ResultCode IsSystemBufferSharingEnabled(ServiceCtx context)
|
||||||
{
|
{
|
||||||
// NOTE: Service checks a private field and return an error if the SystemBufferSharing is disabled.
|
// TODO: Implement this once we have a way to check if we're not an AppletId.Application
|
||||||
|
return ResultCode.Success;
|
||||||
return ResultCode.NotImplemented;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(42)] // 4.0.0+
|
||||||
|
// GetSystemSharedLayerHandle() -> (nn::vi::fbshare::SharedBufferHandle, nn::vi::fbshare::SharedLayerHandle)
|
||||||
|
public ResultCode GetSystemSharedLayerHandle(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write((ulong)context.Device.System.ViServerS.GetSharedBufferNvMapId());
|
||||||
|
context.ResponseData.Write(context.Device.System.ViServerS.GetSharedLayerId());
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandCmif(44)] // 10.0.0+
|
[CommandCmif(44)] // 10.0.0+
|
||||||
// CreateManagedDisplaySeparableLayer() -> (u64, u64)
|
// CreateManagedDisplaySeparableLayer() -> (u64, u64)
|
||||||
public ResultCode CreateManagedDisplaySeparableLayer(ServiceCtx context)
|
public ResultCode CreateManagedDisplaySeparableLayer(ServiceCtx context)
|
||||||
{
|
{
|
||||||
context.Device.System.SurfaceFlinger.CreateLayer(out long displayLayerId, _pid);
|
context.Device.System.SurfaceFlinger.CreateLayer(out long displayLayerId, _pid);
|
||||||
context.Device.System.SurfaceFlinger.CreateLayer(out long recordingLayerId, _pid);
|
context.Device.System.SurfaceFlinger.CreateLayer(out long recordingLayerId, _pid);
|
||||||
context.Device.System.SurfaceFlinger.SetRenderLayer(displayLayerId);
|
//context.Device.System.SurfaceFlinger.SetRenderLayer(displayLayerId);
|
||||||
|
|
||||||
context.ResponseData.Write(displayLayerId);
|
context.ResponseData.Write(displayLayerId);
|
||||||
context.ResponseData.Write(recordingLayerId);
|
context.ResponseData.Write(recordingLayerId);
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
|
|
||||||
public Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new();
|
public Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new();
|
||||||
public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new();
|
public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new();
|
||||||
|
|
||||||
|
internal PlayerIndex LastActiveNpad { get; set; }
|
||||||
|
|
||||||
public NpadDevices(Switch device, bool active = true) : base(device, active)
|
public NpadDevices(Switch device, bool active = true) : base(device, active)
|
||||||
{
|
{
|
||||||
@@ -384,6 +386,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LastActiveNpad = state.PlayerId;
|
||||||
|
|
||||||
ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
|
ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
|
||||||
|
|
||||||
NpadCommonState newState = new()
|
NpadCommonState newState = new()
|
||||||
@@ -639,5 +643,20 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
|
|
||||||
return rumbleQueue;
|
return rumbleQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NpadIdType GetLastActiveNpadId()
|
||||||
|
{
|
||||||
|
return LastActiveNpad switch {
|
||||||
|
PlayerIndex.Player1 => NpadIdType.Player1,
|
||||||
|
PlayerIndex.Player2 => NpadIdType.Player2,
|
||||||
|
PlayerIndex.Player3 => NpadIdType.Player3,
|
||||||
|
PlayerIndex.Player4 => NpadIdType.Player4,
|
||||||
|
PlayerIndex.Player5 => NpadIdType.Player5,
|
||||||
|
PlayerIndex.Player6 => NpadIdType.Player6,
|
||||||
|
PlayerIndex.Player7 => NpadIdType.Player7,
|
||||||
|
PlayerIndex.Player8 => NpadIdType.Player8,
|
||||||
|
_ => NpadIdType.Handheld,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// GetLastActiveNpad(u32) -> u8, u8
|
// GetLastActiveNpad(u32) -> u8, u8
|
||||||
public ResultCode GetLastActiveNpad(ServiceCtx context)
|
public ResultCode GetLastActiveNpad(ServiceCtx context)
|
||||||
{
|
{
|
||||||
// TODO: RequestData seems to have garbage data, reading an extra uint seems to fix the issue.
|
context.ResponseData.Write((byte)context.Device.Hid.Npads.GetLastActiveNpadId());
|
||||||
context.RequestData.ReadUInt32();
|
|
||||||
|
|
||||||
ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType);
|
return ResultCode.Success;
|
||||||
|
|
||||||
context.ResponseData.Write((byte)appletFooterUiType);
|
|
||||||
context.ResponseData.Write((byte)0);
|
|
||||||
|
|
||||||
return resultCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[CommandCmif(307)]
|
[CommandCmif(307)]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
@@ -105,6 +106,26 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(6)]
|
||||||
|
// SetRequirementPreset(u32)
|
||||||
|
public ResultCode SetRequirementPreset(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceNifm);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(9)]
|
||||||
|
// SetNetworkProfileId(nn::util::Uuid)
|
||||||
|
public ResultCode SetNetworkProfileId(ServiceCtx context)
|
||||||
|
{
|
||||||
|
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { uuid });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandCmif(11)]
|
[CommandCmif(11)]
|
||||||
// SetConnectionConfirmationOption(i8)
|
// SetConnectionConfirmationOption(i8)
|
||||||
public ResultCode SetConnectionConfirmationOption(ServiceCtx context)
|
public ResultCode SetConnectionConfirmationOption(ServiceCtx context)
|
||||||
@@ -142,5 +163,38 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
|||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(23)]
|
||||||
|
// SetKeptInSleep(bool)
|
||||||
|
public ResultCode SetKeptInSleep(ServiceCtx context)
|
||||||
|
{
|
||||||
|
bool keptInSleep = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { keptInSleep });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(24)]
|
||||||
|
// RegisterSocketDescriptor(s32)
|
||||||
|
public ResultCode RegisterSocketDescriptor(ServiceCtx context)
|
||||||
|
{
|
||||||
|
int socketDescriptor = context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { socketDescriptor });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(25)]
|
||||||
|
// UnregisterSocketDescriptor(s32)
|
||||||
|
public ResultCode UnregisterSocketDescriptor(ServiceCtx context)
|
||||||
|
{
|
||||||
|
int socketDescriptor = context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { socketDescriptor });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,8 +245,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle);
|
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(arguments.NvMapHandle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!");
|
||||||
@@ -282,7 +282,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
|||||||
{
|
{
|
||||||
if (_asContext.ValidateFixedBuffer(arguments.Offset, size, pageSize))
|
if (_asContext.ValidateFixedBuffer(arguments.Offset, size, pageSize))
|
||||||
{
|
{
|
||||||
_asContext.Gmm.Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind);
|
Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind, map.OwnerPid);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -301,7 +301,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
|||||||
_memoryAllocator.AllocateRange(va, size, freeAddressStartPosition);
|
_memoryAllocator.AllocateRange(va, size, freeAddressStartPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
_asContext.Gmm.Map(physicalAddress, va, size, (PteKind)arguments.Kind);
|
Map(physicalAddress, va, size, (PteKind)arguments.Kind, map.OwnerPid);
|
||||||
arguments.Offset = va;
|
arguments.Offset = va;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,8 +380,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
|||||||
ulong mapOffs = (ulong)argument.MapOffset << 16;
|
ulong mapOffs = (ulong)argument.MapOffset << 16;
|
||||||
PteKind kind = (PteKind)argument.Kind;
|
PteKind kind = (PteKind)argument.Kind;
|
||||||
|
|
||||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, nvmapHandle);
|
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(nvmapHandle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{nvmapHandle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{nvmapHandle:x8}!");
|
||||||
@@ -389,13 +389,25 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
|||||||
return NvInternalResult.InvalidInput;
|
return NvInternalResult.InvalidInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
gmm.Map(mapOffs + map.Address, gpuVa, size, kind);
|
Map(mapOffs + map.Address, gpuVa, size, kind, map.OwnerPid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
return NvInternalResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Map(ulong pa, ulong va, ulong size, PteKind kind, ulong ownerPid)
|
||||||
|
{
|
||||||
|
if (Owner == ownerPid)
|
||||||
|
{
|
||||||
|
_asContext.Gmm.Map(pa, va, size, kind);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_asContext.Gmm.MapForeign(pa, va, size, kind, ownerPid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Close() { }
|
public override void Close() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,8 +167,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|||||||
|
|
||||||
foreach (CommandBuffer commandBuffer in commandBuffers)
|
foreach (CommandBuffer commandBuffer in commandBuffers)
|
||||||
{
|
{
|
||||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBuffer.Mem);
|
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(commandBuffer.Mem);
|
||||||
|
|
||||||
ReadOnlySpan<byte> data = _memory.GetSpan(map.Address + commandBuffer.Offset, commandBuffer.WordsCount * 4);
|
ReadOnlySpan<byte> data = _memory.GetSpan(map.Address + commandBuffer.Offset, commandBuffer.WordsCount * 4);
|
||||||
|
|
||||||
_host1xContext.Host1x.Submit(MemoryMarshal.Cast<byte, int>(data), _contextId);
|
_host1xContext.Host1x.Submit(MemoryMarshal.Cast<byte, int>(data), _contextId);
|
||||||
@@ -237,8 +237,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|||||||
|
|
||||||
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
|
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
|
||||||
{
|
{
|
||||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle);
|
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(commandBufferEntry.MapHandle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
|
||||||
@@ -279,8 +279,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|||||||
|
|
||||||
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
|
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
|
||||||
{
|
{
|
||||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle);
|
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(commandBufferEntry.MapHandle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
|
|
||||||
uint size = BitUtils.AlignUp(arguments.Size, (uint)MemoryManager.PageSize);
|
uint size = BitUtils.AlignUp(arguments.Size, (uint)MemoryManager.PageSize);
|
||||||
|
|
||||||
arguments.Handle = CreateHandleFromMap(new NvMapHandle(size));
|
arguments.Handle = CreateHandleFromMap(new NvMapHandle(size) { OwnerPid = Owner });
|
||||||
|
|
||||||
Logger.Debug?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!");
|
Logger.Debug?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!");
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
return NvInternalResult.Success;
|
||||||
@@ -80,8 +80,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
|
|
||||||
private NvInternalResult FromId(ref NvMapFromId arguments)
|
private NvInternalResult FromId(ref NvMapFromId arguments)
|
||||||
{
|
{
|
||||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Id);
|
NvMapHandle map = GetMapFromHandle(arguments.Id);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||||
@@ -98,8 +98,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
|
|
||||||
private NvInternalResult Alloc(ref NvMapAlloc arguments)
|
private NvInternalResult Alloc(ref NvMapAlloc arguments)
|
||||||
{
|
{
|
||||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
NvMapHandle map = GetMapFromHandle(arguments.Handle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||||
@@ -152,8 +152,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
|
|
||||||
private NvInternalResult Free(ref NvMapFree arguments)
|
private NvInternalResult Free(ref NvMapFree arguments)
|
||||||
{
|
{
|
||||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
NvMapHandle map = GetMapFromHandle(arguments.Handle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||||
@@ -179,8 +179,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
|
|
||||||
private NvInternalResult Param(ref NvMapParam arguments)
|
private NvInternalResult Param(ref NvMapParam arguments)
|
||||||
{
|
{
|
||||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
NvMapHandle map = GetMapFromHandle(arguments.Handle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||||
@@ -217,8 +217,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
|
|
||||||
private NvInternalResult GetId(ref NvMapGetId arguments)
|
private NvInternalResult GetId(ref NvMapGetId arguments)
|
||||||
{
|
{
|
||||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
NvMapHandle map = GetMapFromHandle(arguments.Handle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||||
@@ -237,25 +237,30 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
// _maps.TryRemove(GetOwner(), out _);
|
// _maps.TryRemove(GetOwner(), out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CreateHandleFromMap(NvMapHandle map)
|
public static int CreateMap(ulong pid, ulong address, uint size)
|
||||||
|
{
|
||||||
|
return CreateHandleFromMap(new NvMapHandle(size) { Address = address, OwnerPid = pid });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CreateHandleFromMap(NvMapHandle map)
|
||||||
{
|
{
|
||||||
return _maps.Add(map);
|
return _maps.Add(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DeleteMapWithHandle(ulong pid, int handle)
|
private static bool DeleteMapWithHandle(int handle)
|
||||||
{
|
{
|
||||||
return _maps.Delete(handle) != null;
|
return _maps.Delete(handle) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void IncrementMapRefCount(ulong pid, int handle)
|
public static void IncrementMapRefCount(ulong pid, int handle)
|
||||||
{
|
{
|
||||||
GetMapFromHandle(pid, handle)?.IncrementRefCount();
|
GetMapFromHandle(handle)?.IncrementRefCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool DecrementMapRefCount(ulong pid, int handle)
|
public static bool DecrementMapRefCount(ulong pid, int handle)
|
||||||
{
|
{
|
||||||
NvMapHandle map = GetMapFromHandle(pid, handle);
|
NvMapHandle map = GetMapFromHandle(handle);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -263,8 +268,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
|
|
||||||
if (map.DecrementRefCount() <= 0)
|
if (map.DecrementRefCount() <= 0)
|
||||||
{
|
{
|
||||||
DeleteMapWithHandle(pid, handle);
|
DeleteMapWithHandle(handle);
|
||||||
|
|
||||||
Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!");
|
Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -275,7 +280,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NvMapHandle GetMapFromHandle(ulong pid, int handle)
|
public static NvMapHandle GetMapFromHandle(int handle)
|
||||||
{
|
{
|
||||||
return _maps.Get(handle);
|
return _maps.Get(handle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||||||
public ulong Address;
|
public ulong Address;
|
||||||
public bool Allocated;
|
public bool Allocated;
|
||||||
public ulong DmaMapAddress;
|
public ulong DmaMapAddress;
|
||||||
|
public ulong OwnerPid;
|
||||||
|
|
||||||
private long _dupes;
|
private long _dupes;
|
||||||
|
|
||||||
public NvMapHandle()
|
public NvMapHandle()
|
||||||
|
|||||||
@@ -172,6 +172,15 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
{
|
{
|
||||||
ServerLoop();
|
ServerLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual ulong CalculateRequiredHeapSize()
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void CustomInit(KernelContext context, ulong pid, ulong heapAddress)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private void ServerLoop()
|
private void ServerLoop()
|
||||||
{
|
{
|
||||||
@@ -196,10 +205,14 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
Result result = _selfProcess.HandleTable.GenerateHandle(_wakeEvent.ReadableEvent, out _wakeHandle);
|
Result result = _selfProcess.HandleTable.GenerateHandle(_wakeEvent.ReadableEvent, out _wakeHandle);
|
||||||
|
|
||||||
InitDone.Set();
|
InitDone.Set();
|
||||||
|
|
||||||
|
ulong heapSize = CalculateRequiredHeapSize() + PointerBufferSize;
|
||||||
|
|
||||||
ulong messagePtr = _selfThread.TlsAddress;
|
ulong messagePtr = _selfThread.TlsAddress;
|
||||||
_context.Syscall.SetHeapSize(out ulong heapAddr, 0x200000);
|
_context.Syscall.SetHeapSize(out ulong heapAddr, BitUtils.AlignUp(heapSize, 0x200000UL));
|
||||||
|
|
||||||
|
CustomInit(_context, _selfProcess.Pid, heapAddr + PointerBufferSize);
|
||||||
|
|
||||||
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
||||||
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
||||||
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
|
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.Common.PreciseSleep;
|
using Ryujinx.Common.PreciseSleep;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu;
|
using Ryujinx.Graphics.Gpu;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -38,8 +39,6 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
|
|
||||||
private readonly Lock _lock = new();
|
private readonly Lock _lock = new();
|
||||||
|
|
||||||
public long RenderLayerId { get; private set; }
|
|
||||||
|
|
||||||
private class Layer
|
private class Layer
|
||||||
{
|
{
|
||||||
public int ProducerBinderId;
|
public int ProducerBinderId;
|
||||||
@@ -60,7 +59,6 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
{
|
{
|
||||||
_device = device;
|
_device = device;
|
||||||
_layers = new Dictionary<long, Layer>();
|
_layers = new Dictionary<long, Layer>();
|
||||||
RenderLayerId = 0;
|
|
||||||
|
|
||||||
_composerThread = new Thread(HandleComposition)
|
_composerThread = new Thread(HandleComposition)
|
||||||
{
|
{
|
||||||
@@ -239,34 +237,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
|
|
||||||
private void CloseLayer(long layerId, Layer layer)
|
private void CloseLayer(long layerId, Layer layer)
|
||||||
{
|
{
|
||||||
// If the layer was removed and the current in use, we need to change the current layer in use.
|
|
||||||
if (RenderLayerId == layerId)
|
|
||||||
{
|
|
||||||
// If no layer is availaible, reset to default value.
|
|
||||||
if (_layers.Count == 0)
|
|
||||||
{
|
|
||||||
SetRenderLayer(0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SetRenderLayer(_layers.Last().Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layer.State == LayerState.ManagedOpened)
|
if (layer.State == LayerState.ManagedOpened)
|
||||||
{
|
{
|
||||||
layer.State = LayerState.ManagedClosed;
|
layer.State = LayerState.ManagedClosed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRenderLayer(long layerId)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
RenderLayerId = layerId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Layer GetLayerByIdLocked(long layerId)
|
private Layer GetLayerByIdLocked(long layerId)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<long, Layer> pair in _layers)
|
foreach (KeyValuePair<long, Layer> pair in _layers)
|
||||||
@@ -360,41 +336,55 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
// TODO: support multilayers (& multidisplay ?)
|
foreach (var (layerId, layer) in _layers)
|
||||||
if (RenderLayerId == 0)
|
|
||||||
{
|
{
|
||||||
return;
|
if (layer.State == LayerState.NotInitialized || layer.State == LayerState.ManagedClosed)
|
||||||
}
|
continue;
|
||||||
|
|
||||||
Layer layer = GetLayerByIdLocked(RenderLayerId);
|
if (_device.System.KernelContext.Processes.TryGetValue(layer.Owner, out var process))
|
||||||
|
|
||||||
Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
|
|
||||||
|
|
||||||
if (acquireStatus == Status.Success)
|
|
||||||
{
|
|
||||||
if (_device.VSyncMode == VSyncMode.Unbounded)
|
|
||||||
{
|
{
|
||||||
if (_swapInterval != 0)
|
if (process.State == ProcessState.Exiting || process.State == ProcessState.Exited)
|
||||||
{
|
{
|
||||||
UpdateSwapInterval(0);
|
HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
|
||||||
_vSyncMode = _device.VSyncMode;
|
|
||||||
|
if (_layers.Remove(layerId))
|
||||||
|
{
|
||||||
|
CloseLayer(layerId, layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_device.VSyncMode != _vSyncMode)
|
|
||||||
|
Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
|
||||||
|
|
||||||
|
if (acquireStatus == Status.Success)
|
||||||
{
|
{
|
||||||
UpdateSwapInterval(_device.VSyncMode == VSyncMode.Unbounded ? 0 : item.SwapInterval);
|
if (_device.VSyncMode == VSyncMode.Unbounded)
|
||||||
_vSyncMode = _device.VSyncMode;
|
{
|
||||||
}
|
if (_swapInterval != 0)
|
||||||
else if (item.SwapInterval != _swapInterval || _device.TargetVSyncInterval != _targetVSyncInterval)
|
{
|
||||||
{
|
UpdateSwapInterval(0);
|
||||||
UpdateSwapInterval(item.SwapInterval);
|
_vSyncMode = _device.VSyncMode;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (_device.VSyncMode != _vSyncMode)
|
||||||
|
{
|
||||||
|
UpdateSwapInterval(_device.VSyncMode == VSyncMode.Unbounded ? 0 : item.SwapInterval);
|
||||||
|
_vSyncMode = _device.VSyncMode;
|
||||||
|
}
|
||||||
|
else if (item.SwapInterval != _swapInterval || _device.TargetVSyncInterval != _targetVSyncInterval)
|
||||||
|
{
|
||||||
|
UpdateSwapInterval(item.SwapInterval);
|
||||||
|
}
|
||||||
|
|
||||||
PostFrameBuffer(layer, item);
|
PostFrameBuffer(layer, item);
|
||||||
}
|
}
|
||||||
else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation)
|
else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
Logger.Warning?.Print(LogClass.SurfaceFlinger, $"Failed to acquire buffer for layer {layerId} (status: {acquireStatus})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,8 +403,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
|
|
||||||
ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset;
|
ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset;
|
||||||
|
|
||||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle);
|
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(nvMapHandle);
|
||||||
|
|
||||||
ulong frameBufferAddress = map.Address + bufferOffset;
|
ulong frameBufferAddress = map.Address + bufferOffset;
|
||||||
|
|
||||||
Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
|
Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||||
@@ -36,6 +37,6 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
public int PlanesCount;
|
public int PlanesCount;
|
||||||
|
|
||||||
[FieldOffset(0x34)]
|
[FieldOffset(0x34)]
|
||||||
public NvGraphicBufferSurfaceArray Surfaces;
|
public Array3<NvGraphicBufferSurface> Surfaces;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
|||||||
ulong pid = context.Device.System.AppletState.AppletResourceUserIds.GetData<ulong>((int)appletResourceUserId);
|
ulong pid = context.Device.System.AppletState.AppletResourceUserIds.GetData<ulong>((int)appletResourceUserId);
|
||||||
|
|
||||||
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, pid);
|
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, pid);
|
||||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
|
||||||
|
|
||||||
context.ResponseData.Write(layerId);
|
context.ResponseData.Write(layerId);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types.Fbshare;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
||||||
{
|
{
|
||||||
@@ -7,6 +14,9 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
|||||||
#pragma warning disable IDE0052 // Remove unread private member
|
#pragma warning disable IDE0052 // Remove unread private member
|
||||||
private readonly IApplicationDisplayService _applicationDisplayService;
|
private readonly IApplicationDisplayService _applicationDisplayService;
|
||||||
#pragma warning restore IDE0052
|
#pragma warning restore IDE0052
|
||||||
|
|
||||||
|
private KEvent _sharedFramebufferAcquirableEvent;
|
||||||
|
private int _sharedFramebufferAcquirableEventHandle;
|
||||||
|
|
||||||
public ISystemDisplayService(IApplicationDisplayService applicationDisplayService)
|
public ISystemDisplayService(IApplicationDisplayService applicationDisplayService)
|
||||||
{
|
{
|
||||||
@@ -57,5 +67,138 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
|||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8225)] // 4.0.0+
|
||||||
|
// GetSharedBufferMemoryHandleId()
|
||||||
|
public ResultCode GetSharedBufferMemoryHandleId(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write((ulong)context.Device.System.ViServerS.GetSharedBufferNvMapId());
|
||||||
|
context.ResponseData.Write(context.Device.System.ViServerS.GetSharedBufferSize());
|
||||||
|
|
||||||
|
(ulong mapAddress, ulong mapSize) = context.Request.GetBufferType0x22();
|
||||||
|
|
||||||
|
context.Memory.Write(mapAddress, context.Device.System.ViServerS.GetSharedBufferMap());
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8250)] // 4.0.0+
|
||||||
|
// OpenSharedLayer(nn::vi::fbshare::SharedLayerHandle, nn::applet::AppletResourceUserId, pid)
|
||||||
|
public ResultCode OpenSharedLayer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||||
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
context.Device.System.ViServerS.OpenSharedLayer(sharedLayerHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8251)] // 4.0.0+
|
||||||
|
// CloseSharedLayer(nn::vi::fbshare::SharedLayerHandle)
|
||||||
|
public ResultCode CloseSharedLayer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
context.Device.System.ViServerS.CloseSharedLayer(sharedLayerHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8252)] // 4.0.0+
|
||||||
|
// ConnectSharedLayer(nn::vi::fbshare::SharedLayerHandle)
|
||||||
|
public ResultCode ConnectSharedLayer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
context.Device.System.ViServerS.ConnectSharedLayer(sharedLayerHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8253)] // 4.0.0+
|
||||||
|
// DisconnectSharedLayer(nn::vi::fbshare::SharedLayerHandle)
|
||||||
|
public ResultCode DisconnectSharedLayer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
context.Device.System.ViServerS.DisconnectSharedLayer(sharedLayerHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8254)] // 4.0.0+
|
||||||
|
// AcquireSharedFrameBuffer(nn::vi::fbshare::SharedLayerHandle) -> (nn::vi::native::NativeSync, nn::vi::fbshare::SharedLayerTextureIndexList, u64)
|
||||||
|
public ResultCode AcquireSharedFrameBuffer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
int slot = context.Device.System.ViServerS.DequeueFrameBuffer(sharedLayerHandle, out AndroidFence fence);
|
||||||
|
|
||||||
|
var indexList = new SharedLayerTextureIndexList();
|
||||||
|
|
||||||
|
for (int i = 0; i < indexList.Indices.Length; i++)
|
||||||
|
{
|
||||||
|
indexList.Indices[i] = context.Device.System.ViServerS.GetFrameBufferMapIndex(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.ResponseData.WriteStruct(fence);
|
||||||
|
context.ResponseData.WriteStruct(indexList);
|
||||||
|
context.ResponseData.Write(0); // Padding
|
||||||
|
context.ResponseData.Write((ulong)slot);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8255)] // 4.0.0+
|
||||||
|
// PresentSharedFrameBuffer(nn::vi::native::NativeSync, nn::vi::CropRegion, u32, u32, nn::vi::fbshare::SharedLayerHandle, u64)
|
||||||
|
public ResultCode PresentSharedFrameBuffer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
AndroidFence nativeSync = context.RequestData.ReadStruct<AndroidFence>();
|
||||||
|
Rect cropRegion = context.RequestData.ReadStruct<Rect>();
|
||||||
|
|
||||||
|
NativeWindowTransform transform = (NativeWindowTransform)context.RequestData.ReadUInt32();
|
||||||
|
int swapInterval = context.RequestData.ReadInt32();
|
||||||
|
int padding = context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||||
|
ulong slot = context.RequestData.ReadUInt64();
|
||||||
|
|
||||||
|
context.Device.System.ViServerS.QueueFrameBuffer(sharedLayerHandle, (int)slot, cropRegion, transform, swapInterval, nativeSync);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8256)] // 4.0.0+
|
||||||
|
// GetSharedFrameBufferAcquirableEvent(nn::vi::fbshare::SharedLayerHandle) -> handle<copy>
|
||||||
|
public ResultCode GetSharedFrameBufferAcquirableEvent(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (_sharedFramebufferAcquirableEventHandle == 0)
|
||||||
|
{
|
||||||
|
_sharedFramebufferAcquirableEvent = new KEvent(context.Device.System.KernelContext);
|
||||||
|
_sharedFramebufferAcquirableEvent.WritableEvent.Signal();
|
||||||
|
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_sharedFramebufferAcquirableEvent.ReadableEvent, out _sharedFramebufferAcquirableEventHandle) != Result.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_sharedFramebufferAcquirableEventHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8258)] // 5.0.0+
|
||||||
|
// CancelSharedFrameBuffer(nn::vi::fbshare::SharedLayerHandle, u64)
|
||||||
|
public ResultCode CancelSharedFrameBuffer(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||||
|
ulong slot = context.RequestData.ReadUInt64();
|
||||||
|
|
||||||
|
context.Device.System.ViServerS.CancelFrameBuffer(sharedLayerHandle, (int)slot);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types.Fbshare
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)]
|
||||||
|
struct SharedLayerTextureIndexList
|
||||||
|
{
|
||||||
|
public Array4<int> Indices;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -248,8 +248,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
|
||||||
|
|
||||||
using Parcel parcel = new(0x28, 0x4);
|
using Parcel parcel = new(0x28, 0x4);
|
||||||
|
|
||||||
parcel.WriteObject(producer, "dispdrv\0");
|
parcel.WriteObject(producer, "dispdrv\0");
|
||||||
@@ -285,9 +283,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
|||||||
|
|
||||||
// TODO: support multi display.
|
// TODO: support multi display.
|
||||||
IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, 0, LayerState.Stray);
|
IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, 0, LayerState.Stray);
|
||||||
|
|
||||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
|
||||||
|
|
||||||
using Parcel parcel = new(0x28, 0x4);
|
using Parcel parcel = new(0x28, 0x4);
|
||||||
|
|
||||||
parcel.WriteObject(producer, "dispdrv\0");
|
parcel.WriteObject(producer, "dispdrv\0");
|
||||||
|
|||||||
19
src/Ryujinx.HLE/HOS/Services/Vi/Types/SharedBufferMap.cs
Normal file
19
src/Ryujinx.HLE/HOS/Services/Vi/Types/SharedBufferMap.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Vi.Types
|
||||||
|
{
|
||||||
|
struct SharedBufferMap
|
||||||
|
{
|
||||||
|
public struct Entry
|
||||||
|
{
|
||||||
|
public ulong Offset;
|
||||||
|
public ulong Size;
|
||||||
|
public uint Width;
|
||||||
|
public uint Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count;
|
||||||
|
public int Padding;
|
||||||
|
public Array16<Entry> SharedBuffers;
|
||||||
|
}
|
||||||
|
}
|
||||||
235
src/Ryujinx.HLE/HOS/Services/Vi/ViServer.cs
Normal file
235
src/Ryujinx.HLE/HOS/Services/Vi/ViServer.cs
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
||||||
|
using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
||||||
|
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Vi.Types;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services
|
||||||
|
{
|
||||||
|
class ViServer : ServerBase
|
||||||
|
{
|
||||||
|
private const int TotalFramebuffers = 16;
|
||||||
|
|
||||||
|
private SurfaceFlinger.SurfaceFlinger _surfaceFlinger;
|
||||||
|
|
||||||
|
private readonly uint _fbWidth;
|
||||||
|
private readonly uint _fbHeight;
|
||||||
|
private readonly PixelFormat _fbFormat;
|
||||||
|
private readonly int _fbUsage;
|
||||||
|
private readonly uint _fbCount;
|
||||||
|
private uint _fbSlotsRequested;
|
||||||
|
|
||||||
|
private ulong _pid;
|
||||||
|
private ulong _fbsBaseAddress;
|
||||||
|
private int _bufferNvMapId;
|
||||||
|
private SharedBufferMap _bufferMap;
|
||||||
|
private long _sharedLayerId;
|
||||||
|
|
||||||
|
public ViServer(KernelContext context, string name) : base(context, name)
|
||||||
|
{
|
||||||
|
_fbWidth = 1280;
|
||||||
|
_fbHeight = 720;
|
||||||
|
_fbFormat = PixelFormat.Rgba8888;
|
||||||
|
_fbUsage = 0x100 | 0x200 | 0x800;
|
||||||
|
_fbCount = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ulong CalculateRequiredHeapSize()
|
||||||
|
{
|
||||||
|
return GetSharedBufferSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CustomInit(KernelContext context, ulong pid, ulong heapAddress)
|
||||||
|
{
|
||||||
|
_pid = pid;
|
||||||
|
_fbsBaseAddress = heapAddress;
|
||||||
|
|
||||||
|
context.Device.Gpu.RegisterProcess(pid, KernelStatic.GetCurrentProcess().CpuMemory);
|
||||||
|
|
||||||
|
ulong bufferSize = CalculateFramebufferSize();
|
||||||
|
ulong totalSize = bufferSize * TotalFramebuffers;
|
||||||
|
|
||||||
|
KernelStatic.GetCurrentProcess().CpuMemory.Fill(heapAddress, totalSize, 0xff);
|
||||||
|
|
||||||
|
int mapId = NvMapDeviceFile.CreateMap(pid, heapAddress, (uint)totalSize);
|
||||||
|
|
||||||
|
_bufferNvMapId = mapId;
|
||||||
|
_bufferMap.Count = TotalFramebuffers;
|
||||||
|
|
||||||
|
ulong offset = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < TotalFramebuffers; i++)
|
||||||
|
{
|
||||||
|
_bufferMap.SharedBuffers[i].Offset = offset;
|
||||||
|
_bufferMap.SharedBuffers[i].Size = bufferSize;
|
||||||
|
_bufferMap.SharedBuffers[i].Width = _fbWidth;
|
||||||
|
_bufferMap.SharedBuffers[i].Height = _fbHeight;
|
||||||
|
|
||||||
|
offset += bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surfaceFlinger = context.Device.System.SurfaceFlinger;
|
||||||
|
_surfaceFlinger.CreateLayer(out _sharedLayerId, pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenSharedLayer(long layerId)
|
||||||
|
{
|
||||||
|
_surfaceFlinger.OpenLayer(_pid, layerId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseSharedLayer(long layerId)
|
||||||
|
{
|
||||||
|
_surfaceFlinger.CloseLayer(layerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConnectSharedLayer(long layerId)
|
||||||
|
{
|
||||||
|
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||||
|
|
||||||
|
producer.Connect(null, NativeWindowApi.NVN, false, out IGraphicBufferProducer.QueueBufferOutput output);
|
||||||
|
|
||||||
|
GraphicBuffer graphicBuffer = new GraphicBuffer();
|
||||||
|
|
||||||
|
int gobHeightLog2 = 4;
|
||||||
|
int blockHeight = 8 * (1 << gobHeightLog2);
|
||||||
|
uint widthAlignedBytes = BitUtils.AlignUp(_fbWidth * 4, 64u);
|
||||||
|
uint widthAligned = widthAlignedBytes / 4;
|
||||||
|
uint heightAligned = BitUtils.AlignUp(_fbHeight, (uint)blockHeight);
|
||||||
|
uint totalSize = widthAlignedBytes * heightAligned;
|
||||||
|
|
||||||
|
graphicBuffer.Header.Magic = 0x47424652;
|
||||||
|
graphicBuffer.Header.Width = (int)_fbWidth;
|
||||||
|
graphicBuffer.Header.Height = (int)_fbHeight;
|
||||||
|
graphicBuffer.Header.Stride = (int)widthAligned;
|
||||||
|
graphicBuffer.Header.Format = _fbFormat;
|
||||||
|
graphicBuffer.Header.Usage = _fbUsage;
|
||||||
|
graphicBuffer.Header.IntsCount = (Unsafe.SizeOf<GraphicBuffer>() - Unsafe.SizeOf<GraphicBufferHeader>()) / sizeof(int);
|
||||||
|
graphicBuffer.Buffer.NvMapId = _bufferNvMapId;
|
||||||
|
graphicBuffer.Buffer.Magic = unchecked((int)0xDAFFCAFF);
|
||||||
|
graphicBuffer.Buffer.Pid = 42;
|
||||||
|
graphicBuffer.Buffer.Usage = _fbUsage;
|
||||||
|
graphicBuffer.Buffer.PixelFormat = (int)_fbFormat;
|
||||||
|
graphicBuffer.Buffer.ExternalPixelFormat = (int)_fbFormat;
|
||||||
|
graphicBuffer.Buffer.Stride = (int)widthAligned;
|
||||||
|
graphicBuffer.Buffer.FrameBufferSize = (int)totalSize;
|
||||||
|
graphicBuffer.Buffer.PlanesCount = 1;
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].Width = _fbWidth;
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].Height = _fbHeight;
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].ColorFormat = ColorFormat.A8B8G8R8;
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].Layout = 3; // Block linear
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].Pitch = (int)widthAlignedBytes;
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].Kind = 0xfe; // Generic 16Bx2
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].BlockHeightLog2 = gobHeightLog2;
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].Size = (int)totalSize;
|
||||||
|
|
||||||
|
for (int slot = 0; slot < _fbCount; slot++)
|
||||||
|
{
|
||||||
|
graphicBuffer.Buffer.Surfaces[0].Offset = slot * (int)totalSize;
|
||||||
|
|
||||||
|
producer.SetPreallocatedBuffer(slot, new AndroidStrongPointer<GraphicBuffer>(graphicBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
_fbSlotsRequested = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisconnectSharedLayer(long layerId)
|
||||||
|
{
|
||||||
|
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||||
|
|
||||||
|
producer.Disconnect(NativeWindowApi.NVN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DequeueFrameBuffer(long layerId, out AndroidFence fence)
|
||||||
|
{
|
||||||
|
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||||
|
|
||||||
|
Status status = producer.DequeueBuffer(out int slot, out fence, false, _fbWidth, _fbHeight, _fbFormat, (uint)_fbUsage);
|
||||||
|
|
||||||
|
if (status == Status.Success)
|
||||||
|
{
|
||||||
|
if ((_fbSlotsRequested & (1u << slot)) == 0)
|
||||||
|
{
|
||||||
|
status = producer.RequestBuffer(slot, out _);
|
||||||
|
|
||||||
|
if (status != Status.Success)
|
||||||
|
{
|
||||||
|
producer.CancelBuffer(slot, ref fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fbSlotsRequested |= 1u << slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueFrameBuffer(long layerId, int slot, Rect crop, NativeWindowTransform transform, int swapInterval, AndroidFence fence)
|
||||||
|
{
|
||||||
|
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||||
|
|
||||||
|
IGraphicBufferProducer.QueueBufferInput input = new();
|
||||||
|
|
||||||
|
input.Crop = crop;
|
||||||
|
input.Transform = transform;
|
||||||
|
input.SwapInterval = swapInterval;
|
||||||
|
input.Fence = fence;
|
||||||
|
|
||||||
|
Status status = producer.QueueBuffer(slot, ref input, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelFrameBuffer(long layerId, int slot)
|
||||||
|
{
|
||||||
|
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||||
|
AndroidFence fence = default;
|
||||||
|
|
||||||
|
producer.CancelBuffer(slot, ref fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetFrameBufferMapIndex(int index)
|
||||||
|
{
|
||||||
|
return (uint)index < _fbCount ? index : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetSharedBufferNvMapId()
|
||||||
|
{
|
||||||
|
return _bufferNvMapId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetSharedBufferSize()
|
||||||
|
{
|
||||||
|
return CalculateFramebufferSize() * TotalFramebuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetSharedLayerId()
|
||||||
|
{
|
||||||
|
return _sharedLayerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong CalculateFramebufferSize()
|
||||||
|
{
|
||||||
|
// Each GOB dimension is 512 bytes x 8 lines.
|
||||||
|
// Assume 16 GOBs, for a total of 16 x 8 = 128 lines.
|
||||||
|
return BitUtils.AlignUp(_fbWidth * 4, 512u) * BitUtils.AlignUp(_fbHeight, 128u);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SharedBufferMap GetSharedBufferMap()
|
||||||
|
{
|
||||||
|
return _bufferMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetApplicationLastPresentedFrameHandle(GpuContext gpuContext)
|
||||||
|
{
|
||||||
|
TextureData texture = gpuContext.Window.GetLastPresentedData();
|
||||||
|
IVirtualMemoryManagerTracked selfAs = KernelStatic.GetProcessByPid(_pid).CpuMemory;
|
||||||
|
int fbIndex = (int)_fbCount; // Place it after all our frame buffers.
|
||||||
|
|
||||||
|
selfAs.Write(_fbsBaseAddress + _bufferMap.SharedBuffers[fbIndex].Offset, texture.Data);
|
||||||
|
|
||||||
|
return fbIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +1,38 @@
|
|||||||
using MsgPack;
|
using MsgPack;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Prepo.Types;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon
|
namespace Ryujinx.Horizon
|
||||||
{
|
{
|
||||||
public static class HorizonStatic
|
public static class HorizonStatic
|
||||||
{
|
{
|
||||||
internal static void HandlePlayReport(MessagePackObject report) => PlayReportPrinted?.Invoke(report);
|
internal static void HandlePlayReport(PlayReport report) =>
|
||||||
|
new Thread(() => PlayReport?.Invoke(report))
|
||||||
|
{
|
||||||
|
Name = "HLE.PlayReportEvent",
|
||||||
|
IsBackground = true,
|
||||||
|
Priority = ThreadPriority.AboveNormal
|
||||||
|
}.Start();
|
||||||
|
|
||||||
public static event Action<MessagePackObject> PlayReportPrinted;
|
public static event Action<PlayReport> PlayReport;
|
||||||
|
|
||||||
[ThreadStatic]
|
|
||||||
private static HorizonOptions _options;
|
|
||||||
|
|
||||||
[ThreadStatic]
|
[field: ThreadStatic]
|
||||||
private static ISyscallApi _syscall;
|
public static HorizonOptions Options { get; private set; }
|
||||||
|
|
||||||
[ThreadStatic]
|
[field: ThreadStatic]
|
||||||
private static IVirtualMemoryManager _addressSpace;
|
public static ISyscallApi Syscall { get; private set; }
|
||||||
|
|
||||||
[ThreadStatic]
|
[field: ThreadStatic]
|
||||||
private static IThreadContext _threadContext;
|
public static IVirtualMemoryManager AddressSpace { get; private set; }
|
||||||
|
|
||||||
[ThreadStatic]
|
[field: ThreadStatic]
|
||||||
private static int _threadHandle;
|
public static IThreadContext ThreadContext { get; private set; }
|
||||||
|
|
||||||
public static HorizonOptions Options => _options;
|
[field: ThreadStatic]
|
||||||
public static ISyscallApi Syscall => _syscall;
|
public static int CurrentThreadHandle { get; private set; }
|
||||||
public static IVirtualMemoryManager AddressSpace => _addressSpace;
|
|
||||||
public static IThreadContext ThreadContext => _threadContext;
|
|
||||||
public static int CurrentThreadHandle => _threadHandle;
|
|
||||||
|
|
||||||
public static void Register(
|
public static void Register(
|
||||||
HorizonOptions options,
|
HorizonOptions options,
|
||||||
@@ -39,11 +41,11 @@ namespace Ryujinx.Horizon
|
|||||||
IThreadContext threadContext,
|
IThreadContext threadContext,
|
||||||
int threadHandle)
|
int threadHandle)
|
||||||
{
|
{
|
||||||
_options = options;
|
Options = options;
|
||||||
_syscall = syscallApi;
|
Syscall = syscallApi;
|
||||||
_addressSpace = addressSpace;
|
AddressSpace = addressSpace;
|
||||||
_threadContext = threadContext;
|
ThreadContext = threadContext;
|
||||||
_threadHandle = threadHandle;
|
CurrentThreadHandle = threadHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,6 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
|||||||
{
|
{
|
||||||
partial class PrepoService : IPrepoService
|
partial class PrepoService : IPrepoService
|
||||||
{
|
{
|
||||||
enum PlayReportKind
|
|
||||||
{
|
|
||||||
Normal,
|
|
||||||
System,
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ArpApi _arp;
|
private readonly ArpApi _arp;
|
||||||
private readonly PrepoServicePermissionLevel _permissionLevel;
|
private readonly PrepoServicePermissionLevel _permissionLevel;
|
||||||
private ulong _systemSessionId;
|
private ulong _systemSessionId;
|
||||||
@@ -194,10 +188,17 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
|||||||
{
|
{
|
||||||
return PrepoResult.InvalidBufferSize;
|
return PrepoResult.InvalidBufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder builder = new();
|
StringBuilder builder = new();
|
||||||
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
|
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
|
||||||
|
|
||||||
|
PlayReport playReport = new()
|
||||||
|
{
|
||||||
|
Kind = playReportKind,
|
||||||
|
Room = gameRoom,
|
||||||
|
ReportData = deserializedReport
|
||||||
|
};
|
||||||
|
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine("PlayReport log:");
|
builder.AppendLine("PlayReport log:");
|
||||||
builder.AppendLine($" Kind: {playReportKind}");
|
builder.AppendLine($" Kind: {playReportKind}");
|
||||||
@@ -207,10 +208,12 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
|||||||
if (pid != 0)
|
if (pid != 0)
|
||||||
{
|
{
|
||||||
builder.AppendLine($" Pid: {pid}");
|
builder.AppendLine($" Pid: {pid}");
|
||||||
|
playReport.Pid = pid;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.AppendLine($" ApplicationId: {applicationId}");
|
builder.AppendLine($" ApplicationId: {applicationId}");
|
||||||
|
playReport.AppId = applicationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result result = _arp.GetApplicationInstanceId(out ulong applicationInstanceId, pid);
|
Result result = _arp.GetApplicationInstanceId(out ulong applicationInstanceId, pid);
|
||||||
@@ -221,17 +224,20 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
|||||||
|
|
||||||
_arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, applicationInstanceId).AbortOnFailure();
|
_arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, applicationInstanceId).AbortOnFailure();
|
||||||
|
|
||||||
|
playReport.Version = applicationLaunchProperty.Version;
|
||||||
|
|
||||||
builder.AppendLine($" ApplicationVersion: {applicationLaunchProperty.Version}");
|
builder.AppendLine($" ApplicationVersion: {applicationLaunchProperty.Version}");
|
||||||
|
|
||||||
if (!userId.IsNull)
|
if (!userId.IsNull)
|
||||||
{
|
{
|
||||||
builder.AppendLine($" UserId: {userId}");
|
builder.AppendLine($" UserId: {userId}");
|
||||||
|
playReport.UserId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine($" Room: {gameRoom}");
|
builder.AppendLine($" Room: {gameRoom}");
|
||||||
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
|
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
|
||||||
|
|
||||||
HorizonStatic.HandlePlayReport(deserializedReport);
|
HorizonStatic.HandlePlayReport(playReport);
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());
|
Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());
|
||||||
|
|
||||||
|
|||||||
24
src/Ryujinx.Horizon/Prepo/Types/PlayReport.cs
Normal file
24
src/Ryujinx.Horizon/Prepo/Types/PlayReport.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using MsgPack;
|
||||||
|
using Ryujinx.Horizon.Sdk.Account;
|
||||||
|
using Ryujinx.Horizon.Sdk.Ncm;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Prepo.Types
|
||||||
|
{
|
||||||
|
public struct PlayReport
|
||||||
|
{
|
||||||
|
public PlayReportKind Kind { get; init; }
|
||||||
|
public string Room { get; init; }
|
||||||
|
public MessagePackObject ReportData { get; init; }
|
||||||
|
|
||||||
|
public ApplicationId? AppId;
|
||||||
|
public ulong? Pid;
|
||||||
|
public uint Version;
|
||||||
|
public Uid? UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PlayReportKind
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
System,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
|
using Ryujinx.Memory.Tracking;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -9,7 +10,7 @@ namespace Ryujinx.Memory
|
|||||||
/// Represents a address space manager.
|
/// Represents a address space manager.
|
||||||
/// Supports virtual memory region mapping, address translation and read/write access to mapped regions.
|
/// Supports virtual memory region mapping, address translation and read/write access to mapped regions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager
|
public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManagerTracked
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool UsesPrivateAllocations => false;
|
public bool UsesPrivateAllocations => false;
|
||||||
@@ -21,6 +22,8 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
private readonly MemoryBlock _backingMemory;
|
private readonly MemoryBlock _backingMemory;
|
||||||
private readonly PageTable<nuint> _pageTable;
|
private readonly PageTable<nuint> _pageTable;
|
||||||
|
private readonly MemoryTracking _tracking;
|
||||||
|
private bool _writeTracked;
|
||||||
|
|
||||||
protected override ulong AddressSpaceSize { get; }
|
protected override ulong AddressSpaceSize { get; }
|
||||||
|
|
||||||
@@ -44,6 +47,7 @@ namespace Ryujinx.Memory
|
|||||||
AddressSpaceSize = asSize;
|
AddressSpaceSize = asSize;
|
||||||
_backingMemory = backingMemory;
|
_backingMemory = backingMemory;
|
||||||
_pageTable = new PageTable<nuint>();
|
_pageTable = new PageTable<nuint>();
|
||||||
|
_tracking = new MemoryTracking(this, 0x1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -227,7 +231,7 @@ namespace Ryujinx.Memory
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest = false)
|
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest = false)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_writeTracked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected unsafe override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
protected unsafe override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||||
@@ -240,5 +244,29 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||||
=> GetHostAddress(va);
|
=> GetHostAddress(va);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||||
|
{
|
||||||
|
if (_writeTracked)
|
||||||
|
{
|
||||||
|
_tracking.VirtualMemoryEvent(va, size, write, precise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
|
||||||
|
{
|
||||||
|
return _tracking.BeginTracking(address, size, id, flags);
|
||||||
|
}
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
|
||||||
|
{
|
||||||
|
return _tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
|
||||||
|
}
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
|
||||||
|
{
|
||||||
|
return _tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Ryujinx.Memory.Tracking;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Cpu
|
namespace Ryujinx.Memory
|
||||||
{
|
{
|
||||||
public interface IVirtualMemoryManagerTracked : IVirtualMemoryManager
|
public interface IVirtualMemoryManagerTracked : IVirtualMemoryManager
|
||||||
{
|
{
|
||||||
@@ -938,7 +938,9 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.System.EnableInternetAccess,
|
ConfigurationState.Instance.System.EnableInternetAccess,
|
||||||
ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
|
ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
|
||||||
ConfigurationState.Instance.System.FsGlobalAccessLogMode,
|
ConfigurationState.Instance.System.FsGlobalAccessLogMode,
|
||||||
ConfigurationState.Instance.System.SystemTimeOffset,
|
ConfigurationState.Instance.System.MatchSystemTime
|
||||||
|
? 0
|
||||||
|
: ConfigurationState.Instance.System.SystemTimeOffset,
|
||||||
ConfigurationState.Instance.System.TimeZone,
|
ConfigurationState.Instance.System.TimeZone,
|
||||||
ConfigurationState.Instance.System.MemoryManagerMode,
|
ConfigurationState.Instance.System.MemoryManagerMode,
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices,
|
ConfigurationState.Instance.System.IgnoreMissingServices,
|
||||||
|
|||||||
@@ -584,7 +584,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "UI를 숨긴 상태에서 게임 시작",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -1524,6 +1524,156 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "GameListHeaderDeveloper",
|
"ID": "GameListHeaderDeveloper",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Developed by {0}",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderVersion",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "Έκδοση: {0}",
|
||||||
|
"en_US": "Version: {0}",
|
||||||
|
"es_ES": "Versión: {0}",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "Versione: {0}",
|
||||||
|
"ja_JP": "バージョン: {0}",
|
||||||
|
"ko_KR": "버전: {0}",
|
||||||
|
"no_NO": "Versjon: {0}",
|
||||||
|
"pl_PL": "Wersja: {0}",
|
||||||
|
"pt_BR": "Versão: {0}",
|
||||||
|
"ru_RU": "Версия: {0}",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "เวอร์ชั่น: {0}",
|
||||||
|
"tr_TR": "Sürüm: {0}",
|
||||||
|
"uk_UA": "Версія: {0}",
|
||||||
|
"zh_CN": "版本: {0}",
|
||||||
|
"zh_TW": "版本: {0}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderTimePlayed",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "Spielzeit:",
|
||||||
|
"el_GR": "Χρόνος:",
|
||||||
|
"en_US": "Play Time:",
|
||||||
|
"es_ES": "Tiempo jugado:",
|
||||||
|
"fr_FR": "Temps de jeu:",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "Tempo di gioco:",
|
||||||
|
"ja_JP": "プレイ時間:",
|
||||||
|
"ko_KR": "플레이 타임:",
|
||||||
|
"no_NO": "Spilletid:",
|
||||||
|
"pl_PL": "Czas w grze:",
|
||||||
|
"pt_BR": "Tempo de jogo:",
|
||||||
|
"ru_RU": "Время в игре:",
|
||||||
|
"sv_SE": "Speltid:",
|
||||||
|
"th_TH": "เล่นไปแล้ว:",
|
||||||
|
"tr_TR": "Oynama Süresi:",
|
||||||
|
"uk_UA": "Зіграно часу:",
|
||||||
|
"zh_CN": "游玩时长:",
|
||||||
|
"zh_TW": "遊玩時數:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderLastPlayed",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "Zuletzt gespielt: ",
|
||||||
|
"el_GR": "Παίχτηκε: ",
|
||||||
|
"en_US": "Last Played:",
|
||||||
|
"es_ES": "Jugado por última vez:",
|
||||||
|
"fr_FR": "Dernière partie jouée:",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "Ultima partita:",
|
||||||
|
"ja_JP": "最終プレイ日時:",
|
||||||
|
"ko_KR": "마지막 플레이:",
|
||||||
|
"no_NO": "Sist Spilt:",
|
||||||
|
"pl_PL": "Ostatnio grane:",
|
||||||
|
"pt_BR": "Último jogo:",
|
||||||
|
"ru_RU": "Последний запуск:",
|
||||||
|
"sv_SE": "Senast spelad:",
|
||||||
|
"th_TH": "เล่นล่าสุด:",
|
||||||
|
"tr_TR": "Son Oynama Tarihi:",
|
||||||
|
"uk_UA": "Востаннє зіграно:",
|
||||||
|
"zh_CN": "最近游玩:",
|
||||||
|
"zh_TW": "最近遊玩:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderFileExtension",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "Dateiformat: {0}",
|
||||||
|
"el_GR": "Κατάληξη: {0}",
|
||||||
|
"en_US": "Extension: {0}",
|
||||||
|
"es_ES": "Extensión: {0}",
|
||||||
|
"fr_FR": "Extension du Fichier: {0}",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "Estensione: {0}",
|
||||||
|
"ja_JP": "ファイル拡張子: {0}",
|
||||||
|
"ko_KR": "파일 확장자: {0}",
|
||||||
|
"no_NO": "Fil Eks.: {0}",
|
||||||
|
"pl_PL": "Rozszerzenie pliku: {0}",
|
||||||
|
"pt_BR": "Extensão: {0}",
|
||||||
|
"ru_RU": "Расширение файла: {0}",
|
||||||
|
"sv_SE": "Filänd: {0}",
|
||||||
|
"th_TH": "นามสกุลไฟล์: {0}",
|
||||||
|
"tr_TR": "Dosya Uzantısı: {0}",
|
||||||
|
"uk_UA": "Розширення файлу: {0}",
|
||||||
|
"zh_CN": "扩展名: {0}",
|
||||||
|
"zh_TW": "副檔名: {0}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderFileSize",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "Dateigröße: {0}",
|
||||||
|
"el_GR": "Μέγεθος Αρχείου: {0}",
|
||||||
|
"en_US": "File Size: {0}",
|
||||||
|
"es_ES": "Tamaño del archivo: {0}",
|
||||||
|
"fr_FR": "Taille du Fichier: {0}",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "Dimensione file: {0}",
|
||||||
|
"ja_JP": "ファイルサイズ: {0}",
|
||||||
|
"ko_KR": "파일 크기: {0}",
|
||||||
|
"no_NO": "Fil Størrelse: {0}",
|
||||||
|
"pl_PL": "Rozmiar pliku: {0}",
|
||||||
|
"pt_BR": "Tamanho: {0}",
|
||||||
|
"ru_RU": "Размер файла: {0}",
|
||||||
|
"sv_SE": "Filstorlek: {0}",
|
||||||
|
"th_TH": "ขนาดไฟล์: {0}",
|
||||||
|
"tr_TR": "Dosya Boyutu: {0}",
|
||||||
|
"uk_UA": "Розмір файлу: {0}",
|
||||||
|
"zh_CN": "大小: {0}",
|
||||||
|
"zh_TW": "檔案大小: {0}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListSortDeveloper",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "المطور",
|
"ar_SA": "المطور",
|
||||||
"de_DE": "Entwickler",
|
"de_DE": "Entwickler",
|
||||||
@@ -1548,32 +1698,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "GameListHeaderVersion",
|
"ID": "GameListSortTimePlayed",
|
||||||
"Translations": {
|
|
||||||
"ar_SA": "الإصدار",
|
|
||||||
"de_DE": "",
|
|
||||||
"el_GR": "Έκδοση",
|
|
||||||
"en_US": "Version",
|
|
||||||
"es_ES": "Versión",
|
|
||||||
"fr_FR": "",
|
|
||||||
"he_IL": "גרסה",
|
|
||||||
"it_IT": "Versione",
|
|
||||||
"ja_JP": "バージョン",
|
|
||||||
"ko_KR": "버전",
|
|
||||||
"no_NO": "Versjon",
|
|
||||||
"pl_PL": "Wersja",
|
|
||||||
"pt_BR": "Versão",
|
|
||||||
"ru_RU": "Версия",
|
|
||||||
"sv_SE": "",
|
|
||||||
"th_TH": "เวอร์ชั่น",
|
|
||||||
"tr_TR": "Sürüm",
|
|
||||||
"uk_UA": "Версія",
|
|
||||||
"zh_CN": "版本",
|
|
||||||
"zh_TW": "版本"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ID": "GameListHeaderTimePlayed",
|
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "وقت اللعب",
|
"ar_SA": "وقت اللعب",
|
||||||
"de_DE": "Spielzeit",
|
"de_DE": "Spielzeit",
|
||||||
@@ -1598,7 +1723,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "GameListHeaderLastPlayed",
|
"ID": "GameListSortLastPlayed",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "آخر مرة لُعبت",
|
"ar_SA": "آخر مرة لُعبت",
|
||||||
"de_DE": "Zuletzt gespielt",
|
"de_DE": "Zuletzt gespielt",
|
||||||
@@ -1623,7 +1748,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "GameListHeaderFileExtension",
|
"ID": "GameListSortFileExtension",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "صيغة الملف",
|
"ar_SA": "صيغة الملف",
|
||||||
"de_DE": "Dateiformat",
|
"de_DE": "Dateiformat",
|
||||||
@@ -1648,7 +1773,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "GameListHeaderFileSize",
|
"ID": "GameListSortFileSize",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "حجم الملف",
|
"ar_SA": "حجم الملف",
|
||||||
"de_DE": "Dateigröße",
|
"de_DE": "Dateigröße",
|
||||||
@@ -1673,7 +1798,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "GameListHeaderPath",
|
"ID": "GameListSortPath",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "المسار",
|
"ar_SA": "المسار",
|
||||||
"de_DE": "Pfad",
|
"de_DE": "Pfad",
|
||||||
@@ -1697,6 +1822,106 @@
|
|||||||
"zh_TW": "路徑"
|
"zh_TW": "路徑"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderCompatibilityStatus",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Compatibility:",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderTitleId",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Title ID:",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderHostedGames",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Hosted Games: {0}",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListHeaderPlayerCount",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Online Players: {0}",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ID": "GameListContextMenuOpenUserSaveDirectory",
|
"ID": "GameListContextMenuOpenUserSaveDirectory",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
@@ -2034,7 +2259,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "PPTC 캐시 제거",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -2059,7 +2284,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "앱의 모든 PPTC 캐시 파일 삭제",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -2384,7 +2609,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "선택한 DLC 파일에서 RomFS 추출",
|
||||||
"no_NO": "Pakk ut RomFS filene fra valgt DLC fil",
|
"no_NO": "Pakk ut RomFS filene fra valgt DLC fil",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -2522,6 +2747,106 @@
|
|||||||
"zh_TW": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式"
|
"zh_TW": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListContextMenuShowCompatEntry",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Show Compatibility Entry",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "호환성 항목 표시",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListContextMenuShowCompatEntryToolTip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Show the selected game in the Compatibility List you can normally access via the Help menu.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "일반적으로 도움말 메뉴를 통해 접근할 수 있는 호환성 목록에 선택한 게임을 표시합니다.",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListContextMenuShowGameData",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Show Game Info",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "게임 통계 표시",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameListContextMenuShowGameDataToolTip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Show stats & details about the currently selected game.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "그리드 보기 레이아웃에서 누락된 현재 선택된 게임에 대한 다양한 정보를 표시합니다.",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ID": "GameListContextMenuOpenModsDirectory",
|
"ID": "GameListContextMenuOpenModsDirectory",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
@@ -3284,7 +3609,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "Aggiornamenti e DLC che fanno riferimento a file mancanti verranno disabilitati automaticamente",
|
"it_IT": "Aggiornamenti e DLC che fanno riferimento a file mancanti verranno disabilitati automaticamente",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "누락된 파일을 참조하는 DLC 및 업데이트가 자동으로 언로드",
|
"ko_KR": "누락된 파일을 참조하는 DLC 및 업데이트가 자동으로 불러오기 취소",
|
||||||
"no_NO": "DLC og oppdateringer som henviser til manglende filer, vil bli lastet ned automatisk",
|
"no_NO": "DLC og oppdateringer som henviser til manglende filer, vil bli lastet ned automatisk",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "DLCs e Atualizações que se referem a arquivos ausentes serão descarregadas automaticamente",
|
"pt_BR": "DLCs e Atualizações que se referem a arquivos ausentes serão descarregadas automaticamente",
|
||||||
@@ -4059,7 +4384,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "스웨덴어",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -4084,7 +4409,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "노르웨이어",
|
||||||
"no_NO": "Norsk",
|
"no_NO": "Norsk",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -4153,23 +4478,23 @@
|
|||||||
"ar_SA": "",
|
"ar_SA": "",
|
||||||
"de_DE": "",
|
"de_DE": "",
|
||||||
"el_GR": "",
|
"el_GR": "",
|
||||||
"en_US": "Resync to PC Date & Time",
|
"en_US": "Match System Time",
|
||||||
"es_ES": "",
|
"es_ES": "",
|
||||||
"fr_FR": "Resynchronier la Date à celle du PC",
|
"fr_FR": "",
|
||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "Sincronizza data e ora con il PC",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "PC 날짜와 시간에 동기화",
|
"ko_KR": "매치 시스템 시간",
|
||||||
"no_NO": "Resynkroniser til PC-dato og -klokkeslett",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
"ru_RU": "Повторная синхронизация с датой и временем на компьютере",
|
"ru_RU": "",
|
||||||
"sv_SE": "Återsynka till datorns datum och tid",
|
"sv_SE": "",
|
||||||
"th_TH": "",
|
"th_TH": "",
|
||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "Синхронізувати з датою та часом ПК",
|
"uk_UA": "",
|
||||||
"zh_CN": "与 PC 日期和时间重新同步",
|
"zh_CN": "",
|
||||||
"zh_TW": "重新同步至 PC 的日期和時間"
|
"zh_TW": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5797,6 +6122,56 @@
|
|||||||
"zh_TW": "關閉"
|
"zh_TW": "關閉"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "SettingsButtonReset",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Reset Settings",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "SettingsButtonResetConfirm",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "I want to reset my settings.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ID": "SettingsButtonOk",
|
"ID": "SettingsButtonOk",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
@@ -7709,7 +8084,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "비활성화",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -7734,7 +8109,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "레인보우",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -7759,7 +8134,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "레인보우 속도",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -7784,7 +8159,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "색상",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -13034,7 +13409,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "다음에서 모든 PPTC 데이터를 제거하려고 합니다:\n\n{0}\n\n계속하시겠습니까?",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -15553,23 +15928,23 @@
|
|||||||
"ar_SA": "",
|
"ar_SA": "",
|
||||||
"de_DE": "",
|
"de_DE": "",
|
||||||
"el_GR": "",
|
"el_GR": "",
|
||||||
"en_US": "Resync System Time to match your PC's current date & time.\n\nThis is not an active setting, it can still fall out of sync; in which case just click this button again.",
|
"en_US": "Sync System Time to match your PC's current date & time.",
|
||||||
"es_ES": "",
|
"es_ES": "",
|
||||||
"fr_FR": "Resynchronise la Date du Système pour qu'elle soit la même que celle du PC.\n\nCeci n'est pas un paramètrage automatique, la date peut se désynchroniser; dans ce cas là, rappuyer sur le boutton.",
|
"fr_FR": "Resynchronise la Date du Système pour qu'elle soit la même que celle du PC.",
|
||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "Sincronizza data e ora del sistema con quelle del PC.\n\nQuesta non è un'opzione attiva, perciò data e ora potrebbero tornare a non essere sincronizzate: in tal caso basterà cliccare nuovamente questo pulsante.",
|
"it_IT": "Sincronizza data e ora del sistema con quelle del PC.",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "시스템 시간을 PC의 현재 날짜 및 시간과 일치하도록 다시 동기화합니다.\n\n이 설정은 활성 설정이 아니므로 여전히 동기화되지 않을 수 있으며, 이 경우 이 버튼을 다시 클릭하면 됩니다.",
|
"ko_KR": "시스템 시간을 PC의 현재 날짜 및 시간과 일치하도록 다시 동기화합니다.",
|
||||||
"no_NO": "Resynkroniser systemtiden slik at den samsvarer med PC-ens gjeldende dato og klokkeslett. \\Dette er ikke en aktiv innstilling, men den kan likevel komme ut av synkronisering; i så fall er det bare å klikke på denne knappen igjen.",
|
"no_NO": "Resynkroniser systemtiden slik at den samsvarer med PC-ens gjeldende dato og klokkeslett.",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
"ru_RU": "Повторно синхронизирует системное время, чтобы оно соответствовало текущей дате и времени вашего компьютера.\n\nЭто не активная настройка, она все еще может рассинхронизироваться; в этом случае просто нажмите эту кнопку еще раз.",
|
"ru_RU": "Повторно синхронизирует системное время, чтобы оно соответствовало текущей дате и времени вашего компьютера.",
|
||||||
"sv_SE": "Återsynkronisera systemtiden för att matcha din dators aktuella datum och tid.\n\nDetta är inte en aktiv inställning och den kan tappa synken och om det händer så kan du klicka på denna knapp igen.",
|
"sv_SE": "Återsynkronisera systemtiden för att matcha din dators aktuella datum och tid.",
|
||||||
"th_TH": "",
|
"th_TH": "",
|
||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "Синхронізувати системний час, щоб він відповідав поточній даті та часу вашого ПК.\n\nЦе не активне налаштування, тому синхронізація може збитися; у такому разі просто натискайте цю кнопку знову.",
|
"uk_UA": "Синхронізувати системний час, щоб він відповідав поточній даті та часу вашого ПК.",
|
||||||
"zh_CN": "重新同步系统时间以匹配您电脑的当前日期和时间。\n\n这个操作不会实时同步系统时间与电脑时间,时间仍然可能不同步;在这种情况下,只需再次单击此按钮即可。",
|
"zh_CN": "重新同步系统时间以匹配您电脑的当前日期和时间。",
|
||||||
"zh_TW": "重新同步系統韌體時間至 PC 目前的日期和時間。\n\n這不是一個主動設定,它仍然可能會失去同步;在這種情況下,只需再次點擊此按鈕。"
|
"zh_TW": "重新同步系統韌體時間至 PC 目前的日期和時間。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -19034,7 +19409,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "LED 설정",
|
||||||
"no_NO": "",
|
"no_NO": "",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -21959,7 +22334,7 @@
|
|||||||
"he_IL": "ממשק רשת",
|
"he_IL": "ממשק רשת",
|
||||||
"it_IT": "Interfaccia di rete:",
|
"it_IT": "Interfaccia di rete:",
|
||||||
"ja_JP": "ネットワークインタフェース:",
|
"ja_JP": "ネットワークインタフェース:",
|
||||||
"ko_KR": "네트워크 인터페이스:",
|
"ko_KR": "네트워크 인터페이스 :",
|
||||||
"no_NO": "Nettverksgrensesnitt",
|
"no_NO": "Nettverksgrensesnitt",
|
||||||
"pl_PL": "Interfejs sieci:",
|
"pl_PL": "Interfejs sieci:",
|
||||||
"pt_BR": "Interface de rede:",
|
"pt_BR": "Interface de rede:",
|
||||||
@@ -22934,7 +23309,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "최종 업데이트 : {0}",
|
||||||
"no_NO": "Sist oppdatert: {0}",
|
"no_NO": "Sist oppdatert: {0}",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -23069,7 +23444,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "可游玩",
|
"zh_CN": "可游玩",
|
||||||
"zh_TW": "可暢順遊玩 (Playable)"
|
"zh_TW": "可暢順遊玩"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23094,7 +23469,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "进入游戏",
|
"zh_CN": "进入游戏",
|
||||||
"zh_TW": "大致可遊玩 (Ingame)"
|
"zh_TW": "大致可遊玩"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23119,7 +23494,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "菜单",
|
"zh_CN": "菜单",
|
||||||
"zh_TW": "只開啟至遊戲開始功能表 (Menus)"
|
"zh_TW": "只開啟至遊戲開始功能表"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23144,7 +23519,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "启动",
|
"zh_CN": "启动",
|
||||||
"zh_TW": "只能啟動 (Boots)"
|
"zh_TW": "只能啟動"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23169,7 +23544,132 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "什么都没有",
|
"zh_CN": "什么都没有",
|
||||||
"zh_TW": "無法啟動 (Nothing)"
|
"zh_TW": "無法啟動"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "CompatibilityListPlayableTooltip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Boots and plays without any crashes or GPU bugs of any kind, and at a speed fast enough to reasonably enjoy on an average PC.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "CompatibilityListIngameTooltip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Boots and goes in-game but suffers from one or more of the following: crashes, deadlocks, GPU bugs, distractingly bad audio, or is simply too slow. Game still might able to be played all the way through, but not as the game is intended to play.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "CompatibilityListMenusTooltip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Boots and goes past the title screen but does not make it into main gameplay.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "CompatibilityListBootsTooltip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Boots but does not make it past the title screen.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "CompatibilityListNothingTooltip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Does not boot or shows no signs of activity.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23184,7 +23684,7 @@
|
|||||||
"he_IL": "",
|
"he_IL": "",
|
||||||
"it_IT": "",
|
"it_IT": "",
|
||||||
"ja_JP": "",
|
"ja_JP": "",
|
||||||
"ko_KR": "",
|
"ko_KR": "추출할 DLC 선택",
|
||||||
"no_NO": "Velg en DLC og hente ut",
|
"no_NO": "Velg en DLC og hente ut",
|
||||||
"pl_PL": "",
|
"pl_PL": "",
|
||||||
"pt_BR": "",
|
"pt_BR": "",
|
||||||
@@ -23196,6 +23696,56 @@
|
|||||||
"zh_CN": "选择一个要解压的 DLC",
|
"zh_CN": "选择一个要解压的 DLC",
|
||||||
"zh_TW": ""
|
"zh_TW": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameInfoRpcImage",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Rich Presence Image",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "GameInfoRpcDynamic",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Dynamic Rich Presence",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"sv_SE": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -4,15 +4,13 @@ using MsgPack;
|
|||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
using Ryujinx.Ava.Utilities.Configuration;
|
using Ryujinx.Ava.Utilities.Configuration;
|
||||||
|
using Ryujinx.Ava.Utilities.PlayReport;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Helper;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE;
|
using Ryujinx.HLE;
|
||||||
using Ryujinx.HLE.Loaders.Processes;
|
using Ryujinx.HLE.Loaders.Processes;
|
||||||
using Ryujinx.Horizon;
|
using Ryujinx.Horizon;
|
||||||
using System;
|
using Ryujinx.Horizon.Prepo.Types;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -41,6 +39,9 @@ namespace Ryujinx.Ava
|
|||||||
private static RichPresence _discordPresencePlaying;
|
private static RichPresence _discordPresencePlaying;
|
||||||
private static ApplicationMetadata _currentApp;
|
private static ApplicationMetadata _currentApp;
|
||||||
|
|
||||||
|
public static bool HasAssetImage(string titleId) => TitleIDs.DiscordGameAssetKeys.ContainsIgnoreCase(titleId);
|
||||||
|
public static bool HasAnalyzer(string titleId) => PlayReports.Analyzer.TitleIds.ContainsIgnoreCase(titleId);
|
||||||
|
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
_discordPresenceMain = new RichPresence
|
_discordPresenceMain = new RichPresence
|
||||||
@@ -56,7 +57,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
||||||
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
||||||
HorizonStatic.PlayReportPrinted += HandlePlayReport;
|
HorizonStatic.PlayReport += HandlePlayReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
||||||
@@ -117,11 +118,6 @@ namespace Ryujinx.Ava
|
|||||||
_currentApp = appMeta;
|
_currentApp = appMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdatePlayingState()
|
|
||||||
{
|
|
||||||
_discordClient?.SetPresence(_discordPresencePlaying);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SwitchToMainState()
|
private static void SwitchToMainState()
|
||||||
{
|
{
|
||||||
_discordClient?.SetPresence(_discordPresenceMain);
|
_discordClient?.SetPresence(_discordPresenceMain);
|
||||||
@@ -129,27 +125,28 @@ namespace Ryujinx.Ava
|
|||||||
_currentApp = null;
|
_currentApp = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandlePlayReport(MessagePackObject playReport)
|
private static void HandlePlayReport(PlayReport playReport)
|
||||||
{
|
{
|
||||||
if (_discordClient is null) return;
|
if (_discordClient is null) return;
|
||||||
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
|
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
|
||||||
if (_discordPresencePlaying is null) return;
|
if (_discordPresencePlaying is null) return;
|
||||||
|
|
||||||
PlayReportFormattedValue value = PlayReport.Analyzer.Run(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
|
FormattedValue formattedValue =
|
||||||
|
PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
|
||||||
|
|
||||||
if (!value.Handled) return;
|
if (!formattedValue.Handled) return;
|
||||||
|
|
||||||
if (value.Reset)
|
_discordPresencePlaying.Details = TruncateToByteLength(
|
||||||
{
|
formattedValue.Reset
|
||||||
_discordPresencePlaying.Details = $"Playing {_currentApp.Title}";
|
? $"Playing {_currentApp.Title}"
|
||||||
Logger.Info?.Print(LogClass.UI, "Reset Discord RPC based on a supported play report value formatter.");
|
: formattedValue.FormattedString
|
||||||
}
|
);
|
||||||
else
|
|
||||||
{
|
if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details))
|
||||||
_discordPresencePlaying.Details = value.FormattedString;
|
return; //don't trigger an update if the set presence Details are identical to current
|
||||||
Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report.");
|
|
||||||
}
|
_discordClient.SetPresence(_discordPresencePlaying);
|
||||||
UpdatePlayingState();
|
Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string TruncateToByteLength(string input)
|
private static string TruncateToByteLength(string input)
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ namespace Ryujinx.Ava
|
|||||||
public static MainWindow MainWindow => Current!
|
public static MainWindow MainWindow => Current!
|
||||||
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>()
|
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>()
|
||||||
.MainWindow.Cast<MainWindow>();
|
.MainWindow.Cast<MainWindow>();
|
||||||
|
|
||||||
|
public static IClassicDesktopStyleApplicationLifetime AppLifetime => Current!
|
||||||
|
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>();
|
||||||
|
|
||||||
public static bool IsClipboardAvailable(out IClipboard clipboard)
|
public static bool IsClipboardAvailable(out IClipboard clipboard)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,17 @@
|
|||||||
Header="{ext:Locale GameListContextMenuCreateShortcut}"
|
Header="{ext:Locale GameListContextMenuCreateShortcut}"
|
||||||
Icon="{ext:Icon fa-solid fa-bookmark}"
|
Icon="{ext:Icon fa-solid fa-bookmark}"
|
||||||
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
|
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
|
||||||
|
<MenuItem
|
||||||
|
IsVisible="{Binding HasCompatibilityEntry}"
|
||||||
|
Click="OpenApplicationCompatibility_Click"
|
||||||
|
Header="{ext:Locale GameListContextMenuShowCompatEntry}"
|
||||||
|
Icon="{ext:Icon mdi-gamepad}"
|
||||||
|
ToolTip.Tip="{ext:Locale GameListContextMenuShowCompatEntryToolTip}"/>
|
||||||
|
<MenuItem
|
||||||
|
Click="OpenApplicationData_Click"
|
||||||
|
Header="{ext:Locale GameListContextMenuShowGameData}"
|
||||||
|
Icon="{ext:Icon mdi-chart-line}"
|
||||||
|
ToolTip.Tip="{ext:Locale GameListContextMenuShowGameDataToolTip}"/>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Click="OpenUserSaveDirectory_Click"
|
Click="OpenUserSaveDirectory_Click"
|
||||||
@@ -74,7 +85,6 @@
|
|||||||
Header="{ext:Locale GameListContextMenuTrimXCI}"
|
Header="{ext:Locale GameListContextMenuTrimXCI}"
|
||||||
IsEnabled="{Binding TrimXCIEnabled}"
|
IsEnabled="{Binding TrimXCIEnabled}"
|
||||||
ToolTip.Tip="{ext:Locale GameListContextMenuTrimXCIToolTip}" />
|
ToolTip.Tip="{ext:Locale GameListContextMenuTrimXCIToolTip}" />
|
||||||
<Separator />
|
|
||||||
<MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon mdi-cached}">
|
<MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon mdi-cached}">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Click="PurgePtcCache_Click"
|
Click="PurgePtcCache_Click"
|
||||||
@@ -112,6 +122,7 @@
|
|||||||
Header="{ext:Locale GameListContextMenuExtractDataRomFS}"
|
Header="{ext:Locale GameListContextMenuExtractDataRomFS}"
|
||||||
ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataRomFSToolTip}" />
|
ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataRomFSToolTip}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
IsVisible="{Binding HasDlc}"
|
||||||
Click="ExtractAocRomFs_Click"
|
Click="ExtractAocRomFs_Click"
|
||||||
Header="{ext:Locale GameListContextMenuExtractDataAocRomFS}"
|
Header="{ext:Locale GameListContextMenuExtractDataAocRomFS}"
|
||||||
ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataAocRomFSToolTip}" />
|
ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataAocRomFSToolTip}" />
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Ryujinx.Ava.UI.ViewModels;
|
|||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using Ryujinx.Ava.Utilities.Compat;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Helper;
|
using Ryujinx.Common.Helper;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
@@ -333,7 +334,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DownloadableContentModel selectedDlc = await DlcSelectView.Show(viewModel.SelectedApplication.IdBase, viewModel.ApplicationLibrary);
|
DownloadableContentModel selectedDlc = await DlcSelectView.Show(viewModel.SelectedApplication.Id, viewModel.ApplicationLibrary);
|
||||||
|
|
||||||
if (selectedDlc is not null)
|
if (selectedDlc is not null)
|
||||||
{
|
{
|
||||||
@@ -385,6 +386,18 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
viewModel.SelectedApplication.Icon
|
viewModel.SelectedApplication.Icon
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||||
|
await CompatibilityList.Show(viewModel.SelectedApplication.IdString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||||
|
await ApplicationDataView.Show(viewModel.SelectedApplication);
|
||||||
|
}
|
||||||
|
|
||||||
public async void RunApplication_Click(object sender, RoutedEventArgs args)
|
public async void RunApplication_Click(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
@@ -394,12 +407,8 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
public async void TrimXCI_Click(object sender, RoutedEventArgs args)
|
public async void TrimXCI_Click(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
MainWindowViewModel viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
|
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
|
||||||
{
|
|
||||||
await viewModel.TrimXCIFile(viewModel.SelectedApplication.Path);
|
await viewModel.TrimXCIFile(viewModel.SelectedApplication.Path);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
171
src/Ryujinx/UI/Controls/ApplicationDataView.axaml
Normal file
171
src/Ryujinx/UI/Controls/ApplicationDataView.axaml
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
|
xmlns:ext="using:Ryujinx.Ava.Common.Markup"
|
||||||
|
xmlns:viewModels="using:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Ryujinx.Ava.UI.Controls.ApplicationDataView"
|
||||||
|
x:DataType="viewModels:ApplicationDataViewModel">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Image Margin="0"
|
||||||
|
MaxWidth="256"
|
||||||
|
MinWidth="256"
|
||||||
|
Source="{Binding AppData.Icon, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
|
||||||
|
<Border Margin="5, 0" Width="1" Height="256" BorderBrush="Gray" Background="Gray" />
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<Grid
|
||||||
|
RowDefinitions="Auto,Auto,Auto"
|
||||||
|
ColumnDefinitions="*">
|
||||||
|
<StackPanel Grid.Row="0">
|
||||||
|
<TextBlock HorizontalAlignment="Left"
|
||||||
|
Text="{Binding FormattedVersion}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock HorizontalAlignment="Left"
|
||||||
|
Text="{Binding FormattedDeveloper}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding FormattedFileExtension}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding FormattedFileSize}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
<Separator Grid.Row="1" Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="5">
|
||||||
|
<StackPanel Orientation="Horizontal" IsVisible="{Binding AppData.HasPlayabilityInfo}">
|
||||||
|
<TextBlock Padding="0, 0, 5, 0" Text="{ext:Locale GameListHeaderCompatibilityStatus}" />
|
||||||
|
<Button
|
||||||
|
Click="PlayabilityStatus_OnClick"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{DynamicResource AppListBackgroundColor}"
|
||||||
|
Padding="0">
|
||||||
|
<TextBlock
|
||||||
|
Margin="1.5"
|
||||||
|
Tag="{Binding AppData.IdString}"
|
||||||
|
Text="{Binding AppData.LocalizedStatus}"
|
||||||
|
Foreground="{Binding AppData.PlayabilityStatus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
ToolTip.Tip="{Binding AppData.LocalizedStatusTooltip}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<Button.Styles>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="MinWidth"
|
||||||
|
Value="0" />
|
||||||
|
<!-- avoids very wide buttons from the overall project avalonia style -->
|
||||||
|
</Style>
|
||||||
|
</Button.Styles>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Padding="0, 0, 5, 0" Text="{ext:Locale GameListHeaderTitleId}" />
|
||||||
|
<Button
|
||||||
|
Click="IdString_OnClick"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{DynamicResource AppListBackgroundColor}"
|
||||||
|
Padding="0">
|
||||||
|
<TextBlock
|
||||||
|
Margin="1.5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding AppData.IdString}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<Separator Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
|
||||||
|
<StackPanel Orientation="Vertical" Spacing="5">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
|
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasRichPresenceAsset}"/>
|
||||||
|
<TextBlock
|
||||||
|
Foreground="ForestGreen"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsVisible="{Binding AppData.HasRichPresenceAsset}"
|
||||||
|
Text="{ext:Locale GameInfoRpcImage}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" >
|
||||||
|
</TextBlock>
|
||||||
|
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasRichPresenceAsset}"/>
|
||||||
|
<TextBlock
|
||||||
|
Foreground="Red"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsVisible="{Binding !AppData.HasRichPresenceAsset}"
|
||||||
|
Text="{ext:Locale GameInfoRpcImage}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" >
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
|
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
|
||||||
|
<TextBlock
|
||||||
|
Foreground="ForestGreen"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
|
||||||
|
Text="{ext:Locale GameInfoRpcDynamic}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" >
|
||||||
|
</TextBlock>
|
||||||
|
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
|
||||||
|
<TextBlock
|
||||||
|
Foreground="Red"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"
|
||||||
|
Text="{ext:Locale GameInfoRpcDynamic}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" >
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<Separator Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsVisible="{Binding AppData.HasLdnGames}"
|
||||||
|
Text="{Binding FormattedLdnInfo}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<Separator IsVisible="{Binding AppData.HasLdnGames}" Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
|
||||||
|
<StackPanel Orientation="Vertical" Spacing="5">
|
||||||
|
<Grid
|
||||||
|
ColumnDefinitions="Auto,*,Auto">
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Text="{ext:Locale GameListHeaderLastPlayed}"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="NoWrap" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="2"
|
||||||
|
Text="{Binding AppData.LastPlayedString}"
|
||||||
|
TextAlignment="End"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
ColumnDefinitions="Auto,*,Auto"
|
||||||
|
IsVisible="{Binding AppData.HasPlayedPreviously}">
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Text="{ext:Locale GameListHeaderTimePlayed}"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="NoWrap" />
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
Text="{Binding AppData.TimePlayedString}"
|
||||||
|
TextAlignment="End"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
86
src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs
Normal file
86
src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input.Platform;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Ryujinx.Ava;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Controls;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.Ava.UI.Windows;
|
||||||
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using Ryujinx.Ava.Utilities.Compat;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
|
{
|
||||||
|
public partial class ApplicationDataView : UserControl
|
||||||
|
{
|
||||||
|
public static async Task Show(ApplicationData appData)
|
||||||
|
{
|
||||||
|
ContentDialog contentDialog = new()
|
||||||
|
{
|
||||||
|
Title = appData.Name,
|
||||||
|
PrimaryButtonText = string.Empty,
|
||||||
|
SecondaryButtonText = string.Empty,
|
||||||
|
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
||||||
|
MinWidth = 256,
|
||||||
|
Content = new ApplicationDataView { DataContext = new ApplicationDataViewModel(appData) }
|
||||||
|
};
|
||||||
|
|
||||||
|
Style closeButton = new(x => x.Name("CloseButton"));
|
||||||
|
closeButton.Setters.Add(new Setter(WidthProperty, 160d));
|
||||||
|
|
||||||
|
Style closeButtonParent = new(x => x.Name("CommandSpace"));
|
||||||
|
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty,
|
||||||
|
Avalonia.Layout.HorizontalAlignment.Center));
|
||||||
|
|
||||||
|
contentDialog.Styles.Add(closeButton);
|
||||||
|
contentDialog.Styles.Add(closeButtonParent);
|
||||||
|
|
||||||
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplicationDataView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void PlayabilityStatus_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not Button { Content: TextBlock playabilityLabel })
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (RyujinxApp.AppLifetime.Windows.TryGetFirst(x => x is ContentDialogOverlayWindow, out Window window))
|
||||||
|
window.Close(ContentDialogResult.None);
|
||||||
|
|
||||||
|
await CompatibilityList.Show((string)playabilityLabel.Tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void IdString_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is not MainWindowViewModel mwvm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sender is not Button { Content: TextBlock idText })
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!RyujinxApp.IsClipboardAvailable(out IClipboard clipboard))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ApplicationData appData = mwvm.Applications.FirstOrDefault(it => it.IdString == idText.Text);
|
||||||
|
if (appData is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await clipboard.SetTextAsync(appData.IdString);
|
||||||
|
|
||||||
|
NotificationHelper.ShowInformation(
|
||||||
|
"Copied Title ID",
|
||||||
|
$"{appData.Name} ({appData.IdString})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -86,6 +86,30 @@
|
|||||||
Text="{Binding Version}"
|
Text="{Binding Version}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
|
<Button
|
||||||
|
Click="PlayabilityStatus_OnClick"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding HasPlayabilityInfo}"
|
||||||
|
Background="{DynamicResource AppListBackgroundColor}"
|
||||||
|
Margin="-1, 0, 0, 0"
|
||||||
|
Padding="0"
|
||||||
|
ToolTip.Tip="{Binding LocalizedStatusTooltip}">
|
||||||
|
<TextBlock
|
||||||
|
Margin="1.5"
|
||||||
|
Tag="{Binding IdString}"
|
||||||
|
Text="{Binding LocalizedStatus}"
|
||||||
|
Foreground="{Binding PlayabilityStatus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<Button.Styles>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="MinWidth"
|
||||||
|
Value="0" />
|
||||||
|
<!-- avoids very wide buttons from the overall project avalonia style -->
|
||||||
|
</Style>
|
||||||
|
</Button.Styles>
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
@@ -117,7 +141,8 @@
|
|||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding Converter={helpers:MultiplayerInfoConverter}}"
|
IsVisible="{Binding HasLdnGames}"
|
||||||
|
Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap"/>
|
TextWrapping="Wrap"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ using Avalonia.Interactivity;
|
|||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using Ryujinx.Ava.Utilities.Compat;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
@@ -28,6 +30,17 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
if (sender is ListBox { SelectedItem: ApplicationData selected })
|
if (sender is ListBox { SelectedItem: ApplicationData selected })
|
||||||
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
|
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void PlayabilityStatus_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is not MainWindowViewModel mwvm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sender is not Button { Content: TextBlock playabilityLabel })
|
||||||
|
return;
|
||||||
|
|
||||||
|
await CompatibilityList.Show((string)playabilityLabel.Tag);
|
||||||
|
}
|
||||||
|
|
||||||
private async void IdString_OnClick(object sender, RoutedEventArgs e)
|
private async void IdString_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Gommon;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
{
|
{
|
||||||
internal class MultiplayerInfoConverter : MarkupExtension, IValueConverter
|
internal class MultiplayerInfoConverter : MarkupExtension, IValueConverter
|
||||||
{
|
{
|
||||||
private static readonly MultiplayerInfoConverter _instance = new();
|
public static readonly MultiplayerInfoConverter Instance = new();
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (value is ApplicationData applicationData)
|
if (value is not ApplicationData { HasLdnGames: true } applicationData)
|
||||||
{
|
return "";
|
||||||
if (applicationData.PlayerCount != 0 && applicationData.GameCount != 0)
|
|
||||||
{
|
|
||||||
return $"Hosted Games: {applicationData.GameCount}\nOnline Players: {applicationData.PlayerCount}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
|
|
||||||
|
return new StringBuilder()
|
||||||
|
.AppendLine(
|
||||||
|
LocaleManager.Instance[LocaleKeys.GameListHeaderHostedGames]
|
||||||
|
.Format(applicationData.GameCount))
|
||||||
|
.Append(
|
||||||
|
LocaleManager.Instance[LocaleKeys.GameListHeaderPlayerCount]
|
||||||
|
.Format(applicationData.PlayerCount))
|
||||||
|
.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
@@ -31,7 +35,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
return _instance;
|
return Instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
LocaleKeys.CompatibilityListNothing or
|
LocaleKeys.CompatibilityListNothing or
|
||||||
LocaleKeys.CompatibilityListBoots or
|
LocaleKeys.CompatibilityListBoots or
|
||||||
LocaleKeys.CompatibilityListMenus => Brushes.Red,
|
LocaleKeys.CompatibilityListMenus => Brushes.Red,
|
||||||
LocaleKeys.CompatibilityListIngame => Brushes.Yellow,
|
LocaleKeys.CompatibilityListIngame => Brushes.DarkOrange,
|
||||||
_ => Brushes.ForestGreen
|
_ => Brushes.ForestGreen
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
23
src/Ryujinx/UI/ViewModels/ApplicationDataViewModel.cs
Normal file
23
src/Ryujinx/UI/ViewModels/ApplicationDataViewModel.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Gommon;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
{
|
||||||
|
public class ApplicationDataViewModel : BaseModel
|
||||||
|
{
|
||||||
|
public ApplicationData AppData { get; }
|
||||||
|
|
||||||
|
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData;
|
||||||
|
|
||||||
|
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
|
||||||
|
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
|
||||||
|
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);
|
||||||
|
public string FormattedFileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize].Format(AppData.FileSizeString);
|
||||||
|
|
||||||
|
public string FormattedLdnInfo =>
|
||||||
|
$"{LocaleManager.Instance[LocaleKeys.GameListHeaderHostedGames].Format(AppData.GameCount)}" +
|
||||||
|
$"\n" +
|
||||||
|
$"{LocaleManager.Instance[LocaleKeys.GameListHeaderPlayerCount].Format(AppData.PlayerCount)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,9 +14,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public DlcSelectViewModel(ulong titleId, ApplicationLibrary appLibrary)
|
public DlcSelectViewModel(ulong titleId, ApplicationLibrary appLibrary)
|
||||||
{
|
{
|
||||||
_dlcs = appLibrary.DownloadableContents.Items
|
_dlcs = appLibrary.FindDlcsFor(titleId)
|
||||||
.Where(x => x.Dlc.TitleIdBase == titleId)
|
|
||||||
.Select(x => x.Dlc)
|
|
||||||
.OrderBy(it => it.IsBundled ? 0 : 1)
|
.OrderBy(it => it.IsBundled ? 0 : 1)
|
||||||
.ThenBy(it => it.TitleId)
|
.ThenBy(it => it.TitleId)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|||||||
@@ -69,8 +69,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void LoadDownloadableContents()
|
private void LoadDownloadableContents()
|
||||||
{
|
{
|
||||||
IEnumerable<(DownloadableContentModel Dlc, bool IsEnabled)> dlcs = _applicationLibrary.DownloadableContents.Items
|
(DownloadableContentModel Dlc, bool IsEnabled)[] dlcs = _applicationLibrary.FindDlcConfigurationFor(_applicationData.Id);
|
||||||
.Where(it => it.Dlc.TitleIdBase == _applicationData.IdBase);
|
|
||||||
|
|
||||||
bool hasBundledContent = false;
|
bool hasBundledContent = false;
|
||||||
foreach ((DownloadableContentModel dlc, bool isEnabled) in dlcs)
|
foreach ((DownloadableContentModel dlc, bool isEnabled) in dlcs)
|
||||||
|
|||||||
@@ -349,6 +349,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo;
|
||||||
|
|
||||||
|
public bool HasDlc => ApplicationLibrary.HasDlcs(SelectedApplication.Id);
|
||||||
|
|
||||||
public bool OpenUserSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
public bool OpenUserSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||||
|
|
||||||
public bool OpenDeviceSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
|
public bool OpenDeviceSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||||
@@ -629,15 +633,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
return SortMode switch
|
return SortMode switch
|
||||||
{
|
{
|
||||||
ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.GameListHeaderApplication],
|
|
||||||
ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper],
|
|
||||||
ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderLastPlayed],
|
|
||||||
ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderTimePlayed],
|
|
||||||
ApplicationSort.FileType => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension],
|
|
||||||
ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize],
|
|
||||||
ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListHeaderPath],
|
|
||||||
ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite],
|
ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite],
|
||||||
ApplicationSort.TitleId => LocaleManager.Instance[LocaleKeys.DlcManagerTableHeadingTitleIdLabel],
|
ApplicationSort.TitleId => LocaleManager.Instance[LocaleKeys.DlcManagerTableHeadingTitleIdLabel],
|
||||||
|
ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.GameListHeaderApplication],
|
||||||
|
ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListSortDeveloper],
|
||||||
|
ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListSortLastPlayed],
|
||||||
|
ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListSortTimePlayed],
|
||||||
|
ApplicationSort.FileType => LocaleManager.Instance[LocaleKeys.GameListSortFileExtension],
|
||||||
|
ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListSortFileSize],
|
||||||
|
ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListSortPath],
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Avalonia.Collections;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Gommon;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Audio.Backends.OpenAL;
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
using Ryujinx.Audio.Backends.SDL2;
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
@@ -28,8 +28,6 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||||
|
|
||||||
@@ -116,10 +114,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
||||||
|
|
||||||
public bool IsAppleSiliconMac => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
|
||||||
|
|
||||||
public bool IsMacOS => OperatingSystem.IsMacOS();
|
|
||||||
|
|
||||||
public bool EnableDiscordIntegration { get; set; }
|
public bool EnableDiscordIntegration { get; set; }
|
||||||
public bool CheckUpdatesOnStart { get; set; }
|
public bool CheckUpdatesOnStart { get; set; }
|
||||||
public bool ShowConfirmExit { get; set; }
|
public bool ShowConfirmExit { get; set; }
|
||||||
@@ -201,7 +195,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool EnableTextureRecompression { get; set; }
|
public bool EnableTextureRecompression { get; set; }
|
||||||
public bool EnableMacroHLE { get; set; }
|
public bool EnableMacroHLE { get; set; }
|
||||||
public bool EnableColorSpacePassthrough { get; set; }
|
public bool EnableColorSpacePassthrough { get; set; }
|
||||||
public bool ColorSpacePassthroughAvailable => IsMacOS;
|
public bool ColorSpacePassthroughAvailable => RunningPlatform.IsMacOS;
|
||||||
public bool EnableFileLog { get; set; }
|
public bool EnableFileLog { get; set; }
|
||||||
public bool EnableStub { get; set; }
|
public bool EnableStub { get; set; }
|
||||||
public bool EnableInfo { get; set; }
|
public bool EnableInfo { get; set; }
|
||||||
@@ -297,6 +291,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _matchSystemTime;
|
||||||
|
|
||||||
public DateTimeOffset CurrentDate { get; set; }
|
public DateTimeOffset CurrentDate { get; set; }
|
||||||
|
|
||||||
public TimeSpan CurrentTime { get; set; }
|
public TimeSpan CurrentTime { get; set; }
|
||||||
@@ -412,17 +408,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(PreferredGpuIndex)));
|
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(PreferredGpuIndex)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MatchSystemTime()
|
|
||||||
{
|
|
||||||
(DateTimeOffset dto, TimeSpan timeOfDay) = DateTimeOffset.Now.Extract();
|
|
||||||
|
|
||||||
CurrentDate = dto;
|
|
||||||
CurrentTime = timeOfDay;
|
|
||||||
|
|
||||||
OnPropertyChanged(nameof(CurrentDate));
|
|
||||||
OnPropertyChanged(nameof(CurrentTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LoadTimeZones()
|
public async Task LoadTimeZones()
|
||||||
{
|
{
|
||||||
_timeZoneContentManager = new TimeZoneContentManager();
|
_timeZoneContentManager = new TimeZoneContentManager();
|
||||||
@@ -524,7 +509,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
CurrentDate = currentDateTime.Date;
|
CurrentDate = currentDateTime.Date;
|
||||||
CurrentTime = currentDateTime.TimeOfDay;
|
CurrentTime = currentDateTime.TimeOfDay;
|
||||||
|
|
||||||
EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval.Value;
|
MatchSystemTime = config.System.MatchSystemTime;
|
||||||
|
|
||||||
|
EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval;
|
||||||
CustomVSyncInterval = config.Graphics.CustomVSyncInterval;
|
CustomVSyncInterval = config.Graphics.CustomVSyncInterval;
|
||||||
VSyncMode = config.Graphics.VSyncMode;
|
VSyncMode = config.Graphics.VSyncMode;
|
||||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||||
@@ -629,6 +616,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.System.TimeZone.Value = TimeZone;
|
config.System.TimeZone.Value = TimeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.System.MatchSystemTime.Value = MatchSystemTime;
|
||||||
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
|
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||||
config.System.DramSize.Value = DramSize;
|
config.System.DramSize.Value = DramSize;
|
||||||
@@ -732,6 +720,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
CloseWindow?.Invoke();
|
CloseWindow?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _wantsToReset;
|
||||||
|
|
||||||
|
public AsyncRelayCommand ResetButton => Commands.Create(async () =>
|
||||||
|
{
|
||||||
|
if (!WantsToReset) return;
|
||||||
|
|
||||||
|
CloseWindow?.Invoke();
|
||||||
|
ConfigurationState.Instance.LoadDefault();
|
||||||
|
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
RyujinxApp.MainWindow.LoadApplications();
|
||||||
|
|
||||||
|
await ContentDialogHelper.CreateInfoDialog(
|
||||||
|
$"Your {RyujinxApp.FullAppName} configuration has been reset.",
|
||||||
|
"",
|
||||||
|
string.Empty,
|
||||||
|
LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
||||||
|
"Configuration Reset");
|
||||||
|
});
|
||||||
|
|
||||||
public void CancelButton()
|
public void CancelButton()
|
||||||
{
|
{
|
||||||
RevertIfNotSaved();
|
RevertIfNotSaved();
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void LoadUpdates()
|
private void LoadUpdates()
|
||||||
{
|
{
|
||||||
IEnumerable<(TitleUpdateModel TitleUpdate, bool IsSelected)> updates = ApplicationLibrary.TitleUpdates.Items
|
(TitleUpdateModel TitleUpdate, bool IsSelected)[] updates = ApplicationLibrary.FindUpdateConfigurationFor(ApplicationData.Id);
|
||||||
.Where(it => it.TitleUpdate.TitleIdBase == ApplicationData.IdBase);
|
|
||||||
|
|
||||||
bool hasBundledContent = false;
|
bool hasBundledContent = false;
|
||||||
SelectedUpdate = new TitleUpdateViewModelNoUpdate();
|
SelectedUpdate = new TitleUpdateViewModelNoUpdate();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes);
|
UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes);
|
||||||
XciTrimmerMenuItem.Command = Commands.Create(XCITrimmerWindow.Show);
|
XciTrimmerMenuItem.Command = Commands.Create(XCITrimmerWindow.Show);
|
||||||
AboutWindowMenuItem.Command = Commands.Create(AboutWindow.Show);
|
AboutWindowMenuItem.Command = Commands.Create(AboutWindow.Show);
|
||||||
CompatibilityListMenuItem.Command = Commands.Create(CompatibilityList.Show);
|
CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityList.Show());
|
||||||
|
|
||||||
UpdateMenuItem.Command = Commands.Create(async () =>
|
UpdateMenuItem.Command = Commands.Create(async () =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -113,37 +113,37 @@
|
|||||||
Tag="TitleId" />
|
Tag="TitleId" />
|
||||||
<RadioButton
|
<RadioButton
|
||||||
Checked="Sort_Checked"
|
Checked="Sort_Checked"
|
||||||
Content="{ext:Locale GameListHeaderDeveloper}"
|
Content="{ext:Locale GameListSortDeveloper}"
|
||||||
GroupName="Sort"
|
GroupName="Sort"
|
||||||
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
|
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
|
||||||
Tag="Developer" />
|
Tag="Developer" />
|
||||||
<RadioButton
|
<RadioButton
|
||||||
Checked="Sort_Checked"
|
Checked="Sort_Checked"
|
||||||
Content="{ext:Locale GameListHeaderTimePlayed}"
|
Content="{ext:Locale GameListSortTimePlayed}"
|
||||||
GroupName="Sort"
|
GroupName="Sort"
|
||||||
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
|
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
|
||||||
Tag="TotalTimePlayed" />
|
Tag="TotalTimePlayed" />
|
||||||
<RadioButton
|
<RadioButton
|
||||||
Checked="Sort_Checked"
|
Checked="Sort_Checked"
|
||||||
Content="{ext:Locale GameListHeaderLastPlayed}"
|
Content="{ext:Locale GameListSortLastPlayed}"
|
||||||
GroupName="Sort"
|
GroupName="Sort"
|
||||||
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
|
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
|
||||||
Tag="LastPlayed" />
|
Tag="LastPlayed" />
|
||||||
<RadioButton
|
<RadioButton
|
||||||
Checked="Sort_Checked"
|
Checked="Sort_Checked"
|
||||||
Content="{ext:Locale GameListHeaderFileExtension}"
|
Content="{ext:Locale GameListSortFileExtension}"
|
||||||
GroupName="Sort"
|
GroupName="Sort"
|
||||||
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
|
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
|
||||||
Tag="FileType" />
|
Tag="FileType" />
|
||||||
<RadioButton
|
<RadioButton
|
||||||
Checked="Sort_Checked"
|
Checked="Sort_Checked"
|
||||||
Content="{ext:Locale GameListHeaderFileSize}"
|
Content="{ext:Locale GameListSortFileSize}"
|
||||||
GroupName="Sort"
|
GroupName="Sort"
|
||||||
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
|
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
|
||||||
Tag="FileSize" />
|
Tag="FileSize" />
|
||||||
<RadioButton
|
<RadioButton
|
||||||
Checked="Sort_Checked"
|
Checked="Sort_Checked"
|
||||||
Content="{ext:Locale GameListHeaderPath}"
|
Content="{ext:Locale GameListSortPath}"
|
||||||
GroupName="Sort"
|
GroupName="Sort"
|
||||||
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
|
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
|
||||||
Tag="Path" />
|
Tag="Path" />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:DataType="viewModels:SettingsViewModel">
|
x:DataType="viewModels:SettingsViewModel">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<CheckBox IsChecked="{Binding UseHypervisor}"
|
<CheckBox IsChecked="{Binding UseHypervisor}"
|
||||||
IsVisible="{Binding IsAppleSiliconMac}"
|
IsVisible="{x:Static helper:RunningPlatform.IsArmMac}"
|
||||||
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}">
|
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabSystemUseHypervisor}"
|
<TextBlock Text="{ext:Locale SettingsTabSystemUseHypervisor}"
|
||||||
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" />
|
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||||
Design.Width="1000"
|
Design.Width="1000"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:DataType="viewModels:SettingsViewModel">
|
x:DataType="viewModels:SettingsViewModel">
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
|
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
|
||||||
<TextBlock Text="OpenGL" />
|
<TextBlock Text="OpenGL" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
<ComboBoxItem IsEnabled="{Binding IsAppleSiliconMac}">
|
<ComboBoxItem IsEnabled="{x:Static helper:RunningPlatform.IsArmMac}">
|
||||||
<TextBlock Text="Metal (ARM Mac only, Experimental)" />
|
<TextBlock Text="Metal (ARM Mac only, Experimental)" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|||||||
@@ -170,7 +170,8 @@
|
|||||||
ToolTip.Tip="{ext:Locale TimeTooltip}"
|
ToolTip.Tip="{ext:Locale TimeTooltip}"
|
||||||
Width="250"/>
|
Width="250"/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
IsEnabled="{Binding !MatchSystemTime}"
|
||||||
SelectedDate="{Binding CurrentDate}"
|
SelectedDate="{Binding CurrentDate}"
|
||||||
ToolTip.Tip="{ext:Locale TimeTooltip}"
|
ToolTip.Tip="{ext:Locale TimeTooltip}"
|
||||||
Width="350" />
|
Width="350" />
|
||||||
@@ -181,17 +182,21 @@
|
|||||||
<TimePicker
|
<TimePicker
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
ClockIdentifier="24HourClock"
|
ClockIdentifier="24HourClock"
|
||||||
|
IsEnabled="{Binding !MatchSystemTime}"
|
||||||
SelectedTime="{Binding CurrentTime}"
|
SelectedTime="{Binding CurrentTime}"
|
||||||
Width="350"
|
Width="350"
|
||||||
ToolTip.Tip="{ext:Locale TimeTooltip}" />
|
ToolTip.Tip="{ext:Locale TimeTooltip}" />
|
||||||
<Button
|
</StackPanel>
|
||||||
Margin="10, 0, 0, 0"
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Click="MatchSystemTime_OnClick"
|
Text="{ext:Locale SettingsTabSystemSystemTimeMatch}"
|
||||||
Background="{DynamicResource SystemAccentColor}"
|
ToolTip.Tip="{ext:Locale MatchTimeTooltip}"
|
||||||
ToolTip.Tip="{ext:Locale MatchTimeTooltip}">
|
Width="250"/>
|
||||||
<TextBlock Text="{ext:Locale SettingsTabSystemSystemTimeMatch}" />
|
<CheckBox
|
||||||
</Button>
|
VerticalAlignment="Center"
|
||||||
|
IsChecked="{Binding MatchSystemTime}"
|
||||||
|
ToolTip.Tip="{ext:Locale MatchTimeTooltip}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Separator />
|
<Separator />
|
||||||
<StackPanel Margin="0,10,0,10"
|
<StackPanel Margin="0,10,0,10"
|
||||||
|
|||||||
@@ -34,7 +34,5 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
|
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MatchSystemTime_OnClick(object sender, RoutedEventArgs e) => ViewModel.MatchSystemTime();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
|
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
|
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||||
Width="1100"
|
Width="1100"
|
||||||
Height="768"
|
Height="768"
|
||||||
MinWidth="800"
|
MinWidth="800"
|
||||||
@@ -107,24 +108,36 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</ui:NavigationView.Styles>
|
</ui:NavigationView.Styles>
|
||||||
</ui:NavigationView>
|
</ui:NavigationView>
|
||||||
<ReversibleStackPanel
|
<Grid Grid.Row="2"
|
||||||
Grid.Row="2"
|
ColumnDefinitions="Auto,*,Auto">
|
||||||
Margin="10"
|
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||||
Spacing="10"
|
<Button
|
||||||
Orientation="Horizontal"
|
IsEnabled="{Binding WantsToReset}"
|
||||||
HorizontalAlignment="Right"
|
Margin="10"
|
||||||
ReverseOrder="{Binding IsMacOS}">
|
Content="{ext:Locale SettingsButtonReset}"
|
||||||
<Button
|
Command="{Binding ResetButton}" />
|
||||||
Classes="accent"
|
<CheckBox IsChecked="{Binding WantsToReset}"/>
|
||||||
Content="{ext:Locale SettingsButtonOk}"
|
<TextBlock Text="{ext:Locale SettingsButtonResetConfirm}"/>
|
||||||
Command="{Binding OkButton}" />
|
</StackPanel>
|
||||||
<Button
|
<ReversibleStackPanel
|
||||||
HotKey="Escape"
|
Grid.Column="2"
|
||||||
Content="{ext:Locale SettingsButtonCancel}"
|
Margin="10"
|
||||||
Command="{Binding CancelButton}" />
|
Spacing="10"
|
||||||
<Button
|
Orientation="Horizontal"
|
||||||
Content="{ext:Locale SettingsButtonApply}"
|
HorizontalAlignment="Right"
|
||||||
Command="{Binding ApplyButton}" />
|
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
|
||||||
</ReversibleStackPanel>
|
<Button
|
||||||
|
Classes="accent"
|
||||||
|
Content="{ext:Locale SettingsButtonOk}"
|
||||||
|
Command="{Binding OkButton}" />
|
||||||
|
<Button
|
||||||
|
HotKey="Escape"
|
||||||
|
Content="{ext:Locale SettingsButtonCancel}"
|
||||||
|
Command="{Binding CancelButton}" />
|
||||||
|
<Button
|
||||||
|
Content="{ext:Locale SettingsButtonApply}"
|
||||||
|
Command="{Binding ApplyButton}" />
|
||||||
|
</ReversibleStackPanel>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableAppWindow>
|
</window:StyleableAppWindow>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using LibHac.Ns;
|
|||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Utilities.Compat;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
@@ -21,11 +23,50 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
public bool Favorite { get; set; }
|
public bool Favorite { get; set; }
|
||||||
public byte[] Icon { get; set; }
|
public byte[] Icon { get; set; }
|
||||||
public string Name { get; set; } = "Unknown";
|
public string Name { get; set; } = "Unknown";
|
||||||
public ulong Id { get; set; }
|
|
||||||
|
private ulong _id;
|
||||||
|
|
||||||
|
public ulong Id
|
||||||
|
{
|
||||||
|
get => _id;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_id = value;
|
||||||
|
PlayabilityStatus = CompatibilityCsv.GetStatus(Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
public string Developer { get; set; } = "Unknown";
|
public string Developer { get; set; } = "Unknown";
|
||||||
public string Version { get; set; } = "0";
|
public string Version { get; set; } = "0";
|
||||||
|
|
||||||
|
public bool HasPlayabilityInfo => PlayabilityStatus != null;
|
||||||
|
|
||||||
|
public string LocalizedStatus =>
|
||||||
|
PlayabilityStatus.HasValue
|
||||||
|
? LocaleManager.Instance[PlayabilityStatus!.Value]
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
public LocaleKeys? PlayabilityStatus { get; set; }
|
||||||
|
public string LocalizedStatusTooltip =>
|
||||||
|
PlayabilityStatus.HasValue
|
||||||
|
#pragma warning disable CS8509 // It is exhaustive for any value this property can contain.
|
||||||
|
? LocaleManager.Instance[PlayabilityStatus!.Value switch
|
||||||
|
#pragma warning restore CS8509
|
||||||
|
{
|
||||||
|
LocaleKeys.CompatibilityListPlayable => LocaleKeys.CompatibilityListPlayableTooltip,
|
||||||
|
LocaleKeys.CompatibilityListIngame => LocaleKeys.CompatibilityListIngameTooltip,
|
||||||
|
LocaleKeys.CompatibilityListMenus => LocaleKeys.CompatibilityListMenusTooltip,
|
||||||
|
LocaleKeys.CompatibilityListBoots => LocaleKeys.CompatibilityListBootsTooltip,
|
||||||
|
LocaleKeys.CompatibilityListNothing => LocaleKeys.CompatibilityListNothingTooltip,
|
||||||
|
}]
|
||||||
|
: string.Empty;
|
||||||
public int PlayerCount { get; set; }
|
public int PlayerCount { get; set; }
|
||||||
public int GameCount { get; set; }
|
public int GameCount { get; set; }
|
||||||
|
|
||||||
|
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
|
||||||
|
|
||||||
|
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
|
||||||
|
public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString);
|
||||||
|
|
||||||
public TimeSpan TimePlayed { get; set; }
|
public TimeSpan TimePlayed { get; set; }
|
||||||
public DateTime? LastPlayed { get; set; }
|
public DateTime? LastPlayed { get; set; }
|
||||||
public string FileExtension { get; set; }
|
public string FileExtension { get; set; }
|
||||||
|
|||||||
@@ -128,13 +128,50 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
DynamicData.Kernel.Optional<ApplicationData> appData = Applications.Lookup(id);
|
DynamicData.Kernel.Optional<ApplicationData> appData = Applications.Lookup(id);
|
||||||
if (appData.HasValue)
|
if (appData.HasValue)
|
||||||
return appData.Value.Name;
|
return appData.Value.Name;
|
||||||
|
|
||||||
if (DownloadableContents.Keys.FindFirst(x => x.TitleId == id).TryGet(out DownloadableContentModel dlcData))
|
|
||||||
return Path.GetFileNameWithoutExtension(dlcData.FileName);
|
|
||||||
|
|
||||||
return id.ToString("X16");
|
if (!DownloadableContents.Keys.FindFirst(x => x.TitleId == id).TryGet(out DownloadableContentModel dlcData))
|
||||||
|
return id.ToString("X16");
|
||||||
|
|
||||||
|
string name = Path.GetFileNameWithoutExtension(dlcData.FileName)!;
|
||||||
|
int idx = name.IndexOf('[');
|
||||||
|
if (idx != -1)
|
||||||
|
name = name[..idx];
|
||||||
|
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool FindApplication(ulong id, out ApplicationData foundData)
|
||||||
|
{
|
||||||
|
DynamicData.Kernel.Optional<ApplicationData> appData = Applications.Lookup(id);
|
||||||
|
foundData = appData.HasValue ? appData.Value : null;
|
||||||
|
|
||||||
|
return appData.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FindUpdate(ulong id, out TitleUpdateModel foundData)
|
||||||
|
{
|
||||||
|
Gommon.Optional<TitleUpdateModel> appData =
|
||||||
|
TitleUpdates.Keys.FindFirst(x => x.TitleId == id);
|
||||||
|
foundData = appData.HasValue ? appData.Value : null;
|
||||||
|
|
||||||
|
return appData.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TitleUpdateModel[] FindUpdatesFor(ulong id)
|
||||||
|
=> TitleUpdates.Keys.Where(x => x.TitleIdBase == (id & ~0x1FFFUL)).ToArray();
|
||||||
|
|
||||||
|
public (TitleUpdateModel TitleUpdate, bool IsSelected)[] FindUpdateConfigurationFor(ulong id)
|
||||||
|
=> TitleUpdates.Items.Where(x => x.TitleUpdate.TitleIdBase == (id & ~0x1FFFUL)).ToArray();
|
||||||
|
|
||||||
|
public DownloadableContentModel[] FindDlcsFor(ulong id)
|
||||||
|
=> DownloadableContents.Keys.Where(x => x.TitleIdBase == (id & ~0x1FFFUL)).ToArray();
|
||||||
|
|
||||||
|
public (DownloadableContentModel Dlc, bool IsEnabled)[] FindDlcConfigurationFor(ulong id)
|
||||||
|
=> DownloadableContents.Items.Where(x => x.Dlc.TitleIdBase == (id & ~0x1FFFUL)).ToArray();
|
||||||
|
|
||||||
|
public bool HasDlcs(ulong id)
|
||||||
|
=> DownloadableContents.Keys.Any(x => x.TitleIdBase == (id & ~0x1FFFUL));
|
||||||
|
|
||||||
/// <exception cref="LibHac.Common.Keys.MissingKeyException">The configured key set is missing a key.</exception>
|
/// <exception cref="LibHac.Common.Keys.MissingKeyException">The configured key set is missing a key.</exception>
|
||||||
/// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception>
|
/// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception>
|
||||||
/// <exception cref="NotSupportedException">The NCA version is not supported.</exception>
|
/// <exception cref="NotSupportedException">The NCA version is not supported.</exception>
|
||||||
|
|||||||
@@ -47,11 +47,6 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatibility");
|
Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatibility");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Unload()
|
|
||||||
{
|
|
||||||
_entries = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CompatibilityEntry[] _entries;
|
private static CompatibilityEntry[] _entries;
|
||||||
|
|
||||||
public static CompatibilityEntry[] Entries
|
public static CompatibilityEntry[] Entries
|
||||||
@@ -64,6 +59,11 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
return _entries;
|
return _entries;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LocaleKeys? GetStatus(string titleId)
|
||||||
|
=> Entries.FirstOrDefault(x => x.TitleId.HasValue && x.TitleId.Value.EqualsIgnoreCase(titleId))?.Status;
|
||||||
|
|
||||||
|
public static LocaleKeys? GetStatus(ulong titleId) => GetStatus(titleId.ToString("X16"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CompatibilityEntry
|
public class CompatibilityEntry
|
||||||
@@ -100,12 +100,25 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
public Optional<string> TitleId { get; }
|
public Optional<string> TitleId { get; }
|
||||||
public string[] Labels { get; }
|
public string[] Labels { get; }
|
||||||
public LocaleKeys? Status { get; }
|
public LocaleKeys? Status { get; }
|
||||||
|
|
||||||
|
public LocaleKeys? StatusDescription
|
||||||
|
=> Status switch
|
||||||
|
{
|
||||||
|
LocaleKeys.CompatibilityListPlayable => LocaleKeys.CompatibilityListPlayableTooltip,
|
||||||
|
LocaleKeys.CompatibilityListIngame => LocaleKeys.CompatibilityListIngameTooltip,
|
||||||
|
LocaleKeys.CompatibilityListMenus => LocaleKeys.CompatibilityListMenusTooltip,
|
||||||
|
LocaleKeys.CompatibilityListBoots => LocaleKeys.CompatibilityListBootsTooltip,
|
||||||
|
LocaleKeys.CompatibilityListNothing => LocaleKeys.CompatibilityListNothingTooltip,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
public DateTime LastUpdated { get; }
|
public DateTime LastUpdated { get; }
|
||||||
|
|
||||||
public string LocalizedLastUpdated =>
|
public string LocalizedLastUpdated =>
|
||||||
LocaleManager.FormatDynamicValue(LocaleKeys.CompatibilityListLastUpdated, LastUpdated.Humanize());
|
LocaleManager.FormatDynamicValue(LocaleKeys.CompatibilityListLastUpdated, LastUpdated.Humanize());
|
||||||
|
|
||||||
public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
|
public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
|
||||||
|
public string LocalizedStatusDescription => LocaleManager.Instance[StatusDescription!.Value];
|
||||||
public string FormattedTitleId => TitleId
|
public string FormattedTitleId => TitleId
|
||||||
.OrElse(new string(' ', 16));
|
.OrElse(new string(' ', 16));
|
||||||
|
|
||||||
@@ -113,20 +126,17 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
.Select(FormatLabelName)
|
.Select(FormatLabelName)
|
||||||
.JoinToString(", ");
|
.JoinToString(", ");
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString() =>
|
||||||
{
|
new StringBuilder("CompatibilityEntry: {")
|
||||||
StringBuilder sb = new("CompatibilityEntry: {");
|
.Append($"{nameof(GameName)}=\"{GameName}\", ")
|
||||||
sb.Append($"{nameof(GameName)}=\"{GameName}\", ");
|
.Append($"{nameof(TitleId)}={TitleId}, ")
|
||||||
sb.Append($"{nameof(TitleId)}={TitleId}, ");
|
.Append($"{nameof(Labels)}={
|
||||||
sb.Append($"{nameof(Labels)}={
|
Labels.FormatCollection(it => $"\"{it}\"", separator: ", ", prefix: "[", suffix: "]")
|
||||||
Labels.FormatCollection(it => $"\"{it}\"", separator: ", ", prefix: "[", suffix: "]")
|
}, ")
|
||||||
}, ");
|
.Append($"{nameof(Status)}=\"{Status}\", ")
|
||||||
sb.Append($"{nameof(Status)}=\"{Status}\", ");
|
.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"")
|
||||||
sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"");
|
.Append('}')
|
||||||
sb.Append('}');
|
.ToString();
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string FormatLabelName(string labelName) => labelName.ToLower() switch
|
public static string FormatLabelName(string labelName) => labelName.ToLower() switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
Text="{ext:Locale CompatibilityListWarning}" />
|
Text="{ext:Locale CompatibilityListWarning}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto,Auto">
|
<Grid Grid.Row="1" ColumnDefinitions="*,Auto,Auto">
|
||||||
<TextBox Grid.Column="0" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermark}" TextChanged="TextBox_OnTextChanged" />
|
<TextBox Name="SearchBox" Grid.Column="0" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermark}" TextChanged="TextBox_OnTextChanged" />
|
||||||
<CheckBox Grid.Column="1" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
<CheckBox Grid.Column="1" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
||||||
<TextBlock Grid.Column="2" Margin="-10, 0, 0, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
<TextBlock Grid.Column="2" Margin="-10, 0, 0, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -64,6 +64,8 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{Binding LocalizedStatus}"
|
Text="{Binding LocalizedStatus}"
|
||||||
Width="85"
|
Width="85"
|
||||||
|
Background="Transparent"
|
||||||
|
ToolTip.Tip="{Binding LocalizedStatusDescription}"
|
||||||
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
TextWrapping="NoWrap" />
|
TextWrapping="NoWrap" />
|
||||||
<TextBlock Grid.Column="3"
|
<TextBlock Grid.Column="3"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
{
|
{
|
||||||
public partial class CompatibilityList : UserControl
|
public partial class CompatibilityList : UserControl
|
||||||
{
|
{
|
||||||
public static async Task Show()
|
public static async Task Show(string titleId = null)
|
||||||
{
|
{
|
||||||
ContentDialog contentDialog = new()
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,10 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
||||||
Content = new CompatibilityList
|
Content = new CompatibilityList
|
||||||
{
|
{
|
||||||
DataContext = new CompatibilityViewModel(RyujinxApp.MainWindow.ViewModel.ApplicationLibrary)
|
DataContext = new CompatibilityViewModel(RyujinxApp.MainWindow.ViewModel.ApplicationLibrary),
|
||||||
|
SearchBox = {
|
||||||
|
Text = titleId ?? ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,8 +35,6 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
contentDialog.Styles.Add(closeButtonParent);
|
contentDialog.Styles.Add(closeButtonParent);
|
||||||
|
|
||||||
await ContentDialogHelper.ShowAsync(contentDialog);
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
|
|
||||||
CompatibilityCsv.Unload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompatibilityList()
|
public CompatibilityList()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 62;
|
public const int CurrentVersion = 63;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
@@ -141,6 +141,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// Change System Time Offset in seconds
|
/// Change System Time Offset in seconds
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long SystemTimeOffset { get; set; }
|
public long SystemTimeOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instead of setting the time via configuration, use the values provided by the system.
|
||||||
|
/// </summary>
|
||||||
|
public bool MatchSystemTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Docked Mode
|
/// Enables or disables Docked Mode
|
||||||
|
|||||||
@@ -429,7 +429,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
(62, static cff => cff.RainbowSpeed = 1f)
|
(62, static cff => cff.RainbowSpeed = 1f),
|
||||||
|
(63, static cff => cff.MatchSystemTime = false)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// System Time Offset in Seconds
|
/// System Time Offset in Seconds
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<long> SystemTimeOffset { get; private set; }
|
public ReactiveObject<long> SystemTimeOffset { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instead of setting the time via configuration, use the values provided by the system.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> MatchSystemTime { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Docked Mode
|
/// Enables or disables Docked Mode
|
||||||
@@ -388,6 +393,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
TimeZone.LogChangesToValue(nameof(TimeZone));
|
TimeZone.LogChangesToValue(nameof(TimeZone));
|
||||||
SystemTimeOffset = new ReactiveObject<long>();
|
SystemTimeOffset = new ReactiveObject<long>();
|
||||||
SystemTimeOffset.LogChangesToValue(nameof(SystemTimeOffset));
|
SystemTimeOffset.LogChangesToValue(nameof(SystemTimeOffset));
|
||||||
|
MatchSystemTime = new ReactiveObject<bool>();
|
||||||
|
MatchSystemTime.LogChangesToValue(nameof(MatchSystemTime));
|
||||||
EnableDockedMode = new ReactiveObject<bool>();
|
EnableDockedMode = new ReactiveObject<bool>();
|
||||||
EnableDockedMode.LogChangesToValue(nameof(EnableDockedMode));
|
EnableDockedMode.LogChangesToValue(nameof(EnableDockedMode));
|
||||||
EnablePtc = new ReactiveObject<bool>();
|
EnablePtc = new ReactiveObject<bool>();
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
using Gommon;
|
|
||||||
using MsgPack;
|
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
|
||||||
using Ryujinx.Common.Helper;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Utilities
|
|
||||||
{
|
|
||||||
public static class PlayReport
|
|
||||||
{
|
|
||||||
public static PlayReportAnalyzer Analyzer { get; } = new PlayReportAnalyzer()
|
|
||||||
.AddSpec(
|
|
||||||
"01007ef00011e000",
|
|
||||||
spec => spec.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
|
||||||
)
|
|
||||||
.AddSpec( // Super Mario Odyssey
|
|
||||||
"0100000000010000",
|
|
||||||
spec =>
|
|
||||||
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
|
|
||||||
)
|
|
||||||
.AddSpec( // Super Mario Odyssey (China)
|
|
||||||
"010075000ECBE000",
|
|
||||||
spec =>
|
|
||||||
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
|
|
||||||
)
|
|
||||||
.AddSpec( // Super Mario 3D World + Bowser's Fury
|
|
||||||
"010028600EBDA000",
|
|
||||||
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
|
|
||||||
)
|
|
||||||
.AddSpec( // Mario Kart 8 Deluxe, Mario Kart 8 Deluxe (China)
|
|
||||||
["0100152000022000", "010075100E8EC000"],
|
|
||||||
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
|
|
||||||
);
|
|
||||||
|
|
||||||
private static PlayReportFormattedValue BreathOfTheWild_MasterMode(ref PlayReportValue value)
|
|
||||||
=> value.BoxedValue is 1 ? "Playing Master Mode" : PlayReportFormattedValue.ForceReset;
|
|
||||||
|
|
||||||
private static PlayReportFormattedValue SuperMarioOdyssey_AssistMode(ref PlayReportValue value)
|
|
||||||
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
|
||||||
|
|
||||||
private static PlayReportFormattedValue SuperMarioOdysseyChina_AssistMode(ref PlayReportValue value)
|
|
||||||
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
|
|
||||||
|
|
||||||
private static PlayReportFormattedValue SuperMario3DWorldOrBowsersFury(ref PlayReportValue value)
|
|
||||||
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
|
|
||||||
|
|
||||||
private static PlayReportFormattedValue MarioKart8Deluxe_Mode(ref PlayReportValue value)
|
|
||||||
=> value.BoxedValue switch
|
|
||||||
{
|
|
||||||
// Single Player
|
|
||||||
"Single" => "Single Player",
|
|
||||||
// Multiplayer
|
|
||||||
"Multi-2players" => "Multiplayer 2 Players",
|
|
||||||
"Multi-3players" => "Multiplayer 3 Players",
|
|
||||||
"Multi-4players" => "Multiplayer 4 Players",
|
|
||||||
// Wireless/LAN Play
|
|
||||||
"Local-Single" => "Wireless/LAN Play",
|
|
||||||
"Local-2players" => "Wireless/LAN Play 2 Players",
|
|
||||||
// CC Classes
|
|
||||||
"50cc" => "50cc",
|
|
||||||
"100cc" => "100cc",
|
|
||||||
"150cc" => "150cc",
|
|
||||||
"Mirror" => "Mirror (150cc)",
|
|
||||||
"200cc" => "200cc",
|
|
||||||
// Modes
|
|
||||||
"GrandPrix" => "Grand Prix",
|
|
||||||
"TimeAttack" => "Time Trials",
|
|
||||||
"VS" => "VS Races",
|
|
||||||
"Battle" => "Battle Mode",
|
|
||||||
"RaceStart" => "Selecting a Course",
|
|
||||||
"Race" => "Racing",
|
|
||||||
_ => PlayReportFormattedValue.ForceReset
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Analyzer implementation
|
|
||||||
|
|
||||||
public class PlayReportAnalyzer
|
|
||||||
{
|
|
||||||
private readonly List<PlayReportGameSpec> _specs = [];
|
|
||||||
|
|
||||||
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] }));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
_specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [..titleIds] }));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Action<PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
_specs.Add(new PlayReportGameSpec { TitleIds = [..titleIds] }.Apply(transform));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayReportFormattedValue Run(string runningGameId, ApplicationMetadata appMeta, MessagePackObject playReport)
|
|
||||||
{
|
|
||||||
if (!playReport.IsDictionary)
|
|
||||||
return PlayReportFormattedValue.Unhandled;
|
|
||||||
|
|
||||||
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec))
|
|
||||||
return PlayReportFormattedValue.Unhandled;
|
|
||||||
|
|
||||||
foreach (PlayReportValueFormatterSpec formatSpec in spec.Analyses.OrderBy(x => x.Priority))
|
|
||||||
{
|
|
||||||
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
PlayReportValue value = new()
|
|
||||||
{
|
|
||||||
Application = appMeta,
|
|
||||||
BoxedValue = valuePackObject.ToObject()
|
|
||||||
};
|
|
||||||
|
|
||||||
return formatSpec.ValueFormatter(ref value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PlayReportFormattedValue.Unhandled;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PlayReportGameSpec
|
|
||||||
{
|
|
||||||
public required string[] TitleIds { get; init; }
|
|
||||||
public List<PlayReportValueFormatterSpec> Analyses { get; } = [];
|
|
||||||
|
|
||||||
public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter)
|
|
||||||
{
|
|
||||||
Analyses.Add(new PlayReportValueFormatterSpec
|
|
||||||
{
|
|
||||||
Priority = Analyses.Count,
|
|
||||||
ReportKey = reportKey,
|
|
||||||
ValueFormatter = valueFormatter
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, PlayReportValueFormatter valueFormatter)
|
|
||||||
{
|
|
||||||
Analyses.Add(new PlayReportValueFormatterSpec
|
|
||||||
{
|
|
||||||
Priority = priority,
|
|
||||||
ReportKey = reportKey,
|
|
||||||
ValueFormatter = valueFormatter
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct PlayReportValue
|
|
||||||
{
|
|
||||||
public ApplicationMetadata Application { get; init; }
|
|
||||||
public object BoxedValue { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct PlayReportFormattedValue
|
|
||||||
{
|
|
||||||
public bool Handled { get; private init; }
|
|
||||||
|
|
||||||
public bool Reset { get; private init; }
|
|
||||||
|
|
||||||
public string FormattedString { get; private init; }
|
|
||||||
|
|
||||||
public static implicit operator PlayReportFormattedValue(string formattedValue)
|
|
||||||
=> new() { Handled = true, FormattedString = formattedValue };
|
|
||||||
|
|
||||||
public static PlayReportFormattedValue Unhandled => default;
|
|
||||||
public static PlayReportFormattedValue ForceReset => new() { Handled = true, Reset = true };
|
|
||||||
|
|
||||||
public static PlayReportValueFormatter AlwaysResets = AlwaysResetsImpl;
|
|
||||||
|
|
||||||
private static PlayReportFormattedValue AlwaysResetsImpl(ref PlayReportValue _) => ForceReset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct PlayReportValueFormatterSpec
|
|
||||||
{
|
|
||||||
public required int Priority { get; init; }
|
|
||||||
public required string ReportKey { get; init; }
|
|
||||||
public PlayReportValueFormatter ValueFormatter { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate PlayReportFormattedValue PlayReportValueFormatter(ref PlayReportValue value);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
117
src/Ryujinx/Utilities/PlayReport/Analyzer.cs
Normal file
117
src/Ryujinx/Utilities/PlayReport/Analyzer.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
using Gommon;
|
||||||
|
using MsgPack;
|
||||||
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The entrypoint for the Play Report analysis system.
|
||||||
|
/// </summary>
|
||||||
|
public class Analyzer
|
||||||
|
{
|
||||||
|
private readonly List<GameSpec> _specs = [];
|
||||||
|
|
||||||
|
public string[] TitleIds => Specs.SelectMany(x => x.TitleIds).ToArray();
|
||||||
|
|
||||||
|
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="titleId">The ID of the game to listen to Play Reports in.</param>
|
||||||
|
/// <param name="transform">The configuration function for the analysis spec.</param>
|
||||||
|
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||||
|
public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform)
|
||||||
|
{
|
||||||
|
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
|
||||||
|
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
||||||
|
|
||||||
|
_specs.Add(transform(new GameSpec { TitleIds = [titleId] }));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="titleId">The ID of the game to listen to Play Reports in.</param>
|
||||||
|
/// <param name="transform">The configuration function for the analysis spec.</param>
|
||||||
|
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||||
|
public Analyzer AddSpec(string titleId, Action<GameSpec> transform)
|
||||||
|
{
|
||||||
|
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
|
||||||
|
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
||||||
|
|
||||||
|
_specs.Add(new GameSpec { TitleIds = [titleId] }.Apply(transform));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an analysis spec matching a specific set of games by title IDs, with the provided spec configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param>
|
||||||
|
/// <param name="transform">The configuration function for the analysis spec.</param>
|
||||||
|
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||||
|
public Analyzer AddSpec(IEnumerable<string> titleIds,
|
||||||
|
Func<GameSpec, GameSpec> transform)
|
||||||
|
{
|
||||||
|
string[] tids = titleIds.ToArray();
|
||||||
|
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
||||||
|
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
||||||
|
|
||||||
|
_specs.Add(transform(new GameSpec { TitleIds = [..tids] }));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an analysis spec matching a specific set of games by title IDs, with the provided spec configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param>
|
||||||
|
/// <param name="transform">The configuration function for the analysis spec.</param>
|
||||||
|
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||||
|
public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform)
|
||||||
|
{
|
||||||
|
string[] tids = titleIds.ToArray();
|
||||||
|
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
||||||
|
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
||||||
|
|
||||||
|
_specs.Add(new GameSpec { TitleIds = [..tids] }.Apply(transform));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the configured <see cref="FormatterSpec"/> for the specified game title ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runningGameId">The game currently running.</param>
|
||||||
|
/// <param name="appMeta">The Application metadata information, including localized game name and play time information.</param>
|
||||||
|
/// <param name="playReport">The Play Report received from HLE.</param>
|
||||||
|
/// <returns>A struct representing a possible formatted value.</returns>
|
||||||
|
public FormattedValue Format(
|
||||||
|
string runningGameId,
|
||||||
|
ApplicationMetadata appMeta,
|
||||||
|
Horizon.Prepo.Types.PlayReport playReport
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!playReport.ReportData.IsDictionary)
|
||||||
|
return FormattedValue.Unhandled;
|
||||||
|
|
||||||
|
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
|
||||||
|
return FormattedValue.Unhandled;
|
||||||
|
|
||||||
|
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
|
||||||
|
{
|
||||||
|
if (!formatSpec.Format(appMeta, playReport, out FormattedValue value))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormattedValue.Unhandled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/Ryujinx/Utilities/PlayReport/Delegates.cs
Normal file
40
src/Ryujinx/Utilities/PlayReport/Delegates.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate type that powers single value formatters.<br/>
|
||||||
|
/// Takes in the result value from the Play Report, and outputs:
|
||||||
|
/// <br/>
|
||||||
|
/// a formatted string,
|
||||||
|
/// <br/>
|
||||||
|
/// a signal that nothing was available to handle it,
|
||||||
|
/// <br/>
|
||||||
|
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||||
|
/// </summary>
|
||||||
|
public delegate FormattedValue SingleValueFormatter(SingleValue value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate type that powers multiple value formatters.<br/>
|
||||||
|
/// Takes in the result values from the Play Report, and outputs:
|
||||||
|
/// <br/>
|
||||||
|
/// a formatted string,
|
||||||
|
/// <br/>
|
||||||
|
/// a signal that nothing was available to handle it,
|
||||||
|
/// <br/>
|
||||||
|
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||||
|
/// </summary>
|
||||||
|
public delegate FormattedValue MultiValueFormatter(MultiValue value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delegate type that powers multiple value formatters.
|
||||||
|
/// The dictionary passed to this delegate is sparsely populated;
|
||||||
|
/// that is, not every key specified in the Play Report needs to match for this to be used.<br/>
|
||||||
|
/// Takes in the result values from the Play Report, and outputs:
|
||||||
|
/// <br/>
|
||||||
|
/// a formatted string,
|
||||||
|
/// <br/>
|
||||||
|
/// a signal that nothing was available to handle it,
|
||||||
|
/// <br/>
|
||||||
|
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||||
|
/// </summary>
|
||||||
|
public delegate FormattedValue SparseMultiValueFormatter(SparseMultiValue value);
|
||||||
|
}
|
||||||
74
src/Ryujinx/Utilities/PlayReport/MatchedValues.cs
Normal file
74
src/Ryujinx/Utilities/PlayReport/MatchedValues.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using MsgPack;
|
||||||
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||||
|
{
|
||||||
|
public abstract class MatchedValue<T>
|
||||||
|
{
|
||||||
|
protected MatchedValue(T matched)
|
||||||
|
{
|
||||||
|
Matched = matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The currently running application's <see cref="ApplicationMetadata"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ApplicationMetadata Application { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entire play report.
|
||||||
|
/// </summary>
|
||||||
|
public Horizon.Prepo.Types.PlayReport PlayReport { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The matched value from the Play Report.
|
||||||
|
/// </summary>
|
||||||
|
public T Matched { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The input data to a <see cref="SingleValueFormatter"/>,
|
||||||
|
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||||
|
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
|
||||||
|
/// </summary>
|
||||||
|
public class SingleValue : MatchedValue<Value>
|
||||||
|
{
|
||||||
|
public SingleValue(Value matched) : base(matched)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The input data to a <see cref="MultiValueFormatter"/>,
|
||||||
|
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||||
|
/// and the matched <see cref="MessagePackObject"/>s from the Play Report.
|
||||||
|
/// </summary>
|
||||||
|
public class MultiValue : MatchedValue<Value[]>
|
||||||
|
{
|
||||||
|
public MultiValue(Value[] matched) : base(matched)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiValue(IEnumerable<MessagePackObject> matched) : base(Value.ConvertPackedObjects(matched))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The input data to a <see cref="SparseMultiValueFormatter"/>,
|
||||||
|
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||||
|
/// and the matched <see cref="MessagePackObject"/>s from the Play Report.
|
||||||
|
/// </summary>
|
||||||
|
public class SparseMultiValue : MatchedValue<Dictionary<string, Value>>
|
||||||
|
{
|
||||||
|
public SparseMultiValue(Dictionary<string, Value> matched) : base(matched)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SparseMultiValue(Dictionary<string, MessagePackObject> matched) : base(Value.ConvertPackedObjectMap(matched))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
127
src/Ryujinx/Utilities/PlayReport/PlayReports.cs
Normal file
127
src/Ryujinx/Utilities/PlayReport/PlayReports.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||||
|
{
|
||||||
|
public static class PlayReports
|
||||||
|
{
|
||||||
|
public static Analyzer Analyzer { get; } = new Analyzer()
|
||||||
|
.AddSpec(
|
||||||
|
"01007ef00011e000",
|
||||||
|
spec => spec
|
||||||
|
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
||||||
|
// reset to normal status when switching between normal & master mode in title screen
|
||||||
|
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
|
||||||
|
)
|
||||||
|
.AddSpec(
|
||||||
|
"0100f2c0115b6000",
|
||||||
|
spec => spec
|
||||||
|
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
|
||||||
|
.AddSpec(
|
||||||
|
"0100000000010000",
|
||||||
|
spec =>
|
||||||
|
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
|
||||||
|
)
|
||||||
|
.AddSpec(
|
||||||
|
"010075000ecbe000",
|
||||||
|
spec =>
|
||||||
|
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
|
||||||
|
)
|
||||||
|
.AddSpec(
|
||||||
|
"010028600ebda000",
|
||||||
|
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
|
||||||
|
)
|
||||||
|
.AddSpec( // Global & China IDs
|
||||||
|
["0100152000022000", "010075100e8ec000"],
|
||||||
|
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
|
||||||
|
)
|
||||||
|
.AddSpec(
|
||||||
|
["0100a3d008c5c000", "01008f6008c5e000"],
|
||||||
|
spec => spec
|
||||||
|
.AddValueFormatter("area_no", PokemonSVArea)
|
||||||
|
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
|
||||||
|
);
|
||||||
|
|
||||||
|
private static FormattedValue BreathOfTheWild_MasterMode(SingleValue value)
|
||||||
|
=> value.Matched.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
|
||||||
|
|
||||||
|
private static FormattedValue TearsOfTheKingdom_CurrentField(SingleValue value) =>
|
||||||
|
value.Matched.DoubleValue switch
|
||||||
|
{
|
||||||
|
> 800d => "Exploring the Sky Islands",
|
||||||
|
< -201d => "Exploring the Depths",
|
||||||
|
_ => "Roaming Hyrule"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
|
||||||
|
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
||||||
|
|
||||||
|
private static FormattedValue SuperMarioOdysseyChina_AssistMode(SingleValue value)
|
||||||
|
=> value.Matched.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
|
||||||
|
|
||||||
|
private static FormattedValue SuperMario3DWorldOrBowsersFury(SingleValue value)
|
||||||
|
=> value.Matched.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
|
||||||
|
|
||||||
|
private static FormattedValue MarioKart8Deluxe_Mode(SingleValue value)
|
||||||
|
=> value.Matched.StringValue switch
|
||||||
|
{
|
||||||
|
// Single Player
|
||||||
|
"Single" => "Single Player",
|
||||||
|
// Multiplayer
|
||||||
|
"Multi-2players" => "Multiplayer 2 Players",
|
||||||
|
"Multi-3players" => "Multiplayer 3 Players",
|
||||||
|
"Multi-4players" => "Multiplayer 4 Players",
|
||||||
|
// Wireless/LAN Play
|
||||||
|
"Local-Single" => "Wireless/LAN Play",
|
||||||
|
"Local-2players" => "Wireless/LAN Play 2 Players",
|
||||||
|
// CC Classes
|
||||||
|
"50cc" => "50cc",
|
||||||
|
"100cc" => "100cc",
|
||||||
|
"150cc" => "150cc",
|
||||||
|
"Mirror" => "Mirror (150cc)",
|
||||||
|
"200cc" => "200cc",
|
||||||
|
// Modes
|
||||||
|
"GrandPrix" => "Grand Prix",
|
||||||
|
"TimeAttack" => "Time Trials",
|
||||||
|
"VS" => "VS Races",
|
||||||
|
"Battle" => "Battle Mode",
|
||||||
|
"RaceStart" => "Selecting a Course",
|
||||||
|
"Race" => "Racing",
|
||||||
|
_ => FormattedValue.ForceReset
|
||||||
|
};
|
||||||
|
|
||||||
|
private static FormattedValue PokemonSVUnionCircle(SingleValue value)
|
||||||
|
=> value.Matched.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
|
||||||
|
|
||||||
|
private static FormattedValue PokemonSVArea(SingleValue value)
|
||||||
|
=> value.Matched.StringValue switch
|
||||||
|
{
|
||||||
|
// Base Game Locations
|
||||||
|
"a_w01" => "South Area One",
|
||||||
|
"a_w02" => "Mesagoza",
|
||||||
|
"a_w03" => "The Pokemon League",
|
||||||
|
"a_w04" => "South Area Two",
|
||||||
|
"a_w05" => "South Area Four",
|
||||||
|
"a_w06" => "South Area Six",
|
||||||
|
"a_w07" => "South Area Five",
|
||||||
|
"a_w08" => "South Area Three",
|
||||||
|
"a_w09" => "West Area One",
|
||||||
|
"a_w10" => "Asado Desert",
|
||||||
|
"a_w11" => "West Area Two",
|
||||||
|
"a_w12" => "Medali",
|
||||||
|
"a_w13" => "Tagtree Thicket",
|
||||||
|
"a_w14" => "East Area Three",
|
||||||
|
"a_w15" => "Artazon",
|
||||||
|
"a_w16" => "East Area Two",
|
||||||
|
"a_w18" => "Casseroya Lake",
|
||||||
|
"a_w19" => "Glaseado Mountain",
|
||||||
|
"a_w20" => "North Area Three",
|
||||||
|
"a_w21" => "North Area One",
|
||||||
|
"a_w22" => "North Area Two",
|
||||||
|
"a_w23" => "The Great Crater of Paldea",
|
||||||
|
"a_w24" => "South Paldean Sea",
|
||||||
|
"a_w25" => "West Paldean Sea",
|
||||||
|
"a_w26" => "East Paldean Sea",
|
||||||
|
"a_w27" => "Nouth Paldean Sea",
|
||||||
|
//TODO DLC Locations
|
||||||
|
_ => FormattedValue.ForceReset
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user