mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-08-01 18:49:05 -06:00
Compare commits
164 Commits
Canary-1.2
...
Canary-1.2
Author | SHA1 | Date | |
---|---|---|---|
a3596ba858 | |||
3ffcc72117 | |||
fe1617ffea | |||
eb6b0e9adc | |||
9631bdfe16 | |||
2a84656ffc | |||
6c6580ddcc | |||
c47448628c | |||
d0ac83b493 | |||
e0ddbe55c0 | |||
4a4078865f | |||
3f59bade94 | |||
c2ed0fd5fd | |||
de16d8fa3e | |||
9b1fb3a27b | |||
b9150a0092 | |||
c1002d4826 | |||
b1de7696ee | |||
f91cd05260 | |||
920933bc9f | |||
52b0b45d34 | |||
12f0dbcc70 | |||
719be560ec | |||
18238736be | |||
5d9e4ad7a4 | |||
adba775f0c | |||
2ffaeb2803 | |||
b16b844760 | |||
bc07bc482d | |||
61975ca44d | |||
66054dd225 | |||
b1f61e5143 | |||
0d7d0e8092 | |||
aa2178dbe5 | |||
f92d09711b | |||
45ee8cd0e8 | |||
395bbd144a | |||
744d813b87 | |||
7d59ada798 | |||
a4b5304935 | |||
0965ee905d | |||
855161b23b | |||
6b55d158b7 | |||
91f73a4891 | |||
883d4d863a | |||
ca5de909a1 | |||
5172567b08 | |||
6fe4cee7c0 | |||
8623452abc | |||
17e8ae1d9a | |||
7591b07fce | |||
89b4389ed2 | |||
d9ee729199 | |||
ba0cd13cff | |||
501b199e24 | |||
8aecccadb8 | |||
e23d610f49 | |||
f6822f7358 | |||
d3f84a1305 | |||
06d34a5992 | |||
e8e1dc6619 | |||
c5603d4c36 | |||
05b56730d6 | |||
43f7b000ca | |||
ad89cf39b6 | |||
96c33a0b92 | |||
e0db55df46 | |||
30fef8e96e | |||
9cb5f5689b | |||
a205ec374b | |||
aab9b58542 | |||
daa648dc40 | |||
1024aa8757 | |||
13388e972a | |||
1eb78872d8 | |||
fe9fe2a10f | |||
6ab899f621 | |||
faacec9801 | |||
55fdb3f6b2 | |||
1129ab0e8c | |||
b6b391b2cf | |||
f3cf03495d | |||
7bce8206d5 | |||
efa0cc7554 | |||
1c0813d09d | |||
8bec09d7ff | |||
e4b4e94b56 | |||
764c9e9d4e | |||
05e991db87 | |||
2cd876b1cb | |||
93a298523f | |||
253cbb2810 | |||
9c226dcc7a | |||
30a534edcd | |||
1d88771d1b | |||
4e8157688e | |||
5085af0050 | |||
2c8edaf89e | |||
aa8ba8b503 | |||
a4211fec33 | |||
54b233dd78 | |||
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 | |||
55536f5d78 | |||
b2eecd28ce | |||
fe43c32e60 | |||
8117e160c2 | |||
bf713a80d6 | |||
b38b5a1e70 | |||
2d7700949c | |||
ea2287af03 | |||
37af8c70aa | |||
50cee3fd19 | |||
a46aacf2e2 | |||
ad9d6588e8 | |||
38ef65aae0 | |||
9f94aa1c79 | |||
2c9a26c11c | |||
a4a15a4c80 | |||
cc3b95eee1 | |||
2ab806f759 | |||
6d75410bd2 | |||
196b2eaf66 | |||
82fe519766 | |||
ff05e03cc8 | |||
e18c6d90c4 | |||
9075a3960b | |||
3cf54987d2 |
4
.github/workflows/canary.yml
vendored
4
.github/workflows/canary.yml
vendored
@ -29,7 +29,7 @@ env:
|
||||
jobs:
|
||||
tag:
|
||||
name: Create tag
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
@ -202,7 +202,7 @@ jobs:
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -18,7 +18,7 @@ env:
|
||||
jobs:
|
||||
tag:
|
||||
name: Create tag
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
@ -183,7 +183,7 @@ jobs:
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
@ -42,11 +42,10 @@
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||
<PackageVersion Include="Gommon" Version="2.7.1" />
|
||||
<PackageVersion Include="Gommon" Version="2.7.1.1" />
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
<PackageVersion Include="Sep" Version="0.6.0" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpMetal" Version="1.0.0-preview21" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
||||
|
@ -39,12 +39,12 @@
|
||||
<p align="center">
|
||||
Click below to join the Discord:
|
||||
<br>
|
||||
<a href="https://discord.gg/dHPrkBkkyA">
|
||||
<a href="https://discord.gg/PEuzjrFXUA">
|
||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
|
||||
<img src="https://raw.githubusercontent.com/Ryubing/Ryujinx/refs/heads/master/docs/shell.png">
|
||||
</p>
|
||||
|
||||
## Usage
|
||||
|
24
Ryujinx.sln
24
Ryujinx.sln
@ -77,15 +77,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E} = {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal.SharpMetalExtensions", "src/Ryujinx.Graphics.Metal.SharpMetalExtensions\Ryujinx.Graphics.Metal.SharpMetalExtensions.csproj", "{81EA598C-DBA1-40B0-8DA4-4796B78F2037}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
@ -95,6 +86,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -217,6 +210,10 @@ Global
|
||||
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -249,16 +246,9 @@ Global
|
||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -332,6 +332,7 @@
|
||||
0100E680149DC000,"Arcaea",,playable,2023-03-16 19:31:21
|
||||
01003C2010C78000,"Archaica: The Path Of Light",crash,nothing,2020-10-16 13:22:26
|
||||
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
|
||||
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
|
||||
@ -426,6 +427,7 @@
|
||||
0100E48013A34000,"Balan Wonderworld Demo",gpu;services;UE4;demo,ingame,2023-02-16 20:05:07
|
||||
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
|
||||
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
|
||||
01008D30128E0000,"Bamerang",,playable,2022-10-26 00:29:39
|
||||
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
|
||||
0100DF0011A6A000,"Bladed Fury",,playable,2022-10-26 11:36:26
|
||||
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
|
||||
010039501405E000,"Blanc",gpu;slow,ingame,2023-02-22 14:00:13
|
||||
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
|
||||
01009740120FE000,"DISTRAINT 2",,playable,2020-09-03 16:08:12
|
||||
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
|
||||
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
|
||||
@ -966,6 +969,7 @@
|
||||
0100751007ADA000,"Don't Starve: Nintendo Switch Edition",nvdec,playable,2022-02-05 20:43:34
|
||||
010088B010DD2000,"Dongo Adventure",,playable,2022-10-04 16:22:26
|
||||
0100C1F0051B6000,"Donkey Kong Country™: Tropical Freeze",,playable,2024-08-05 16:46:10
|
||||
01009D901BC56000,"Donkey Kong Country™: Returns HD",gpu,ingame,2025-02-16 13:44:12
|
||||
0100F2C00F060000,"Doodle Derby",,boots,2020-12-04 22:51:48
|
||||
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
|
||||
010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
|
||||
@ -1155,7 +1159,7 @@
|
||||
010095600AA36000,"Fill-a-Pix: Phil's Epic Adventure",,playable,2020-12-22 13:48:22
|
||||
0100C3A00BB76000,"Fimbul",nvdec,playable,2022-07-26 13:31:47
|
||||
0100C8200E942000,"Fin and the Ancient Mystery",nvdec,playable,2020-12-17 16:40:39
|
||||
01000EA014150000,"FINAL FANTASY",crash,nothing,2024-09-05 20:55:30
|
||||
01000EA014150000,"FINAL FANTASY",,playable,2025-02-16 21:27:30
|
||||
01006B7014156000,"FINAL FANTASY II",crash,nothing,2024-04-13 19:18:04
|
||||
01006F000B056000,"FINAL FANTASY IX",audout;nvdec,playable,2021-06-05 11:35:00
|
||||
0100AA201415C000,"FINAL FANTASY V",,playable,2023-04-26 01:11:55
|
||||
@ -1246,7 +1250,7 @@
|
||||
0100A6B00D4EC000,"Furwind",,playable,2021-02-19 19:44:08
|
||||
0100ECE00C0C4000,"Fury Unleashed",crash;services,ingame,2020-10-18 11:52:40
|
||||
010070000ED9E000,"Fury Unleashed Demo",,playable,2020-10-08 20:09:21
|
||||
0100E1F013674000,"FUSER™",nvdec;UE4,playable,2022-10-17 20:58:32
|
||||
0100E1F013674000,"FUSER™",nvdec;UE4;slow;gpu,ingame,2025-02-12 16:03:00
|
||||
0100A7A015E4C000,"Fushigi no Gensokyo Lotus Labyrinth",Needs Update;audio;gpu;nvdec,ingame,2021-01-20 15:30:02
|
||||
01003C300B274000,"Futari de! Nyanko Daisensou",,playable,2024-01-05 22:26:52
|
||||
010055801134E000,"FUZE Player",online-broken;vulkan-backend-bug,ingame,2022-10-18 12:23:53
|
||||
@ -1432,7 +1436,7 @@
|
||||
010083A018262000,"Hitman: Blood Money — Reprisal",deadlock,ingame,2024-09-28 16:28:50
|
||||
01004B100A5CC000,"Hob: The Definitive Edition",,playable,2021-01-13 09:39:19
|
||||
0100F7300ED2C000,"Hoggy2",,playable,2022-10-10 13:53:35
|
||||
0100F7E00C70E000,"Hogwarts Legacy",slow,ingame,2024-09-03 19:53:58
|
||||
0100F7E00C70E000,"Hogwarts Legacy",UE4;slow,ingame,2024-09-03 19:53:58
|
||||
0100633007D48000,"Hollow Knight",nvdec,playable,2023-01-16 15:44:56
|
||||
0100F2100061E800,"Hollow0",UE4;gpu,ingame,2021-03-03 23:42:56
|
||||
0100342009E16000,"Holy Potatoes! What The Hell?!",,playable,2020-07-03 10:48:56
|
||||
@ -1654,7 +1658,7 @@
|
||||
0100A73006E74000,"Legendary Eleven",,playable,2021-06-08 12:09:03
|
||||
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
|
||||
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
|
||||
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
|
||||
@ -1796,6 +1800,7 @@
|
||||
010005A00B312000,"Megaton Rainfall",gpu;opengl,boots,2022-08-04 18:29:43
|
||||
0100EA100DF92000,"Meiji Katsugeki Haikara Ryuuseigumi - Seibai Shimaseu, Yonaoshi Kagyou",32-bit;nvdec,playable,2022-12-05 13:19:12
|
||||
0100B360068B2000,"Mekorama",gpu,boots,2021-06-17 16:37:21
|
||||
010012301932A000,"Melatonin",,playable,2025-02-16 04:08:17
|
||||
01000FA010340000,"Melbits World",nvdec;online,menus,2021-11-26 13:51:22
|
||||
0100F68019636000,"Melon Journey",,playable,2023-04-23 21:20:01
|
||||
010079C012896000,"Memories Off -Innocent Fille- for Dearest",,playable,2020-08-04 07:31:22
|
||||
@ -1913,6 +1918,7 @@
|
||||
010073E008E6E000,"Mugsters",,playable,2021-01-28 17:57:17
|
||||
0100A8400471A000,"MUJO",,playable,2020-05-08 16:31:04
|
||||
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
|
||||
01008E200C5C2000,"Muse Dash",,playable,2020-06-06 14:41:29
|
||||
010035901046C000,"Mushroom Quest",,playable,2020-05-17 13:07:08
|
||||
@ -2028,6 +2034,7 @@
|
||||
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
|
||||
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
|
||||
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
|
||||
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
|
||||
@ -2058,7 +2065,7 @@
|
||||
010002700C34C000,"Numbala",,playable,2020-05-11 12:01:07
|
||||
010020500C8C8000,"Number Place 10000",gpu,menus,2021-11-24 09:14:23
|
||||
010003701002C000,"Nurse Love Syndrome",,playable,2022-10-13 10:05:22
|
||||
0000000000000000,"nx-hbmenu",Needs Update;homebrew,boots,2024-04-06 22:05:32
|
||||
,"nx-hbmenu",Needs Update;homebrew,boots,2024-04-06 22:05:32
|
||||
,"nxquake2",services;crash;homebrew,nothing,2022-08-04 23:14:04
|
||||
010049F00EC30000,"Nyan Cat: Lost in Space",online,playable,2021-06-12 13:22:03
|
||||
01002E6014FC4000,"O---O",,playable,2022-10-29 12:12:14
|
||||
@ -2466,7 +2473,7 @@
|
||||
0100AFE00DDAC000,"Royal Roads",,playable,2020-11-17 12:54:38
|
||||
0100E2C00B414000,"RPG Maker MV",nvdec,playable,2021-01-05 20:12:01
|
||||
01005CD015986000,"rRootage Reloaded",,playable,2022-08-05 23:20:18
|
||||
0000000000000000,"RSDKv5u",homebrew,ingame,2024-04-01 16:25:34
|
||||
,"RSDKv5u",homebrew,ingame,2024-04-01 16:25:34
|
||||
010009B00D33C000,"Rugby Challenge 4",slow;online-broken;UE4,playable,2022-10-06 12:45:53
|
||||
01006EC00F2CC000,"RUINER",UE4,playable,2022-10-03 14:11:33
|
||||
010074F00DE4A000,"Run the Fan",,playable,2021-02-27 13:36:28
|
||||
@ -2475,6 +2482,7 @@
|
||||
010081C0191D8000,"Rune Factory 3 Special",,playable,2023-10-15 08:32:49
|
||||
010051D00E3A4000,"Rune Factory 4 Special",32-bit;crash;nvdec,ingame,2023-05-06 08:49:17
|
||||
010014D01216E000,"Rune Factory 5 (JP)",gpu,ingame,2021-06-01 12:00:36
|
||||
010071E0145F8000,"Rustler",,playable,2025-02-10 20:17:12
|
||||
0100E21013908000,"RWBY: Grimm Eclipse - Definitive Edition",online-broken,playable,2022-11-03 10:44:01
|
||||
010012C0060F0000,"RXN -Raijin-",nvdec,playable,2021-01-10 16:05:43
|
||||
0100B8B012ECA000,"S.N.I.P.E.R. - Hunter Scope",,playable,2021-04-19 15:58:09
|
||||
@ -2483,7 +2491,7 @@
|
||||
0100A5200C2E0000,"Safety First!",,playable,2021-01-06 09:05:23
|
||||
0100A51013530000,"SaGa Frontier Remastered",nvdec,playable,2022-11-03 13:54:56
|
||||
010003A00D0B4000,"SaGa SCARLET GRACE: AMBITIONS™",,playable,2022-10-06 13:20:31
|
||||
01008D100D43E000,"Saints Row IV®: Re-Elected™",ldn-untested;LAN,playable,2023-12-04 18:33:37
|
||||
01008D100D43E000,"Saints Row IV®: Re-Elected™",ldn-untested;LAN;deadlock,ingame,2025-02-02 16:57:53
|
||||
0100DE600BEEE000,"SAINTS ROW®: THE THIRD™ - THE FULL PACKAGE",slow;LAN,playable,2023-08-24 02:40:58
|
||||
01007F000EB36000,"Sakai and...",nvdec,playable,2022-12-15 13:53:19
|
||||
0100B1400E8FE000,"Sakuna: Of Rice and Ruin",,playable,2023-07-24 13:47:13
|
||||
@ -2532,7 +2540,7 @@
|
||||
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
|
||||
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
|
||||
01009840046BC000,"Semispheres",,playable,2021-01-06 23:08:31
|
||||
0100D1800D902000,"SENRAN KAGURA Peach Ball",,playable,2021-06-03 15:12:10
|
||||
@ -2668,10 +2676,10 @@
|
||||
01004F401BEBE000,"Song of Nunu: A League of Legends Story",,ingame,2024-07-12 18:53:44
|
||||
0100E5400BF94000,"Songbird Symphony",,playable,2021-02-27 02:44:04
|
||||
010031D00A604000,"Songbringer",,playable,2020-06-22 10:42:02
|
||||
0000000000000000,"Sonic 1 (2013)",crash;homebrew,ingame,2024-04-06 18:31:20
|
||||
0000000000000000,"Sonic 2 (2013)",crash;homebrew,ingame,2024-04-01 16:25:30
|
||||
0000000000000000,"Sonic A.I.R",homebrew,ingame,2024-04-01 16:25:32
|
||||
0000000000000000,"Sonic CD",crash;homebrew,ingame,2024-04-01 16:25:31
|
||||
,"Sonic 1 (2013)",crash;homebrew,ingame,2024-04-06 18:31:20
|
||||
,"Sonic 2 (2013)",crash;homebrew,ingame,2024-04-01 16:25:30
|
||||
,"Sonic A.I.R",homebrew,ingame,2024-04-01 16:25:32
|
||||
,"Sonic CD",crash;homebrew,ingame,2024-04-01 16:25:31
|
||||
010040E0116B8000,"Sonic Colors: Ultimate",,playable,2022-11-12 21:24:26
|
||||
01001270012B6000,"SONIC FORCES™",,playable,2024-07-28 13:11:21
|
||||
01004AD014BF0000,"Sonic Frontiers",gpu;deadlock;amd-vendor-bug;intel-vendor-bug,ingame,2024-09-05 09:18:53
|
||||
@ -2688,7 +2696,7 @@
|
||||
0100707011722000,"Space Elite Force",,playable,2020-11-27 15:21:05
|
||||
010047B010260000,"Space Pioneer",,playable,2022-10-20 12:24:37
|
||||
010010A009830000,"Space Ribbon",,playable,2022-08-15 17:17:10
|
||||
0000000000000000,"SpaceCadetPinball",homebrew,ingame,2024-04-18 19:30:04
|
||||
,"SpaceCadetPinball",homebrew,ingame,2024-04-18 19:30:04
|
||||
0100D9B0041CE000,"Spacecats with Lasers",,playable,2022-08-15 17:22:44
|
||||
010034800FB60000,"Spaceland",,playable,2020-11-01 14:31:56
|
||||
010028D0045CE000,"Sparkle 2",,playable,2020-10-19 11:51:39
|
||||
@ -2832,8 +2840,9 @@
|
||||
01009B90006DC000,"Super Mario Maker™ 2",online-broken;ldn-broken,playable,2024-08-25 11:05:19
|
||||
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
|
||||
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
|
||||
0100965017338000,"Super Mario Party Jamboree",mac-bug;gpu,ingame,2025-02-17 02:09:20
|
||||
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
|
||||
0000000000000000,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
|
||||
,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
|
||||
010049900F546000,"Super Mario™ 3D All-Stars",services-horizon;slow;vulkan;amd-vendor-bug,ingame,2024-05-07 02:38:16
|
||||
010028600EBDA000,"Super Mario™ 3D World + Bowser’s Fury",ldn-works,playable,2024-07-31 10:45:37
|
||||
01004F8006A78000,"Super Meat Boy",services,playable,2020-04-02 23:10:07
|
||||
@ -2964,6 +2973,7 @@
|
||||
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
|
||||
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
|
||||
0100B13007A6A000,"The Gardens Between",,playable,2021-01-29 16:16:53
|
||||
010036E00FB20000,"The Great Ace Attorney Chronicles",,playable,2023-06-22 21:26:29
|
||||
@ -2981,6 +2991,8 @@
|
||||
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
|
||||
0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34
|
||||
01006fe0096ac000,"The Jackbox Party Pack 5",slow;online-working,ingame,2025-02-14 05:32:00
|
||||
01005a400db52000,"The Jackbox Party Pack 6",slow;online-working,ingame,2025-02-14 05:26:00
|
||||
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
|
||||
01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13
|
||||
@ -3159,6 +3171,7 @@
|
||||
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
|
||||
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
|
||||
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
|
||||
@ -3208,6 +3221,7 @@
|
||||
0100AB2010B4C000,"Unlock The King",,playable,2020-09-01 13:58:27
|
||||
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
|
||||
0100b6e012ebe000,"UNO",ldn-untested,ingame,2025-02-03 22:40:00
|
||||
0100E5D00CC0C000,"Unravel Two",nvdec,playable,2024-05-23 15:45:05
|
||||
010001300CC4A000,"Unruly Heroes",,playable,2021-01-07 18:09:31
|
||||
0100B410138C0000,"Unspottable",,playable,2022-10-25 19:28:49
|
||||
@ -3372,6 +3386,7 @@
|
||||
0100F47016F26000,"Yomawari 3",,playable,2022-05-10 08:26:51
|
||||
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
|
||||
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
|
||||
0100F110029C8000,"Yooka-Laylee",,playable,2021-01-28 14:21:45
|
||||
010022F00DA66000,"Yooka-Laylee and the Impossible Lair",,playable,2021-03-05 17:32:21
|
||||
|
|
@ -1,7 +1,6 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.CodeGen.Unwinding;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.CodeGen
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -7,6 +7,7 @@ namespace ARMeilleure.Memory
|
||||
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
|
||||
|
||||
public IJitMemoryBlock Block { get; }
|
||||
public IJitMemoryAllocator Allocator { get; }
|
||||
|
||||
public nint Pointer => Block.Pointer;
|
||||
|
||||
@ -21,6 +22,7 @@ namespace ARMeilleure.Memory
|
||||
granularity = DefaultGranularity;
|
||||
}
|
||||
|
||||
Allocator = allocator;
|
||||
Block = allocator.Reserve(maxSize);
|
||||
_maxSize = maxSize;
|
||||
_sizeGranularity = granularity;
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using Ryujinx.Common.Memory.PartialUnmaps;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using ARMeilleure.Memory;
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
|
@ -6,7 +6,6 @@ using ARMeilleure.Instructions;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
@ -2,6 +2,8 @@ using ARMeilleure.CodeGen;
|
||||
using ARMeilleure.CodeGen.Unwinding;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Native;
|
||||
using Humanizer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -18,51 +20,68 @@ namespace ARMeilleure.Translation.Cache
|
||||
private static readonly int _pageMask = _pageSize - 1;
|
||||
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int CacheSize = 2047 * 1024 * 1024;
|
||||
private const int CacheSize = 256 * 1024 * 1024;
|
||||
|
||||
private static ReservedRegion _jitRegion;
|
||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||
|
||||
private static CacheMemoryAllocator _cacheAllocator;
|
||||
private static List<CacheMemoryAllocator> _cacheAllocators = [];
|
||||
|
||||
private static readonly List<CacheEntry> _cacheEntries = [];
|
||||
|
||||
private static readonly Lock _lock = new();
|
||||
private static bool _initialized;
|
||||
|
||||
private static readonly List<ReservedRegion> _jitRegions = [];
|
||||
private static int _activeRegionIndex = 0;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
|
||||
|
||||
public static void Initialize(IJitMemoryAllocator allocator)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
JitUnwindWindows.RemoveFunctionTableHandler(
|
||||
_jitRegions[0].Pointer);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _jitRegions.Count; i++)
|
||||
{
|
||||
_jitRegions[i].Dispose();
|
||||
}
|
||||
|
||||
_jitRegions.Clear();
|
||||
_cacheAllocators.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
||||
_activeRegionIndex = 0;
|
||||
|
||||
ReservedRegion firstRegion = new(allocator, CacheSize);
|
||||
_jitRegions.Add(firstRegion);
|
||||
|
||||
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
|
||||
_cacheAllocators.Add(firstCacheAllocator);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
||||
}
|
||||
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
|
||||
JitUnwindWindows.InstallFunctionTableHandler(
|
||||
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
|
||||
);
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,8 +94,8 @@ namespace ARMeilleure.Translation.Cache
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = Allocate(code.Length);
|
||||
|
||||
nint funcPtr = _jitRegion.Pointer + funcOffset;
|
||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||
nint funcPtr = targetRegion.Pointer + funcOffset;
|
||||
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@ -90,9 +109,9 @@ namespace ARMeilleure.Translation.Cache
|
||||
}
|
||||
else
|
||||
{
|
||||
ReprotectAsWritable(funcOffset, code.Length);
|
||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||
ReprotectAsExecutable(funcOffset, code.Length);
|
||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||
|
||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@ -116,52 +135,77 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
foreach (ReservedRegion region in _jitRegions)
|
||||
{
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
||||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReprotectAsWritable(int offset, int size)
|
||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static void ReprotectAsExecutable(int offset, int size)
|
||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static int Allocate(int codeSize)
|
||||
{
|
||||
codeSize = AlignCodeSize(codeSize);
|
||||
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
|
||||
|
||||
if (allocOffset < 0)
|
||||
if (allocOffset >= 0)
|
||||
{
|
||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
||||
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
int exhaustedRegion = _activeRegionIndex;
|
||||
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
|
||||
_jitRegions.Add(newRegion);
|
||||
_activeRegionIndex = _jitRegions.Count - 1;
|
||||
|
||||
return allocOffset;
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
|
||||
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
|
||||
|
||||
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
|
||||
if (allocOffsetNew < 0)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||
}
|
||||
|
||||
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
|
||||
return allocOffsetNew;
|
||||
}
|
||||
|
||||
|
||||
private static int AlignCodeSize(int codeSize)
|
||||
{
|
||||
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
|
||||
@ -185,18 +229,21 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
|
||||
if (index < 0)
|
||||
foreach (ReservedRegion _ in _jitRegions)
|
||||
{
|
||||
index = ~index - 1;
|
||||
}
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
entry = _cacheEntries[index];
|
||||
entryIndex = index;
|
||||
return true;
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index - 1;
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
entry = _cacheEntries[index];
|
||||
entryIndex = index;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using ARMeilleure.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.Translation.Cache
|
||||
|
@ -52,6 +52,11 @@ namespace ARMeilleure.Translation.Cache
|
||||
nint context,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll);
|
||||
|
||||
[LibraryImport("kernel32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static unsafe partial bool RtlDeleteFunctionTable(
|
||||
ulong tableIdentifier);
|
||||
|
||||
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
|
||||
|
||||
private static int _sizeOfRuntimeFunction;
|
||||
@ -91,6 +96,23 @@ namespace ARMeilleure.Translation.Cache
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveFunctionTableHandler(nint codeCachePointer)
|
||||
{
|
||||
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
|
||||
|
||||
bool result;
|
||||
|
||||
unsafe
|
||||
{
|
||||
result = RtlDeleteFunctionTable(codeCachePtr | 3);
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
throw new InvalidOperationException("Failure removing function table callback.");
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
|
||||
{
|
||||
int offset = (int)((long)controlPc - context.ToInt64());
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
class DelegateInfo
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
delegate void DispatcherFunction(nint nativeContext, ulong startAddress);
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
delegate ulong GuestFunction(nint nativeContextPtr);
|
||||
|
@ -144,17 +144,15 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
public List<ulong> GetBlacklistedFunctions()
|
||||
{
|
||||
List<ulong> funcs = new List<ulong>();
|
||||
List<ulong> funcs = [];
|
||||
|
||||
foreach (var profiledFunc in ProfiledFuncs)
|
||||
foreach ((ulong ptr, FuncProfile funcProfile) in ProfiledFuncs)
|
||||
{
|
||||
if (profiledFunc.Value.Blacklist)
|
||||
{
|
||||
if (!funcs.Contains(profiledFunc.Key))
|
||||
{
|
||||
funcs.Add(profiledFunc.Key);
|
||||
}
|
||||
}
|
||||
if (!funcProfile.Blacklist)
|
||||
continue;
|
||||
|
||||
if (!funcs.Contains(ptr))
|
||||
funcs.Add(ptr);
|
||||
}
|
||||
|
||||
return funcs;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using ARMeilleure.Common;
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ using ARMeilleure.Diagnostics;
|
||||
using ARMeilleure.Instructions;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Signal;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
|
@ -4,7 +4,6 @@ using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
|
@ -4,7 +4,6 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
|
@ -4,7 +4,6 @@ using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.Common
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using CpuAddress = System.UInt64;
|
||||
using DspAddress = System.UInt64;
|
||||
|
@ -6,9 +6,7 @@ namespace Ryujinx.Common.Configuration
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||
public enum GraphicsBackend
|
||||
{
|
||||
Auto,
|
||||
Vulkan,
|
||||
OpenGl,
|
||||
Metal
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Microsoft.Win32;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
|
118
src/Ryujinx.Common/Helpers/Patterns.cs
Normal file
118
src/Ryujinx.Common/Helpers/Patterns.cs
Normal file
@ -0,0 +1,118 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ryujinx.Common.Helper
|
||||
{
|
||||
public static partial class Patterns
|
||||
{
|
||||
#region Accessors
|
||||
|
||||
public static readonly Regex Numeric = NumericRegex();
|
||||
|
||||
public static readonly Regex AmdGcn = AmdGcnRegex();
|
||||
public static readonly Regex NvidiaConsumerClass = NvidiaConsumerClassRegex();
|
||||
|
||||
public static readonly Regex DomainLp1Ns = DomainLp1NsRegex();
|
||||
public static readonly Regex DomainLp1Lp1Npln = DomainLp1Lp1NplnRegex();
|
||||
public static readonly Regex DomainLp1Znc = DomainLp1ZncRegex();
|
||||
public static readonly Regex DomainSbApi = DomainSbApiRegex();
|
||||
public static readonly Regex DomainSbAccounts = DomainSbAccountsRegex();
|
||||
public static readonly Regex DomainAccounts = DomainAccountsRegex();
|
||||
|
||||
public static readonly Regex Module = ModuleRegex();
|
||||
public static readonly Regex FsSdk = FsSdkRegex();
|
||||
public static readonly Regex SdkMw = SdkMwRegex();
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static readonly Regex CJK = CJKRegex();
|
||||
|
||||
public static readonly Regex LdnPassphrase = LdnPassphraseRegex();
|
||||
|
||||
public static readonly Regex CleanText = CleanTextRegex();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generated pattern stubs
|
||||
|
||||
#region Numeric validation
|
||||
|
||||
[GeneratedRegex("[0-9]|.")]
|
||||
internal static partial Regex NumericRegex();
|
||||
|
||||
#endregion
|
||||
|
||||
#region GPU names
|
||||
|
||||
[GeneratedRegex(
|
||||
"Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")]
|
||||
internal static partial Regex AmdGcnRegex();
|
||||
|
||||
[GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")]
|
||||
internal static partial Regex NvidiaConsumerClassRegex();
|
||||
|
||||
#endregion
|
||||
|
||||
#region DNS blocking
|
||||
|
||||
public static readonly Regex[] BlockedHosts =
|
||||
[
|
||||
DomainLp1Ns,
|
||||
DomainLp1Lp1Npln,
|
||||
DomainLp1Znc,
|
||||
DomainSbApi,
|
||||
DomainSbAccounts,
|
||||
DomainAccounts
|
||||
];
|
||||
|
||||
const RegexOptions DnsRegexOpts =
|
||||
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
|
||||
|
||||
[GeneratedRegex(@"^(.*)\-lp1\.(n|s)\.n\.srv\.nintendo\.net$", DnsRegexOpts)]
|
||||
internal static partial Regex DomainLp1NsRegex();
|
||||
|
||||
[GeneratedRegex(@"^(.*)\-lp1\.lp1\.t\.npln\.srv\.nintendo\.net$", DnsRegexOpts)]
|
||||
internal static partial Regex DomainLp1Lp1NplnRegex();
|
||||
|
||||
[GeneratedRegex(@"^(.*)\-lp1\.(znc|p)\.srv\.nintendo\.net$", DnsRegexOpts)]
|
||||
internal static partial Regex DomainLp1ZncRegex();
|
||||
|
||||
[GeneratedRegex(@"^(.*)\-sb\-api\.accounts\.nintendo\.com$", DnsRegexOpts)]
|
||||
internal static partial Regex DomainSbApiRegex();
|
||||
|
||||
[GeneratedRegex(@"^(.*)\-sb\.accounts\.nintendo\.com$", DnsRegexOpts)]
|
||||
internal static partial Regex DomainSbAccountsRegex();
|
||||
|
||||
[GeneratedRegex(@"^accounts\.nintendo\.com$", DnsRegexOpts)]
|
||||
internal static partial Regex DomainAccountsRegex();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Executable information
|
||||
|
||||
[GeneratedRegex(@"[a-z]:[\\/][ -~]{5,}\.nss", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||
internal static partial Regex ModuleRegex();
|
||||
|
||||
[GeneratedRegex(@"sdk_version: ([0-9.]*)")]
|
||||
internal static partial Regex FsSdkRegex();
|
||||
|
||||
[GeneratedRegex(@"SDK MW[ -~]*")]
|
||||
internal static partial Regex SdkMwRegex();
|
||||
|
||||
#endregion
|
||||
|
||||
#region CJK
|
||||
|
||||
[GeneratedRegex(
|
||||
"\\p{IsHangulJamo}|\\p{IsCJKRadicalsSupplement}|\\p{IsCJKSymbolsandPunctuation}|\\p{IsEnclosedCJKLettersandMonths}|\\p{IsCJKCompatibility}|\\p{IsCJKUnifiedIdeographsExtensionA}|\\p{IsCJKUnifiedIdeographs}|\\p{IsHangulSyllables}|\\p{IsCJKCompatibilityForms}")]
|
||||
private static partial Regex CJKRegex();
|
||||
|
||||
#endregion
|
||||
|
||||
[GeneratedRegex("Ryujinx-[0-9a-f]{8}")]
|
||||
private static partial Regex LdnPassphraseRegex();
|
||||
|
||||
[GeneratedRegex(@"[^\u0000\u0009\u000A\u000D\u0020-\uFFFF]..")]
|
||||
private static partial Regex CleanTextRegex();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -10,14 +10,18 @@ namespace Ryujinx.Common.Helper
|
||||
public static bool IsMacOS => OperatingSystem.IsMacOS();
|
||||
public static bool IsWindows => OperatingSystem.IsWindows();
|
||||
public static bool IsLinux => OperatingSystem.IsLinux();
|
||||
|
||||
public static bool IsArm => RuntimeInformation.OSArchitecture is Architecture.Arm64;
|
||||
|
||||
public static bool IsX64 => RuntimeInformation.OSArchitecture is Architecture.X64;
|
||||
|
||||
public static bool IsIntelMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.X64;
|
||||
public static bool IsArmMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.Arm64;
|
||||
public static bool IsIntelMac => IsMacOS && IsX64;
|
||||
public static bool IsArmMac => IsMacOS && IsArm;
|
||||
|
||||
public static bool IsX64Windows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.X64);
|
||||
public static bool IsArmWindows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
|
||||
public static bool IsX64Windows => IsWindows && IsX64;
|
||||
public static bool IsArmWindows => IsWindows && IsArm;
|
||||
|
||||
public static bool IsX64Linux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.X64);
|
||||
public static bool IsArmLinux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
|
||||
public static bool IsX64Linux => IsLinux && IsX64;
|
||||
public static bool IsArmLinux => IsLinux && IsArmMac;
|
||||
}
|
||||
}
|
||||
|
9
src/Ryujinx.Common/SharedConstants.cs
Normal file
9
src/Ryujinx.Common/SharedConstants.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class SharedConstants
|
||||
{
|
||||
public const string DefaultLanPlayHost = "ryuldn.vudjun.com";
|
||||
public const short LanPlayPort = 30456;
|
||||
public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Helper;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
@ -11,54 +10,6 @@ namespace Ryujinx.Common
|
||||
{
|
||||
public static ReactiveObject<Optional<string>> CurrentApplication { get; } = new();
|
||||
|
||||
public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend)
|
||||
{
|
||||
switch (currentBackend)
|
||||
{
|
||||
case GraphicsBackend.Metal when !OperatingSystem.IsMacOS():
|
||||
case GraphicsBackend.OpenGl when OperatingSystem.IsMacOS():
|
||||
return GraphicsBackend.Vulkan;
|
||||
case GraphicsBackend.Vulkan or GraphicsBackend.OpenGl or GraphicsBackend.Metal:
|
||||
return currentBackend;
|
||||
}
|
||||
|
||||
if (!RunningPlatform.IsArmMac)
|
||||
return GraphicsBackend.Vulkan;
|
||||
|
||||
return GreatMetalTitles.ContainsIgnoreCase(titleId) ? GraphicsBackend.Metal : GraphicsBackend.Vulkan;
|
||||
}
|
||||
|
||||
public static readonly string[] GreatMetalTitles =
|
||||
[
|
||||
"01009b500007c000", // ARMS
|
||||
"0100a5c00d162000", // Cuphead
|
||||
"010023800d64a000", // Deltarune
|
||||
"01003a30012c0000", // LEGO City Undercover
|
||||
"010048701995e000", // Luigi's Manion 2 HD
|
||||
"010028600EBDA000", // Mario 3D World
|
||||
"0100152000022000", // Mario Kart 8 Deluxe
|
||||
"010075a016a3a000", // Persona 4 Arena Ultimax
|
||||
"0100187003A36000", // Pokémon: Let's Go, Eevee!
|
||||
"010003f003a34000", // Pokémon: Let's Go, Pikachu!
|
||||
"01008C0016544000", // Sea of Stars
|
||||
"01006A800016E000", // Smash Ultimate
|
||||
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
|
||||
|
||||
// These ones have small issues, but those happen on Vulkan as well:
|
||||
"01006f8002326000", // Animal Crossings: New Horizons
|
||||
"01009bf0072d4000", // Captain Toad: Treasure Tracker
|
||||
"01009510001ca000", // Fast RMX
|
||||
"01005CA01580E000", // Persona 5 Royale
|
||||
"010015100b514000", // Super Mario Bros. Wonder
|
||||
"0100000000010000", // Super Mario Odyssey
|
||||
|
||||
// Further testing is appreciated, I did not test the entire game:
|
||||
"01007300020fa000", // Astral Chain
|
||||
"010076f0049a2000", // Bayonetta
|
||||
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
|
||||
"0100f4300bf2c000", // New Pokemon Snap
|
||||
];
|
||||
|
||||
public static string GetDiscordGameAsset(string titleId)
|
||||
=> DiscordGameAssetKeys.Contains(titleId) ? titleId : "game";
|
||||
|
||||
@ -164,15 +115,16 @@ namespace Ryujinx.Common
|
||||
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
|
||||
|
||||
//NSO Membership games
|
||||
"0100ccf019c8c000", // F-ZERO 99
|
||||
"0100c62011050000", // GB - Nintendo Switch Online
|
||||
"010012f017576000", // GBA - Nintendo Switch Online
|
||||
"0100c9a00ece6000", // N64 - Nintendo Switch Online
|
||||
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
|
||||
"0100d870045b6000", // NES - Nintendo Switch Online
|
||||
"0100b3c014bda000", // SEGA Genesis - Nintendo Switch Online
|
||||
"01008d300c50c000", // SNES - Nintendo Switch Online
|
||||
"0100ccf019c8c000", // F-ZERO 99
|
||||
"0100ad9012510000", // PAC-MAN 99
|
||||
"010040600c5ce000", // Tetris 99
|
||||
"01008d300c50c000", // SNES - Nintendo Switch Online
|
||||
"0100277011f1a000", // Super Mario Bros. 35
|
||||
|
||||
//Misc Nintendo 1st party games
|
||||
@ -218,6 +170,7 @@ namespace Ryujinx.Common
|
||||
//Misc Games
|
||||
"010056e00853a000", // A Hat in Time
|
||||
"0100fd1014726000", // Baldurs Gate: Dark Alliance
|
||||
"01008c2019598000", // Bluey: The Video Game
|
||||
"0100c6800b934000", // Brawlhalla
|
||||
"0100dbf01000a000", // Burnout Paradise Remastered
|
||||
"0100744001588000", // Cars 3: Driven to Win
|
||||
@ -228,6 +181,7 @@ namespace Ryujinx.Common
|
||||
"01008c8012920000", // Dying Light Platinum Edition
|
||||
"01001cc01b2d4000", // Goat Simulator 3
|
||||
"01003620068ea000", // Hand of Fate 2
|
||||
"0100f7e00c70e000", // Hogwarts Legacy
|
||||
"010085500130a000", // Lego City: Undercover
|
||||
"010073c01af34000", // LEGO Horizon Adventures
|
||||
"0100d71004694000", // Minecraft
|
||||
|
@ -1,6 +1,5 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
|
@ -5,7 +5,6 @@ using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
|
@ -2,7 +2,6 @@ using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Memory;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
@ -8,7 +8,6 @@ using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
|
@ -3,7 +3,6 @@ using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.Arm32;
|
||||
using Ryujinx.Cpu.LightningJit.Arm64;
|
||||
using Ryujinx.Cpu.LightningJit.State;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit
|
||||
|
@ -2,7 +2,6 @@ using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
|
@ -1,5 +1,3 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
{
|
||||
static class InstEmitVfpMove
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm64
|
||||
{
|
||||
static class SysUtils
|
||||
|
@ -3,7 +3,6 @@ using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using Ryujinx.Cpu.LightningJit.Graph;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
@ -1,4 +1,6 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Humanizer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -15,9 +17,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
private static readonly int _pageMask = _pageSize - 1;
|
||||
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int CacheSize = 2047 * 1024 * 1024;
|
||||
private const int CacheSize = 256 * 1024 * 1024;
|
||||
|
||||
private static ReservedRegion _jitRegion;
|
||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||
|
||||
private static CacheMemoryAllocator _cacheAllocator;
|
||||
@ -26,6 +27,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
|
||||
private static readonly Lock _lock = new();
|
||||
private static bool _initialized;
|
||||
private static readonly List<ReservedRegion> _jitRegions = [];
|
||||
private static int _activeRegionIndex = 0;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
@ -45,7 +48,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
return;
|
||||
}
|
||||
|
||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
||||
ReservedRegion firstRegion = new(allocator, CacheSize);
|
||||
_jitRegions.Add(firstRegion);
|
||||
_activeRegionIndex = 0;
|
||||
|
||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
@ -65,8 +70,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = Allocate(code.Length);
|
||||
|
||||
nint funcPtr = _jitRegion.Pointer + funcOffset;
|
||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||
nint funcPtr = targetRegion.Pointer + funcOffset;
|
||||
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@ -80,18 +85,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
}
|
||||
else
|
||||
{
|
||||
ReprotectAsWritable(funcOffset, code.Length);
|
||||
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
||||
ReprotectAsExecutable(funcOffset, code.Length);
|
||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||
Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
|
||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||
|
||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
|
||||
}
|
||||
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
|
||||
}
|
||||
|
||||
Add(funcOffset, code.Length);
|
||||
@ -106,50 +104,80 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
foreach (ReservedRegion region in _jitRegions)
|
||||
{
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
||||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReprotectAsWritable(int offset, int size)
|
||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static void ReprotectAsExecutable(int offset, int size)
|
||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static int Allocate(int codeSize)
|
||||
{
|
||||
codeSize = AlignCodeSize(codeSize);
|
||||
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset < 0)
|
||||
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
||||
{
|
||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset >= 0)
|
||||
{
|
||||
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
_activeRegionIndex = i;
|
||||
return allocOffset;
|
||||
}
|
||||
}
|
||||
|
||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
int exhaustedRegion = _activeRegionIndex;
|
||||
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
|
||||
_jitRegions.Add(newRegion);
|
||||
_activeRegionIndex = _jitRegions.Count - 1;
|
||||
|
||||
int newRegionNumber = _activeRegionIndex;
|
||||
|
||||
return allocOffset;
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
|
||||
if (allocOffsetNew < 0)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||
}
|
||||
|
||||
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
|
||||
return allocOffsetNew;
|
||||
}
|
||||
|
||||
private static int AlignCodeSize(int codeSize)
|
||||
|
@ -1,5 +1,4 @@
|
||||
using ARMeilleure.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
|
@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
{
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int SharedCacheSize = 2047 * 1024 * 1024;
|
||||
private const int LocalCacheSize = 128 * 1024 * 1024;
|
||||
private const int LocalCacheSize = 256 * 1024 * 1024;
|
||||
|
||||
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
|
||||
// and allow the guest to take the fast path.
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit
|
||||
{
|
||||
class TranslatedFunction
|
||||
|
@ -5,7 +5,6 @@ using Ryujinx.Cpu.LightningJit.Cache;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using Ryujinx.Cpu.LightningJit.State;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Signal
|
||||
|
@ -1,6 +1,4 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Device
|
||||
{
|
||||
|
@ -1,18 +0,0 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public readonly struct ComputeSize
|
||||
{
|
||||
public readonly static ComputeSize VtgAsCompute = new(32, 32, 1);
|
||||
|
||||
public readonly int X;
|
||||
public readonly int Y;
|
||||
public readonly int Z;
|
||||
|
||||
public ComputeSize(int x, int y, int z)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
}
|
||||
}
|
||||
}
|
@ -339,84 +339,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get bytes per element for this format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>Byte size for an element of this format (pixel, vertex attribute, etc)</returns>
|
||||
public static int GetBytesPerElement(this Format format)
|
||||
{
|
||||
int scalarSize = format.GetScalarSize();
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case Format.R8G8Unorm:
|
||||
case Format.R8G8Snorm:
|
||||
case Format.R8G8Uint:
|
||||
case Format.R8G8Sint:
|
||||
case Format.R8G8Uscaled:
|
||||
case Format.R8G8Sscaled:
|
||||
case Format.R16G16Float:
|
||||
case Format.R16G16Unorm:
|
||||
case Format.R16G16Snorm:
|
||||
case Format.R16G16Uint:
|
||||
case Format.R16G16Sint:
|
||||
case Format.R16G16Uscaled:
|
||||
case Format.R16G16Sscaled:
|
||||
case Format.R32G32Float:
|
||||
case Format.R32G32Uint:
|
||||
case Format.R32G32Sint:
|
||||
case Format.R32G32Uscaled:
|
||||
case Format.R32G32Sscaled:
|
||||
return 2 * scalarSize;
|
||||
|
||||
case Format.R8G8B8Unorm:
|
||||
case Format.R8G8B8Snorm:
|
||||
case Format.R8G8B8Uint:
|
||||
case Format.R8G8B8Sint:
|
||||
case Format.R8G8B8Uscaled:
|
||||
case Format.R8G8B8Sscaled:
|
||||
case Format.R16G16B16Float:
|
||||
case Format.R16G16B16Unorm:
|
||||
case Format.R16G16B16Snorm:
|
||||
case Format.R16G16B16Uint:
|
||||
case Format.R16G16B16Sint:
|
||||
case Format.R16G16B16Uscaled:
|
||||
case Format.R16G16B16Sscaled:
|
||||
case Format.R32G32B32Float:
|
||||
case Format.R32G32B32Uint:
|
||||
case Format.R32G32B32Sint:
|
||||
case Format.R32G32B32Uscaled:
|
||||
case Format.R32G32B32Sscaled:
|
||||
return 3 * scalarSize;
|
||||
|
||||
case Format.R8G8B8A8Unorm:
|
||||
case Format.R8G8B8A8Snorm:
|
||||
case Format.R8G8B8A8Uint:
|
||||
case Format.R8G8B8A8Sint:
|
||||
case Format.R8G8B8A8Srgb:
|
||||
case Format.R8G8B8A8Uscaled:
|
||||
case Format.R8G8B8A8Sscaled:
|
||||
case Format.B8G8R8A8Unorm:
|
||||
case Format.B8G8R8A8Srgb:
|
||||
case Format.R16G16B16A16Float:
|
||||
case Format.R16G16B16A16Unorm:
|
||||
case Format.R16G16B16A16Snorm:
|
||||
case Format.R16G16B16A16Uint:
|
||||
case Format.R16G16B16A16Sint:
|
||||
case Format.R16G16B16A16Uscaled:
|
||||
case Format.R16G16B16A16Sscaled:
|
||||
case Format.R32G32B32A32Float:
|
||||
case Format.R32G32B32A32Uint:
|
||||
case Format.R32G32B32A32Sint:
|
||||
case Format.R32G32B32A32Uscaled:
|
||||
case Format.R32G32B32A32Sscaled:
|
||||
return 4 * scalarSize;
|
||||
}
|
||||
|
||||
return scalarSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is a depth or depth-stencil format.
|
||||
/// </summary>
|
||||
|
@ -4,22 +4,23 @@ namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public int FragmentOutputMap { get; }
|
||||
public ResourceLayout ResourceLayout { get; }
|
||||
public ComputeSize ComputeLocalSize { get; }
|
||||
public ProgramPipelineState? State { get; }
|
||||
public bool FromCache { get; set; }
|
||||
|
||||
public ShaderInfo(
|
||||
int fragmentOutputMap,
|
||||
ResourceLayout resourceLayout,
|
||||
ComputeSize computeLocalSize,
|
||||
ProgramPipelineState? state,
|
||||
bool fromCache = false)
|
||||
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false)
|
||||
{
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
ResourceLayout = resourceLayout;
|
||||
ComputeLocalSize = computeLocalSize;
|
||||
State = state;
|
||||
FromCache = fromCache;
|
||||
}
|
||||
|
||||
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false)
|
||||
{
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
ResourceLayout = resourceLayout;
|
||||
State = null;
|
||||
FromCache = fromCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
|
@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
/// </summary>
|
||||
class VtgAsComputeContext : IDisposable
|
||||
{
|
||||
private const int DummyBufferSize = 16;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
/// <summary>
|
||||
@ -46,7 +48,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
format.GetBytesPerElement(),
|
||||
1,
|
||||
format,
|
||||
DepthStencilMode.Depth,
|
||||
Target.TextureBuffer,
|
||||
@ -519,6 +521,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
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>
|
||||
/// Gets the range for a sequential index buffer, with ever incrementing index values.
|
||||
/// </summary>
|
||||
|
@ -147,6 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -162,12 +163,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||
continue;
|
||||
}
|
||||
|
||||
int vbStride = vertexBuffer.UnpackStride();
|
||||
ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count);
|
||||
|
||||
ulong oldVbSize = vbSize;
|
||||
|
||||
ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset();
|
||||
int componentSize = format.GetScalarSize();
|
||||
|
||||
@ -340,6 +344,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
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>
|
||||
/// Binds a vertex buffer into a buffer texture.
|
||||
/// </summary>
|
||||
|
@ -913,7 +913,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
Span<Rectangle<int>> scissors =
|
||||
[
|
||||
new Rectangle<int>(scissorX, scissorY, scissorW, scissorH)
|
||||
new(scissorX, scissorY, scissorW, scissorH)
|
||||
];
|
||||
|
||||
_context.Renderer.Pipeline.SetScissors(scissors);
|
||||
|
@ -1,4 +1,3 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -4,7 +4,6 @@ using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
|
@ -7,7 +7,6 @@ using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -1,6 +1,5 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -2,7 +2,6 @@ using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
@ -324,11 +324,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
bool loadHostCache = header.CodeGenVersion == CodeGenVersion;
|
||||
|
||||
if (context.Capabilities.Api == TargetApi.Metal)
|
||||
{
|
||||
loadHostCache = false;
|
||||
}
|
||||
|
||||
int programIndex = 0;
|
||||
|
||||
DataEntry entry = new();
|
||||
@ -397,8 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
context,
|
||||
shaders,
|
||||
specState.PipelineState,
|
||||
specState.TransformFeedbackDescriptors != null,
|
||||
specState.ComputeState.GetLocalSize());
|
||||
specState.TransformFeedbackDescriptors != null);
|
||||
|
||||
IProgram hostProgram;
|
||||
|
||||
@ -635,10 +629,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Capabilities.Api != TargetApi.Metal)
|
||||
{
|
||||
WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
|
||||
}
|
||||
WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,4 +1,3 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
@ -367,9 +366,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_context.Capabilities.Api == TargetApi.Metal && _context.DirtyHacks.IsEnabled(DirtyHack.ShaderTranslationDelay))
|
||||
Thread.Sleep(_context.DirtyHacks[DirtyHack.ShaderTranslationDelay]);
|
||||
|
||||
AsyncProgramTranslation asyncTranslation = new(guestShaders, specState, programIndex, isCompute);
|
||||
_asyncTranslationQueue.Add(asyncTranslation, _cancellationToken);
|
||||
}
|
||||
@ -494,12 +490,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
|
||||
|
||||
ref GpuChannelComputeState computeState = ref compilation.SpecializationState.ComputeState;
|
||||
|
||||
ShaderInfoBuilder shaderInfoBuilder = new(
|
||||
_context,
|
||||
compilation.SpecializationState.TransformFeedbackDescriptors != null,
|
||||
computeLocalSize: computeState.GetLocalSize());
|
||||
ShaderInfoBuilder shaderInfoBuilder = new(_context, compilation.SpecializationState.TransformFeedbackDescriptors != null);
|
||||
|
||||
for (int index = 0; index < compilation.TranslatedStages.Length; index++)
|
||||
{
|
||||
@ -729,7 +720,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
ShaderProgram program = translatorContext.Translate();
|
||||
|
||||
CachedShaderStage[] shaders = [new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data)];
|
||||
CachedShaderStage[] shaders = [new(program.Info, shader.Code, shader.Cb1Data)];
|
||||
|
||||
_compilationQueue.Enqueue(new ProgramCompilation([program], shaders, newSpecState, programIndex, isCompute: true));
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private readonly GpuAccessorState _state;
|
||||
private readonly int _stageIndex;
|
||||
private readonly bool _compute;
|
||||
private readonly bool _isOpenGL;
|
||||
private readonly bool _isVulkan;
|
||||
private readonly bool _hasGeometryShader;
|
||||
private readonly bool _supportsQuads;
|
||||
|
||||
@ -39,7 +39,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_stageIndex = stageIndex;
|
||||
_isOpenGL = context.Capabilities.Api == TargetApi.OpenGL;
|
||||
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
||||
_hasGeometryShader = hasGeometryShader;
|
||||
_supportsQuads = context.Capabilities.SupportsQuads;
|
||||
|
||||
@ -117,10 +117,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
public GpuGraphicsState QueryGraphicsState()
|
||||
{
|
||||
return _state.GraphicsState.CreateShaderGraphicsState(
|
||||
_isOpenGL,
|
||||
!_isVulkan,
|
||||
_supportsQuads,
|
||||
_hasGeometryShader,
|
||||
!_isOpenGL || _state.GraphicsState.YNegateEnabled);
|
||||
_isVulkan || _state.GraphicsState.YNegateEnabled);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -55,7 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
int binding;
|
||||
|
||||
if (_context.Capabilities.Api != TargetApi.OpenGL)
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer");
|
||||
}
|
||||
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
int binding;
|
||||
|
||||
if (_context.Capabilities.Api != TargetApi.OpenGL)
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
if (count == 1)
|
||||
{
|
||||
@ -103,7 +103,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
int binding;
|
||||
|
||||
if (_context.Capabilities.Api != TargetApi.OpenGL)
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
|
||||
}
|
||||
@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
int binding;
|
||||
|
||||
if (_context.Capabilities.Api != TargetApi.OpenGL)
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
if (count == 1)
|
||||
{
|
||||
|
@ -1,5 +1,3 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
@ -63,14 +61,5 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
SharedMemorySize = sharedMemorySize;
|
||||
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local group size of the shader in a GAL compatible struct.
|
||||
/// </summary>
|
||||
/// <returns>Local group size</returns>
|
||||
public ComputeSize GetLocalSize()
|
||||
{
|
||||
return new ComputeSize(LocalSizeX, LocalSizeY, LocalSizeZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
@ -118,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private static string GetDiskCachePath()
|
||||
{
|
||||
return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
|
||||
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId.ToLower(), "cache", "shader")
|
||||
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader")
|
||||
: null;
|
||||
}
|
||||
|
||||
@ -205,7 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
GpuChannelComputeState computeState,
|
||||
ulong gpuVa)
|
||||
{
|
||||
if (_cpPrograms.TryGetValue(gpuVa, out CachedShaderProgram cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa))
|
||||
if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa))
|
||||
{
|
||||
return cpShader;
|
||||
}
|
||||
@ -224,11 +223,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
|
||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false);
|
||||
|
||||
ShaderSource[] shaderSourcesArray = [CreateShaderSource(translatedShader.Program)];
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(
|
||||
_context,
|
||||
translatedShader.Program.Info,
|
||||
computeState.GetLocalSize());
|
||||
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
|
||||
|
||||
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
|
||||
@ -255,8 +251,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
channel.TextureManager.UpdateRenderTargets();
|
||||
|
||||
RtControl rtControl = state.RtControl;
|
||||
TextureMsaaMode msaaMode = state.RtMsaaMode;
|
||||
var rtControl = state.RtControl;
|
||||
var msaaMode = state.RtMsaaMode;
|
||||
|
||||
pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY();
|
||||
|
||||
@ -266,7 +262,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
int rtIndex = rtControl.UnpackPermutationIndex(index);
|
||||
|
||||
RtColorState colorState = state.RtColorState[rtIndex];
|
||||
var colorState = state.RtColorState[rtIndex];
|
||||
|
||||
if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0)
|
||||
{
|
||||
@ -311,12 +307,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
ref GpuChannelGraphicsState graphicsState,
|
||||
ShaderAddresses addresses)
|
||||
{
|
||||
if (_gpPrograms.TryGetValue(addresses, out CachedShaderProgram gpShaders) && IsShaderEqual(channel, ref poolState, ref graphicsState, gpShaders, addresses))
|
||||
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, ref poolState, ref graphicsState, gpShaders, addresses))
|
||||
{
|
||||
return gpShaders;
|
||||
}
|
||||
|
||||
if (_graphicsShaderCache.TryFind(channel, ref poolState, ref graphicsState, addresses, out gpShaders, out CachedGraphicsGuestCode cachedGuestCode))
|
||||
if (_graphicsShaderCache.TryFind(channel, ref poolState, ref graphicsState, addresses, out gpShaders, out var cachedGuestCode))
|
||||
{
|
||||
_gpPrograms[addresses] = gpShaders;
|
||||
return gpShaders;
|
||||
@ -369,7 +365,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore);
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
|
||||
List<ShaderSource> shaderSources = [];
|
||||
List<ShaderSource> shaderSources = new();
|
||||
|
||||
TranslatorContext previousStage = null;
|
||||
ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute);
|
||||
@ -429,8 +425,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
|
||||
|
||||
(program, ShaderProgramInfo vacInfo) = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
|
||||
infoBuilder.AddStageInfoVac(vacInfo);
|
||||
program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -535,9 +530,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled)
|
||||
{
|
||||
ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language);
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, context.GetVertexAsComputeInfo(), tfEnabled);
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled);
|
||||
|
||||
return new(_context.Renderer.CreateProgram([source], info), program.Info, context.GetResourceReservations());
|
||||
return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -587,7 +582,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++)
|
||||
{
|
||||
TfState tf = state.TfState[i];
|
||||
var tf = state.TfState[i];
|
||||
|
||||
descs[i] = new TransformFeedbackDescriptor(
|
||||
tf.BufferIndex,
|
||||
@ -693,7 +688,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <returns>The generated translator context</returns>
|
||||
public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa)
|
||||
{
|
||||
TranslationOptions options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
|
||||
var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
|
||||
return Translator.CreateContext(gpuVa, gpuAccessor, options);
|
||||
}
|
||||
|
||||
@ -710,7 +705,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <returns>The generated translator context</returns>
|
||||
public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa)
|
||||
{
|
||||
TranslationOptions options = CreateTranslationOptions(api, flags);
|
||||
var options = CreateTranslationOptions(api, flags);
|
||||
return Translator.CreateContext(gpuVa, gpuAccessor, options);
|
||||
}
|
||||
|
||||
@ -736,7 +731,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||
|
||||
MemoryManager memoryManager = channel.MemoryManager;
|
||||
var memoryManager = channel.MemoryManager;
|
||||
|
||||
codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
|
||||
codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
|
||||
@ -774,7 +769,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <returns>Compiled graphics shader code</returns>
|
||||
private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code, bool asCompute)
|
||||
{
|
||||
MemoryManager memoryManager = channel.MemoryManager;
|
||||
var memoryManager = channel.MemoryManager;
|
||||
|
||||
ulong cb1DataAddress = context.Stage == ShaderStage.Compute
|
||||
? channel.BufferManager.GetComputeUniformBufferAddress(1)
|
||||
@ -802,7 +797,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
if (address == MemoryManager.PteUnmapped || size == 0)
|
||||
{
|
||||
return [];
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
return memoryManager.Physical.GetSpan(address, size).ToArray();
|
||||
@ -827,20 +822,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
/// <summary>
|
||||
/// Creates shader translation options with the requested graphics API and flags.
|
||||
/// The shader language is chosen based on the current configuration and graphics API.
|
||||
/// The shader language is choosen based on the current configuration and graphics API.
|
||||
/// </summary>
|
||||
/// <param name="api">Target graphics API</param>
|
||||
/// <param name="flags">Translation flags</param>
|
||||
/// <returns>Translation options</returns>
|
||||
private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
|
||||
{
|
||||
TargetLanguage lang = api switch
|
||||
{
|
||||
TargetApi.OpenGL => TargetLanguage.Glsl,
|
||||
TargetApi.Vulkan => GraphicsConfig.EnableSpirvCompilationOnVulkan ? TargetLanguage.Spirv : TargetLanguage.Glsl,
|
||||
TargetApi.Metal => TargetLanguage.Msl,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
|
||||
? TargetLanguage.Spirv
|
||||
: TargetLanguage.Glsl;
|
||||
|
||||
return new TranslationOptions(lang, api, flags);
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
ResourceStages.Geometry;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly ComputeSize _computeLocalSize;
|
||||
|
||||
private int _fragmentOutputMap;
|
||||
|
||||
@ -40,11 +39,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="context">GPU context that owns the shaders that will be added to the builder</param>
|
||||
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
|
||||
/// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param>
|
||||
/// <param name="computeLocalSize">Indicates the local thread size for a compute shader</param>
|
||||
public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false, ComputeSize computeLocalSize = default)
|
||||
public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false)
|
||||
{
|
||||
_context = context;
|
||||
_computeLocalSize = computeLocalSize;
|
||||
|
||||
_fragmentOutputMap = -1;
|
||||
|
||||
@ -98,7 +95,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
|
||||
{
|
||||
AddDescriptor(stages, type, setIndex, start, count);
|
||||
// AddUsage(stages, type, setIndex, start, count, write);
|
||||
AddUsage(stages, type, setIndex, start, count, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -162,25 +159,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
AddUsage(info.Images, stages, isImage: true);
|
||||
}
|
||||
|
||||
public void AddStageInfoVac(ShaderProgramInfo info)
|
||||
{
|
||||
ResourceStages stages = info.Stage switch
|
||||
{
|
||||
ShaderStage.Compute => ResourceStages.Compute,
|
||||
ShaderStage.Vertex => ResourceStages.Vertex,
|
||||
ShaderStage.TessellationControl => ResourceStages.TessellationControl,
|
||||
ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation,
|
||||
ShaderStage.Geometry => ResourceStages.Geometry,
|
||||
ShaderStage.Fragment => ResourceStages.Fragment,
|
||||
_ => ResourceStages.None,
|
||||
};
|
||||
|
||||
AddUsage(info.CBuffers, stages, isStorage: false);
|
||||
AddUsage(info.SBuffers, stages, isStorage: true);
|
||||
AddUsage(info.Textures, stages, isImage: false);
|
||||
AddUsage(info.Images, stages, isImage: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a resource descriptor to the list of descriptors.
|
||||
/// </summary>
|
||||
@ -383,7 +361,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
ResourceLayout resourceLayout = new(descriptors.AsReadOnly(), usages.AsReadOnly());
|
||||
|
||||
return new ShaderInfo(_fragmentOutputMap, resourceLayout, _computeLocalSize, pipeline, fromCache);
|
||||
if (pipeline.HasValue)
|
||||
{
|
||||
return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -393,16 +378,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="programs">Shaders from the disk cache</param>
|
||||
/// <param name="pipeline">Optional pipeline for background compilation</param>
|
||||
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
|
||||
/// <param name="computeLocalSize">Compute local thread size</param>
|
||||
/// <returns>Shader information</returns>
|
||||
public static ShaderInfo BuildForCache(
|
||||
GpuContext context,
|
||||
IEnumerable<CachedShaderStage> programs,
|
||||
ProgramPipelineState? pipeline,
|
||||
bool tfEnabled,
|
||||
ComputeSize computeLocalSize)
|
||||
bool tfEnabled)
|
||||
{
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled, computeLocalSize: computeLocalSize);
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled);
|
||||
|
||||
foreach (CachedShaderStage program in programs)
|
||||
{
|
||||
@ -420,12 +403,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that owns the shader</param>
|
||||
/// <param name="info">Compute shader information</param>
|
||||
/// <param name="computeLocalSize">Compute local thread size</param>
|
||||
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
|
||||
/// <returns>Shader information</returns>
|
||||
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, ComputeSize computeLocalSize, bool fromCache = false)
|
||||
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
|
||||
{
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false, computeLocalSize: computeLocalSize);
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false);
|
||||
|
||||
builder.AddStageInfo(info);
|
||||
|
||||
@ -440,11 +422,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
|
||||
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
|
||||
/// <returns>Shader information</returns>
|
||||
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, ShaderProgramInfo info2, bool tfEnabled, bool fromCache = false)
|
||||
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false)
|
||||
{
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true, computeLocalSize: ComputeSize.VtgAsCompute);
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true);
|
||||
|
||||
builder.AddStageInfoVac(info2);
|
||||
builder.AddStageInfo(info, vertexAsCompute: true);
|
||||
|
||||
return builder.Build(null, fromCache);
|
||||
|
@ -87,7 +87,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||
}
|
||||
|
||||
using ManualResetEvent waitEvent = new(false);
|
||||
SyncpointWaiterHandle info = _syncpoints[id].RegisterCallback(threshold, (x) => waitEvent.Set());
|
||||
SyncpointWaiterHandle info = _syncpoints[id].RegisterCallback(threshold, _ => waitEvent.Set());
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||
|
||||
bool signaled = waitEvent.WaitOne(timeout);
|
||||
|
||||
if (!signaled && info != null)
|
||||
if (!signaled)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Wait on syncpoint {id} for threshold {threshold} took more than {timeout.TotalMilliseconds}ms, resuming execution...");
|
||||
|
||||
|
@ -1,146 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
interface IAuto
|
||||
{
|
||||
bool HasCommandBufferDependency(CommandBufferScoped cbs);
|
||||
|
||||
void IncrementReferenceCount();
|
||||
void DecrementReferenceCount(int cbIndex);
|
||||
void DecrementReferenceCount();
|
||||
}
|
||||
|
||||
interface IAutoPrivate : IAuto
|
||||
{
|
||||
void AddCommandBufferDependencies(CommandBufferScoped cbs);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
class Auto<T> : IAutoPrivate, IDisposable where T : IDisposable
|
||||
{
|
||||
private int _referenceCount;
|
||||
private T _value;
|
||||
|
||||
private readonly BitMap _cbOwnership;
|
||||
private readonly MultiFenceHolder _waitable;
|
||||
|
||||
private bool _disposed;
|
||||
private bool _destroyed;
|
||||
|
||||
public Auto(T value)
|
||||
{
|
||||
_referenceCount = 1;
|
||||
_value = value;
|
||||
_cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers);
|
||||
}
|
||||
|
||||
public Auto(T value, MultiFenceHolder waitable) : this(value)
|
||||
{
|
||||
_waitable = waitable;
|
||||
}
|
||||
|
||||
public T Get(CommandBufferScoped cbs, int offset, int size, bool write = false)
|
||||
{
|
||||
_waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, write);
|
||||
return Get(cbs);
|
||||
}
|
||||
|
||||
public T GetUnsafe()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
public T Get(CommandBufferScoped cbs)
|
||||
{
|
||||
if (!_destroyed)
|
||||
{
|
||||
AddCommandBufferDependencies(cbs);
|
||||
}
|
||||
|
||||
return _value;
|
||||
}
|
||||
|
||||
public bool HasCommandBufferDependency(CommandBufferScoped cbs)
|
||||
{
|
||||
return _cbOwnership.IsSet(cbs.CommandBufferIndex);
|
||||
}
|
||||
|
||||
public bool HasRentedCommandBufferDependency(CommandBufferPool cbp)
|
||||
{
|
||||
return _cbOwnership.AnySet();
|
||||
}
|
||||
|
||||
public void AddCommandBufferDependencies(CommandBufferScoped cbs)
|
||||
{
|
||||
// We don't want to add a reference to this object to the command buffer
|
||||
// more than once, so if we detect that the command buffer already has ownership
|
||||
// of this object, then we can just return without doing anything else.
|
||||
if (_cbOwnership.Set(cbs.CommandBufferIndex))
|
||||
{
|
||||
if (_waitable != null)
|
||||
{
|
||||
cbs.AddWaitable(_waitable);
|
||||
}
|
||||
|
||||
cbs.AddDependant(this);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryIncrementReferenceCount()
|
||||
{
|
||||
int lastValue;
|
||||
do
|
||||
{
|
||||
lastValue = _referenceCount;
|
||||
|
||||
if (lastValue == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void IncrementReferenceCount()
|
||||
{
|
||||
if (Interlocked.Increment(ref _referenceCount) == 1)
|
||||
{
|
||||
Interlocked.Decrement(ref _referenceCount);
|
||||
throw new InvalidOperationException("Attempted to increment the reference count of an object that was already destroyed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void DecrementReferenceCount(int cbIndex)
|
||||
{
|
||||
_cbOwnership.Clear(cbIndex);
|
||||
DecrementReferenceCount();
|
||||
}
|
||||
|
||||
public void DecrementReferenceCount()
|
||||
{
|
||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
||||
{
|
||||
_value.Dispose();
|
||||
_value = default;
|
||||
_destroyed = true;
|
||||
}
|
||||
|
||||
Debug.Assert(_referenceCount >= 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
DecrementReferenceCount();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class BackgroundResource : IDisposable
|
||||
{
|
||||
private readonly MetalRenderer _renderer;
|
||||
|
||||
private CommandBufferPool _pool;
|
||||
private PersistentFlushBuffer _flushBuffer;
|
||||
|
||||
public BackgroundResource(MetalRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
}
|
||||
|
||||
public CommandBufferPool GetPool()
|
||||
{
|
||||
if (_pool == null)
|
||||
{
|
||||
MTLCommandQueue queue = _renderer.BackgroundQueue;
|
||||
_pool = new CommandBufferPool(queue, true);
|
||||
_pool.Initialize(null); // TODO: Proper encoder factory for background render/compute
|
||||
}
|
||||
|
||||
return _pool;
|
||||
}
|
||||
|
||||
public PersistentFlushBuffer GetFlushBuffer()
|
||||
{
|
||||
_flushBuffer ??= new PersistentFlushBuffer(_renderer);
|
||||
|
||||
return _flushBuffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pool?.Dispose();
|
||||
_flushBuffer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
class BackgroundResources : IDisposable
|
||||
{
|
||||
private readonly MetalRenderer _renderer;
|
||||
|
||||
private readonly Dictionary<Thread, BackgroundResource> _resources;
|
||||
|
||||
public BackgroundResources(MetalRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
|
||||
_resources = new Dictionary<Thread, BackgroundResource>();
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
lock (_resources)
|
||||
{
|
||||
foreach (KeyValuePair<Thread, BackgroundResource> tuple in _resources)
|
||||
{
|
||||
if (!tuple.Key.IsAlive)
|
||||
{
|
||||
tuple.Value.Dispose();
|
||||
_resources.Remove(tuple.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BackgroundResource Get()
|
||||
{
|
||||
Thread thread = Thread.CurrentThread;
|
||||
|
||||
lock (_resources)
|
||||
{
|
||||
if (!_resources.TryGetValue(thread, out BackgroundResource resource))
|
||||
{
|
||||
Cleanup();
|
||||
|
||||
resource = new BackgroundResource(_renderer);
|
||||
|
||||
_resources[thread] = resource;
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_resources)
|
||||
{
|
||||
foreach (BackgroundResource resource in _resources.Values)
|
||||
{
|
||||
resource.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
readonly struct BitMap
|
||||
{
|
||||
public const int IntSize = 64;
|
||||
|
||||
private const int IntShift = 6;
|
||||
private const int IntMask = IntSize - 1;
|
||||
|
||||
private readonly long[] _masks;
|
||||
|
||||
public BitMap(int count)
|
||||
{
|
||||
_masks = new long[(count + IntMask) / IntSize];
|
||||
}
|
||||
|
||||
public bool AnySet()
|
||||
{
|
||||
for (int i = 0; i < _masks.Length; i++)
|
||||
{
|
||||
if (_masks[i] != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsSet(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
return (_masks[wordIndex] & wordMask) != 0;
|
||||
}
|
||||
|
||||
public bool IsSet(int start, int end)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
return IsSet(start);
|
||||
}
|
||||
|
||||
int startIndex = start >> IntShift;
|
||||
int startBit = start & IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = end >> IntShift;
|
||||
int endBit = end & IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
return (_masks[startIndex] & startMask & endMask) != 0;
|
||||
}
|
||||
|
||||
if ((_masks[startIndex] & startMask) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
if (_masks[i] != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((_masks[endIndex] & endMask) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Set(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
if ((_masks[wordIndex] & wordMask) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_masks[wordIndex] |= wordMask;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetRange(int start, int end)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
Set(start);
|
||||
return;
|
||||
}
|
||||
|
||||
int startIndex = start >> IntShift;
|
||||
int startBit = start & IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = end >> IntShift;
|
||||
int endBit = end & IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
_masks[startIndex] |= startMask & endMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
_masks[startIndex] |= startMask;
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
_masks[i] |= -1;
|
||||
}
|
||||
|
||||
_masks[endIndex] |= endMask;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
_masks[wordIndex] &= ~wordMask;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < _masks.Length; i++)
|
||||
{
|
||||
_masks[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearInt(int start, int end)
|
||||
{
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
_masks[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,385 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class BufferHolder : IDisposable
|
||||
{
|
||||
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
||||
|
||||
public int Size { get; }
|
||||
|
||||
private readonly IntPtr _map;
|
||||
private readonly MetalRenderer _renderer;
|
||||
private readonly Pipeline _pipeline;
|
||||
|
||||
private readonly MultiFenceHolder _waitable;
|
||||
private readonly Auto<DisposableBuffer> _buffer;
|
||||
|
||||
private readonly ReaderWriterLockSlim _flushLock;
|
||||
private FenceHolder _flushFence;
|
||||
private int _flushWaiting;
|
||||
|
||||
public BufferHolder(MetalRenderer renderer, Pipeline pipeline, MTLBuffer buffer, int size)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_pipeline = pipeline;
|
||||
_map = buffer.Contents;
|
||||
_waitable = new MultiFenceHolder(size);
|
||||
_buffer = new Auto<DisposableBuffer>(new(buffer), _waitable);
|
||||
|
||||
_flushLock = new ReaderWriterLockSlim();
|
||||
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(bool isWrite)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
SignalWrite(0, Size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(int offset, int size, bool isWrite)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
SignalWrite(offset, size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public void SignalWrite(int offset, int size)
|
||||
{
|
||||
if (offset == 0 && size == Size)
|
||||
{
|
||||
_cachedConvertedBuffers.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedConvertedBuffers.ClearRange(offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearFlushFence()
|
||||
{
|
||||
// Assumes _flushLock is held as writer.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
if (_flushWaiting == 0)
|
||||
{
|
||||
_flushFence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForFlushFence()
|
||||
{
|
||||
if (_flushFence == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
||||
_flushLock.ExitReadLock();
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
FenceHolder fence = _flushFence;
|
||||
Interlocked.Increment(ref _flushWaiting);
|
||||
|
||||
// Don't wait in the lock.
|
||||
|
||||
_flushLock.ExitWriteLock();
|
||||
|
||||
fence.Wait();
|
||||
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
||||
{
|
||||
fence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
|
||||
// Assumes the _flushLock is held as reader, returns in same state.
|
||||
_flushLock.ExitWriteLock();
|
||||
_flushLock.EnterReadLock();
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetData(int offset, int size)
|
||||
{
|
||||
_flushLock.EnterReadLock();
|
||||
|
||||
WaitForFlushFence();
|
||||
|
||||
Span<byte> result;
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
result = GetDataStorage(offset, size);
|
||||
|
||||
// Need to be careful here, the buffer can't be unmapped while the data is being used.
|
||||
_buffer.IncrementReferenceCount();
|
||||
|
||||
_flushLock.ExitReadLock();
|
||||
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The buffer is not mapped");
|
||||
}
|
||||
|
||||
public unsafe Span<byte> GetDataStorage(int offset, int size)
|
||||
{
|
||||
int mappingSize = Math.Min(size, Size - offset);
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
return new Span<byte>((void*)(_map + offset), mappingSize);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The buffer is not mapped.");
|
||||
}
|
||||
|
||||
public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, bool allowCbsWait = true)
|
||||
{
|
||||
int dataSize = Math.Min(data.Length, Size - offset);
|
||||
if (dataSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
// If persistently mapped, set the data directly if the buffer is not currently in use.
|
||||
bool isRented = _buffer.HasRentedCommandBufferDependency(_renderer.CommandBufferPool);
|
||||
|
||||
// If the buffer is rented, take a little more time and check if the use overlaps this handle.
|
||||
bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize, false);
|
||||
|
||||
if (!needsFlush)
|
||||
{
|
||||
WaitForFences(offset, dataSize);
|
||||
|
||||
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
|
||||
SignalWrite(offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cbs != null &&
|
||||
cbs.Value.Encoders.CurrentEncoderType == EncoderType.Render &&
|
||||
!(_buffer.HasCommandBufferDependency(cbs.Value) &&
|
||||
_waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize)))
|
||||
{
|
||||
// If the buffer hasn't been used on the command buffer yet, try to preload the data.
|
||||
// This avoids ending and beginning render passes on each buffer data upload.
|
||||
|
||||
cbs = _pipeline.GetPreloadCommandBuffer();
|
||||
}
|
||||
|
||||
if (allowCbsWait)
|
||||
{
|
||||
_renderer.BufferManager.StagingBuffer.PushData(_renderer.CommandBufferPool, cbs, this, offset, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool rentCbs = cbs == null;
|
||||
if (rentCbs)
|
||||
{
|
||||
cbs = _renderer.CommandBufferPool.Rent();
|
||||
}
|
||||
|
||||
if (!_renderer.BufferManager.StagingBuffer.TryPushData(cbs.Value, this, offset, data))
|
||||
{
|
||||
// Need to do a slow upload.
|
||||
BufferHolder srcHolder = _renderer.BufferManager.Create(dataSize);
|
||||
srcHolder.SetDataUnchecked(0, data);
|
||||
|
||||
Auto<DisposableBuffer> srcBuffer = srcHolder.GetBuffer();
|
||||
Auto<DisposableBuffer> dstBuffer = this.GetBuffer(true);
|
||||
|
||||
Copy(cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize);
|
||||
|
||||
srcHolder.Dispose();
|
||||
}
|
||||
|
||||
if (rentCbs)
|
||||
{
|
||||
cbs.Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetDataUnchecked(int offset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
int dataSize = Math.Min(data.Length, Size - offset);
|
||||
if (dataSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDataUnchecked<T>(int offset, ReadOnlySpan<T> data) where T : unmanaged
|
||||
{
|
||||
SetDataUnchecked(offset, MemoryMarshal.AsBytes(data));
|
||||
}
|
||||
|
||||
public static void Copy(
|
||||
CommandBufferScoped cbs,
|
||||
Auto<DisposableBuffer> src,
|
||||
Auto<DisposableBuffer> dst,
|
||||
int srcOffset,
|
||||
int dstOffset,
|
||||
int size,
|
||||
bool registerSrcUsage = true)
|
||||
{
|
||||
MTLBuffer srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
|
||||
MTLBuffer dstbuffer = dst.Get(cbs, dstOffset, size, true).Value;
|
||||
|
||||
cbs.Encoders.EnsureBlitEncoder().CopyFromBuffer(
|
||||
srcBuffer,
|
||||
(ulong)srcOffset,
|
||||
dstbuffer,
|
||||
(ulong)dstOffset,
|
||||
(ulong)size);
|
||||
}
|
||||
|
||||
public void WaitForFences()
|
||||
{
|
||||
_waitable.WaitForFences();
|
||||
}
|
||||
|
||||
public void WaitForFences(int offset, int size)
|
||||
{
|
||||
_waitable.WaitForFences(offset, size);
|
||||
}
|
||||
|
||||
private bool BoundToRange(int offset, ref int size)
|
||||
{
|
||||
if (offset >= Size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size = Math.Min(Size - offset, size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
|
||||
{
|
||||
if (!BoundToRange(offset, ref size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
I8ToI16CacheKey key = new(_renderer);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out BufferHolder holder))
|
||||
{
|
||||
holder = _renderer.BufferManager.Create((size * 2 + 3) & ~3);
|
||||
|
||||
_renderer.HelperShader.ConvertI8ToI16(cbs, this, holder, offset, size);
|
||||
|
||||
key.SetBuffer(holder.GetBuffer());
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
return holder.GetBuffer();
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
if (!BoundToRange(offset, ref size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TopologyConversionCacheKey key = new(_renderer, pattern, indexSize);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out BufferHolder holder))
|
||||
{
|
||||
// The destination index size is always I32.
|
||||
|
||||
int indexCount = size / indexSize;
|
||||
|
||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||
|
||||
holder = _renderer.BufferManager.Create(convertedCount * 4);
|
||||
|
||||
_renderer.HelperShader.ConvertIndexBuffer(cbs, this, holder, pattern, indexSize, offset, indexCount);
|
||||
|
||||
key.SetBuffer(holder.GetBuffer());
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
return holder.GetBuffer();
|
||||
}
|
||||
|
||||
public bool TryGetCachedConvertedBuffer(int offset, int size, ICacheKey key, out BufferHolder holder)
|
||||
{
|
||||
return _cachedConvertedBuffers.TryGetValue(offset, size, key, out holder);
|
||||
}
|
||||
|
||||
public void AddCachedConvertedBuffer(int offset, int size, ICacheKey key, BufferHolder holder)
|
||||
{
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
public void AddCachedConvertedBufferDependency(int offset, int size, ICacheKey key, Dependency dependency)
|
||||
{
|
||||
_cachedConvertedBuffers.AddDependency(offset, size, key, dependency);
|
||||
}
|
||||
|
||||
public void RemoveCachedConvertedBuffer(int offset, int size, ICacheKey key)
|
||||
{
|
||||
_cachedConvertedBuffers.Remove(offset, size, key);
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pipeline.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||
|
||||
_buffer.Dispose();
|
||||
_cachedConvertedBuffers.Dispose();
|
||||
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_flushLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,237 +0,0 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
readonly struct ScopedTemporaryBuffer : IDisposable
|
||||
{
|
||||
private readonly BufferManager _bufferManager;
|
||||
private readonly bool _isReserved;
|
||||
|
||||
public readonly BufferRange Range;
|
||||
public readonly BufferHolder Holder;
|
||||
|
||||
public BufferHandle Handle => Range.Handle;
|
||||
public int Offset => Range.Offset;
|
||||
|
||||
public ScopedTemporaryBuffer(BufferManager bufferManager, BufferHolder holder, BufferHandle handle, int offset, int size, bool isReserved)
|
||||
{
|
||||
_bufferManager = bufferManager;
|
||||
|
||||
Range = new BufferRange(handle, offset, size);
|
||||
Holder = holder;
|
||||
|
||||
_isReserved = isReserved;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isReserved)
|
||||
{
|
||||
_bufferManager.Delete(Range.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
class BufferManager : IDisposable
|
||||
{
|
||||
private readonly IdList<BufferHolder> _buffers;
|
||||
|
||||
private readonly MTLDevice _device;
|
||||
private readonly MetalRenderer _renderer;
|
||||
private readonly Pipeline _pipeline;
|
||||
|
||||
public int BufferCount { get; private set; }
|
||||
|
||||
public StagingBuffer StagingBuffer { get; }
|
||||
|
||||
public BufferManager(MTLDevice device, MetalRenderer renderer, Pipeline pipeline)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
_pipeline = pipeline;
|
||||
_buffers = new IdList<BufferHolder>();
|
||||
|
||||
StagingBuffer = new StagingBuffer(_renderer, this);
|
||||
}
|
||||
|
||||
public BufferHandle Create(nint pointer, int size)
|
||||
{
|
||||
// TODO: This is the wrong Metal method, we need no-copy which SharpMetal isn't giving us.
|
||||
MTLBuffer buffer = _device.NewBuffer(pointer, (ulong)size, MTLResourceOptions.ResourceStorageModeShared);
|
||||
|
||||
if (buffer == IntPtr.Zero)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X}, and pointer 0x{pointer:X}.");
|
||||
|
||||
return BufferHandle.Null;
|
||||
}
|
||||
|
||||
BufferHolder holder = new(_renderer, _pipeline, buffer, size);
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(int size)
|
||||
{
|
||||
return CreateWithHandle(size, out _);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(int size, out BufferHolder holder)
|
||||
{
|
||||
holder = Create(size);
|
||||
|
||||
if (holder == null)
|
||||
{
|
||||
return BufferHandle.Null;
|
||||
}
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public ScopedTemporaryBuffer ReserveOrCreate(CommandBufferScoped cbs, int size)
|
||||
{
|
||||
StagingBufferReserved? result = StagingBuffer.TryReserveData(cbs, size);
|
||||
|
||||
if (result.HasValue)
|
||||
{
|
||||
return new ScopedTemporaryBuffer(this, result.Value.Buffer, StagingBuffer.Handle, result.Value.Offset, result.Value.Size, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a temporary buffer.
|
||||
BufferHandle handle = CreateWithHandle(size, out BufferHolder holder);
|
||||
|
||||
return new ScopedTemporaryBuffer(this, holder, handle, 0, size, false);
|
||||
}
|
||||
}
|
||||
|
||||
public BufferHolder Create(int size)
|
||||
{
|
||||
MTLBuffer buffer = _device.NewBuffer((ulong)size, MTLResourceOptions.ResourceStorageModeShared);
|
||||
|
||||
if (buffer != IntPtr.Zero)
|
||||
{
|
||||
return new BufferHolder(_renderer, _pipeline, buffer, size);
|
||||
}
|
||||
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X}.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(BufferHandle handle, bool isWrite, out int size)
|
||||
{
|
||||
if (TryGetBuffer(handle, out BufferHolder holder))
|
||||
{
|
||||
size = holder.Size;
|
||||
return holder.GetBuffer(isWrite);
|
||||
}
|
||||
|
||||
size = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(BufferHandle handle, int offset, int size, bool isWrite)
|
||||
{
|
||||
if (TryGetBuffer(handle, out BufferHolder holder))
|
||||
{
|
||||
return holder.GetBuffer(offset, size, isWrite);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(BufferHandle handle, bool isWrite)
|
||||
{
|
||||
if (TryGetBuffer(handle, out BufferHolder holder))
|
||||
{
|
||||
return holder.GetBuffer(isWrite);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, BufferHandle handle, int offset, int size)
|
||||
{
|
||||
if (TryGetBuffer(handle, out BufferHolder holder))
|
||||
{
|
||||
return holder.GetBufferI8ToI16(cbs, offset, size);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
if (TryGetBuffer(handle, out BufferHolder holder))
|
||||
{
|
||||
return holder.GetBufferTopologyConversion(cbs, offset, size, pattern, indexSize);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetData(BufferHandle handle, int offset, int size)
|
||||
{
|
||||
if (TryGetBuffer(handle, out BufferHolder holder))
|
||||
{
|
||||
return holder.GetData(offset, size);
|
||||
}
|
||||
|
||||
return new PinnedSpan<byte>();
|
||||
}
|
||||
|
||||
public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
|
||||
{
|
||||
SetData(handle, offset, MemoryMarshal.Cast<T, byte>(data), null);
|
||||
}
|
||||
|
||||
public void SetData(BufferHandle handle, int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs)
|
||||
{
|
||||
if (TryGetBuffer(handle, out BufferHolder holder))
|
||||
{
|
||||
holder.SetData(offset, data, cbs);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(BufferHandle handle)
|
||||
{
|
||||
if (TryGetBuffer(handle, out BufferHolder holder))
|
||||
{
|
||||
holder.Dispose();
|
||||
_buffers.Remove((int)Unsafe.As<BufferHandle, ulong>(ref handle));
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetBuffer(BufferHandle handle, out BufferHolder holder)
|
||||
{
|
||||
return _buffers.TryGetValue((int)Unsafe.As<BufferHandle, ulong>(ref handle), out holder);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StagingBuffer.Dispose();
|
||||
|
||||
foreach (BufferHolder buffer in _buffers)
|
||||
{
|
||||
buffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
internal class BufferUsageBitmap
|
||||
{
|
||||
private readonly BitMap _bitmap;
|
||||
private readonly int _size;
|
||||
private readonly int _granularity;
|
||||
private readonly int _bits;
|
||||
private readonly int _writeBitOffset;
|
||||
|
||||
private readonly int _intsPerCb;
|
||||
private readonly int _bitsPerCb;
|
||||
|
||||
public BufferUsageBitmap(int size, int granularity)
|
||||
{
|
||||
_size = size;
|
||||
_granularity = granularity;
|
||||
|
||||
// There are two sets of bits - one for read tracking, and the other for write.
|
||||
int bits = (size + (granularity - 1)) / granularity;
|
||||
_writeBitOffset = bits;
|
||||
_bits = bits << 1;
|
||||
|
||||
_intsPerCb = (_bits + (BitMap.IntSize - 1)) / BitMap.IntSize;
|
||||
_bitsPerCb = _intsPerCb * BitMap.IntSize;
|
||||
|
||||
_bitmap = new BitMap(_bitsPerCb * CommandBufferPool.MaxCommandBuffers);
|
||||
}
|
||||
|
||||
public void Add(int cbIndex, int offset, int size, bool write)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Some usages can be out of bounds (vertex buffer on amd), so bound if necessary.
|
||||
if (offset + size > _size)
|
||||
{
|
||||
size = _size - offset;
|
||||
}
|
||||
|
||||
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
|
||||
int start = cbBase + offset / _granularity;
|
||||
int end = cbBase + (offset + size - 1) / _granularity;
|
||||
|
||||
_bitmap.SetRange(start, end);
|
||||
}
|
||||
|
||||
public bool OverlapsWith(int cbIndex, int offset, int size, bool write = false)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
|
||||
int start = cbBase + offset / _granularity;
|
||||
int end = cbBase + (offset + size - 1) / _granularity;
|
||||
|
||||
return _bitmap.IsSet(start, end);
|
||||
}
|
||||
|
||||
public bool OverlapsWith(int offset, int size, bool write)
|
||||
{
|
||||
for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
|
||||
{
|
||||
if (OverlapsWith(i, offset, size, write))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear(int cbIndex)
|
||||
{
|
||||
_bitmap.ClearInt(cbIndex * _intsPerCb, (cbIndex + 1) * _intsPerCb - 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
interface ICacheKey : IDisposable
|
||||
{
|
||||
bool KeyEqual(ICacheKey other);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
struct I8ToI16CacheKey : ICacheKey
|
||||
{
|
||||
// Used to notify the pipeline that bindings have invalidated on dispose.
|
||||
// private readonly MetalRenderer _renderer;
|
||||
// private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public I8ToI16CacheKey(MetalRenderer renderer)
|
||||
{
|
||||
// _renderer = renderer;
|
||||
// _buffer = null;
|
||||
}
|
||||
|
||||
public readonly bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is I8ToI16CacheKey;
|
||||
}
|
||||
|
||||
public readonly void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
// _buffer = buffer;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
// TODO: Tell pipeline buffer is dirty!
|
||||
// _renderer.PipelineInternal.DirtyIndexBuffer(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
readonly struct TopologyConversionCacheKey : ICacheKey
|
||||
{
|
||||
private readonly IndexBufferPattern _pattern;
|
||||
private readonly int _indexSize;
|
||||
|
||||
// Used to notify the pipeline that bindings have invalidated on dispose.
|
||||
// private readonly MetalRenderer _renderer;
|
||||
// private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public TopologyConversionCacheKey(MetalRenderer renderer, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
// _renderer = renderer;
|
||||
// _buffer = null;
|
||||
_pattern = pattern;
|
||||
_indexSize = indexSize;
|
||||
}
|
||||
|
||||
public readonly bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is TopologyConversionCacheKey entry &&
|
||||
entry._pattern == _pattern &&
|
||||
entry._indexSize == _indexSize;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
// _buffer = buffer;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
// TODO: Tell pipeline buffer is dirty!
|
||||
// _renderer.PipelineInternal.DirtyVertexBuffer(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
readonly struct Dependency
|
||||
{
|
||||
private readonly BufferHolder _buffer;
|
||||
private readonly int _offset;
|
||||
private readonly int _size;
|
||||
private readonly ICacheKey _key;
|
||||
|
||||
public Dependency(BufferHolder buffer, int offset, int size, ICacheKey key)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public void RemoveFromOwner()
|
||||
{
|
||||
_buffer.RemoveCachedConvertedBuffer(_offset, _size, _key);
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
struct CacheByRange<T> where T : IDisposable
|
||||
{
|
||||
private struct Entry
|
||||
{
|
||||
public readonly ICacheKey Key;
|
||||
public readonly T Value;
|
||||
public List<Dependency> DependencyList;
|
||||
|
||||
public Entry(ICacheKey key, T value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
DependencyList = null;
|
||||
}
|
||||
|
||||
public readonly void InvalidateDependencies()
|
||||
{
|
||||
if (DependencyList != null)
|
||||
{
|
||||
foreach (Dependency dependency in DependencyList)
|
||||
{
|
||||
dependency.RemoveFromOwner();
|
||||
}
|
||||
|
||||
DependencyList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<ulong, List<Entry>> _ranges;
|
||||
|
||||
public void Add(int offset, int size, ICacheKey key, T value)
|
||||
{
|
||||
List<Entry> entries = GetEntries(offset, size);
|
||||
|
||||
entries.Add(new Entry(key, value));
|
||||
}
|
||||
|
||||
public void AddDependency(int offset, int size, ICacheKey key, Dependency dependency)
|
||||
{
|
||||
List<Entry> entries = GetEntries(offset, size);
|
||||
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
Entry entry = entries[i];
|
||||
|
||||
if (entry.Key.KeyEqual(key))
|
||||
{
|
||||
if (entry.DependencyList == null)
|
||||
{
|
||||
entry.DependencyList = [];
|
||||
entries[i] = entry;
|
||||
}
|
||||
|
||||
entry.DependencyList.Add(dependency);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(int offset, int size, ICacheKey key)
|
||||
{
|
||||
List<Entry> entries = GetEntries(offset, size);
|
||||
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
Entry entry = entries[i];
|
||||
|
||||
if (entry.Key.KeyEqual(key))
|
||||
{
|
||||
entries.RemoveAt(i--);
|
||||
|
||||
DestroyEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.Count == 0)
|
||||
{
|
||||
_ranges.Remove(PackRange(offset, size));
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(int offset, int size, ICacheKey key, out T value)
|
||||
{
|
||||
List<Entry> entries = GetEntries(offset, size);
|
||||
|
||||
foreach (Entry entry in entries)
|
||||
{
|
||||
if (entry.Key.KeyEqual(key))
|
||||
{
|
||||
value = entry.Value;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_ranges != null)
|
||||
{
|
||||
foreach (List<Entry> entries in _ranges.Values)
|
||||
{
|
||||
foreach (Entry entry in entries)
|
||||
{
|
||||
DestroyEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
_ranges.Clear();
|
||||
_ranges = null;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly void ClearRange(int offset, int size)
|
||||
{
|
||||
if (_ranges != null && _ranges.Count > 0)
|
||||
{
|
||||
int end = offset + size;
|
||||
|
||||
List<ulong> toRemove = null;
|
||||
|
||||
foreach (KeyValuePair<ulong, List<Entry>> range in _ranges)
|
||||
{
|
||||
(int rOffset, int rSize) = UnpackRange(range.Key);
|
||||
|
||||
int rEnd = rOffset + rSize;
|
||||
|
||||
if (rEnd > offset && rOffset < end)
|
||||
{
|
||||
List<Entry> entries = range.Value;
|
||||
|
||||
foreach (Entry entry in entries)
|
||||
{
|
||||
DestroyEntry(entry);
|
||||
}
|
||||
|
||||
(toRemove ??= []).Add(range.Key);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (ulong range in toRemove)
|
||||
{
|
||||
_ranges.Remove(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Entry> GetEntries(int offset, int size)
|
||||
{
|
||||
_ranges ??= new Dictionary<ulong, List<Entry>>();
|
||||
|
||||
ulong key = PackRange(offset, size);
|
||||
|
||||
if (!_ranges.TryGetValue(key, out List<Entry> value))
|
||||
{
|
||||
value = [];
|
||||
_ranges.Add(key, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void DestroyEntry(Entry entry)
|
||||
{
|
||||
entry.Key.Dispose();
|
||||
entry.Value?.Dispose();
|
||||
entry.InvalidateDependencies();
|
||||
}
|
||||
|
||||
private static ulong PackRange(int offset, int size)
|
||||
{
|
||||
return (uint)offset | ((ulong)size << 32);
|
||||
}
|
||||
|
||||
private static (int offset, int size) UnpackRange(ulong range)
|
||||
{
|
||||
return ((int)range, (int)(range >> 32));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
using Ryujinx.Graphics.Metal;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
interface IEncoderFactory
|
||||
{
|
||||
MTLRenderCommandEncoder CreateRenderCommandEncoder();
|
||||
MTLComputeCommandEncoder CreateComputeCommandEncoder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks active encoder object for a command buffer.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("macos")]
|
||||
class CommandBufferEncoder
|
||||
{
|
||||
public EncoderType CurrentEncoderType { get; private set; } = EncoderType.None;
|
||||
|
||||
public MTLBlitCommandEncoder BlitEncoder => new(CurrentEncoder.Value);
|
||||
|
||||
public MTLComputeCommandEncoder ComputeEncoder => new(CurrentEncoder.Value);
|
||||
|
||||
public MTLRenderCommandEncoder RenderEncoder => new(CurrentEncoder.Value);
|
||||
|
||||
internal MTLCommandEncoder? CurrentEncoder { get; private set; }
|
||||
|
||||
private MTLCommandBuffer _commandBuffer;
|
||||
private IEncoderFactory _encoderFactory;
|
||||
|
||||
public void Initialize(MTLCommandBuffer commandBuffer, IEncoderFactory encoderFactory)
|
||||
{
|
||||
_commandBuffer = commandBuffer;
|
||||
_encoderFactory = encoderFactory;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public MTLRenderCommandEncoder EnsureRenderEncoder()
|
||||
{
|
||||
if (CurrentEncoderType != EncoderType.Render)
|
||||
{
|
||||
return BeginRenderPass();
|
||||
}
|
||||
|
||||
return RenderEncoder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public MTLBlitCommandEncoder EnsureBlitEncoder()
|
||||
{
|
||||
if (CurrentEncoderType != EncoderType.Blit)
|
||||
{
|
||||
return BeginBlitPass();
|
||||
}
|
||||
|
||||
return BlitEncoder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public MTLComputeCommandEncoder EnsureComputeEncoder()
|
||||
{
|
||||
if (CurrentEncoderType != EncoderType.Compute)
|
||||
{
|
||||
return BeginComputePass();
|
||||
}
|
||||
|
||||
return ComputeEncoder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetRenderEncoder(out MTLRenderCommandEncoder encoder)
|
||||
{
|
||||
if (CurrentEncoderType != EncoderType.Render)
|
||||
{
|
||||
encoder = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder = RenderEncoder;
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetBlitEncoder(out MTLBlitCommandEncoder encoder)
|
||||
{
|
||||
if (CurrentEncoderType != EncoderType.Blit)
|
||||
{
|
||||
encoder = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder = BlitEncoder;
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetComputeEncoder(out MTLComputeCommandEncoder encoder)
|
||||
{
|
||||
if (CurrentEncoderType != EncoderType.Compute)
|
||||
{
|
||||
encoder = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder = ComputeEncoder;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void EndCurrentPass()
|
||||
{
|
||||
if (CurrentEncoder != null)
|
||||
{
|
||||
switch (CurrentEncoderType)
|
||||
{
|
||||
case EncoderType.Blit:
|
||||
BlitEncoder.EndEncoding();
|
||||
CurrentEncoder = null;
|
||||
break;
|
||||
case EncoderType.Compute:
|
||||
ComputeEncoder.EndEncoding();
|
||||
CurrentEncoder = null;
|
||||
break;
|
||||
case EncoderType.Render:
|
||||
RenderEncoder.EndEncoding();
|
||||
CurrentEncoder = null;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
CurrentEncoderType = EncoderType.None;
|
||||
}
|
||||
}
|
||||
|
||||
private MTLRenderCommandEncoder BeginRenderPass()
|
||||
{
|
||||
EndCurrentPass();
|
||||
|
||||
MTLRenderCommandEncoder renderCommandEncoder = _encoderFactory.CreateRenderCommandEncoder();
|
||||
|
||||
CurrentEncoder = renderCommandEncoder;
|
||||
CurrentEncoderType = EncoderType.Render;
|
||||
|
||||
return renderCommandEncoder;
|
||||
}
|
||||
|
||||
private MTLBlitCommandEncoder BeginBlitPass()
|
||||
{
|
||||
EndCurrentPass();
|
||||
|
||||
using MTLBlitPassDescriptor descriptor = new();
|
||||
MTLBlitCommandEncoder blitCommandEncoder = _commandBuffer.BlitCommandEncoder(descriptor);
|
||||
|
||||
CurrentEncoder = blitCommandEncoder;
|
||||
CurrentEncoderType = EncoderType.Blit;
|
||||
return blitCommandEncoder;
|
||||
}
|
||||
|
||||
private MTLComputeCommandEncoder BeginComputePass()
|
||||
{
|
||||
EndCurrentPass();
|
||||
|
||||
MTLComputeCommandEncoder computeCommandEncoder = _encoderFactory.CreateComputeCommandEncoder();
|
||||
|
||||
CurrentEncoder = computeCommandEncoder;
|
||||
CurrentEncoderType = EncoderType.Compute;
|
||||
return computeCommandEncoder;
|
||||
}
|
||||
}
|
@ -1,289 +0,0 @@
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class CommandBufferPool : IDisposable
|
||||
{
|
||||
public const int MaxCommandBuffers = 16;
|
||||
|
||||
private readonly int _totalCommandBuffers;
|
||||
private readonly int _totalCommandBuffersMask;
|
||||
private readonly MTLCommandQueue _queue;
|
||||
private readonly Thread _owner;
|
||||
private IEncoderFactory _defaultEncoderFactory;
|
||||
|
||||
public bool OwnedByCurrentThread => _owner == Thread.CurrentThread;
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
private struct ReservedCommandBuffer
|
||||
{
|
||||
public bool InUse;
|
||||
public bool InConsumption;
|
||||
public int SubmissionCount;
|
||||
public MTLCommandBuffer CommandBuffer;
|
||||
public CommandBufferEncoder Encoders;
|
||||
public FenceHolder Fence;
|
||||
|
||||
public List<IAuto> Dependants;
|
||||
public List<MultiFenceHolder> Waitables;
|
||||
|
||||
public void Use(MTLCommandQueue queue, IEncoderFactory stateManager)
|
||||
{
|
||||
MTLCommandBufferDescriptor descriptor = new();
|
||||
#if DEBUG
|
||||
descriptor.ErrorOptions = MTLCommandBufferErrorOption.EncoderExecutionStatus;
|
||||
#endif
|
||||
|
||||
CommandBuffer = queue.CommandBuffer(descriptor);
|
||||
Fence = new FenceHolder(CommandBuffer);
|
||||
|
||||
Encoders.Initialize(CommandBuffer, stateManager);
|
||||
|
||||
InUse = true;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Dependants = [];
|
||||
Waitables = [];
|
||||
Encoders = new CommandBufferEncoder();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ReservedCommandBuffer[] _commandBuffers;
|
||||
|
||||
private readonly int[] _queuedIndexes;
|
||||
private int _queuedIndexesPtr;
|
||||
private int _queuedCount;
|
||||
private int _inUseCount;
|
||||
|
||||
public CommandBufferPool(MTLCommandQueue queue, bool isLight = false)
|
||||
{
|
||||
_queue = queue;
|
||||
_owner = Thread.CurrentThread;
|
||||
|
||||
_totalCommandBuffers = isLight ? 2 : MaxCommandBuffers;
|
||||
_totalCommandBuffersMask = _totalCommandBuffers - 1;
|
||||
|
||||
_commandBuffers = new ReservedCommandBuffer[_totalCommandBuffers];
|
||||
|
||||
_queuedIndexes = new int[_totalCommandBuffers];
|
||||
_queuedIndexesPtr = 0;
|
||||
_queuedCount = 0;
|
||||
}
|
||||
|
||||
public void Initialize(IEncoderFactory encoderFactory)
|
||||
{
|
||||
_defaultEncoderFactory = encoderFactory;
|
||||
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
_commandBuffers[i].Initialize();
|
||||
WaitAndDecrementRef(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDependant(int cbIndex, IAuto dependant)
|
||||
{
|
||||
dependant.IncrementReferenceCount();
|
||||
_commandBuffers[cbIndex].Dependants.Add(dependant);
|
||||
}
|
||||
|
||||
public void AddWaitable(MultiFenceHolder waitable)
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[i];
|
||||
|
||||
if (entry.InConsumption)
|
||||
{
|
||||
AddWaitable(i, waitable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddInUseWaitable(MultiFenceHolder waitable)
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[i];
|
||||
|
||||
if (entry.InUse)
|
||||
{
|
||||
AddWaitable(i, waitable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
||||
{
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[cbIndex];
|
||||
if (waitable.AddFence(cbIndex, entry.Fence))
|
||||
{
|
||||
entry.Waitables.Add(waitable);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFenceOnRentedCommandBuffer(FenceHolder fence)
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[i];
|
||||
|
||||
if (entry.InUse && entry.Fence == fence)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public FenceHolder GetFence(int cbIndex)
|
||||
{
|
||||
return _commandBuffers[cbIndex].Fence;
|
||||
}
|
||||
|
||||
public int GetSubmissionCount(int cbIndex)
|
||||
{
|
||||
return _commandBuffers[cbIndex].SubmissionCount;
|
||||
}
|
||||
|
||||
private int FreeConsumed(bool wait)
|
||||
{
|
||||
int freeEntry = 0;
|
||||
|
||||
while (_queuedCount > 0)
|
||||
{
|
||||
int index = _queuedIndexes[_queuedIndexesPtr];
|
||||
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[index];
|
||||
|
||||
if (wait || !entry.InConsumption || entry.Fence.IsSignaled())
|
||||
{
|
||||
WaitAndDecrementRef(index);
|
||||
|
||||
wait = false;
|
||||
freeEntry = index;
|
||||
|
||||
_queuedCount--;
|
||||
_queuedIndexesPtr = (_queuedIndexesPtr + 1) % _totalCommandBuffers;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return freeEntry;
|
||||
}
|
||||
|
||||
public CommandBufferScoped ReturnAndRent(CommandBufferScoped cbs)
|
||||
{
|
||||
Return(cbs);
|
||||
return Rent();
|
||||
}
|
||||
|
||||
public CommandBufferScoped Rent()
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
int cursor = FreeConsumed(_inUseCount + _queuedCount == _totalCommandBuffers);
|
||||
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[cursor];
|
||||
|
||||
if (!entry.InUse && !entry.InConsumption)
|
||||
{
|
||||
entry.Use(_queue, _defaultEncoderFactory);
|
||||
|
||||
_inUseCount++;
|
||||
|
||||
return new CommandBufferScoped(this, entry.CommandBuffer, entry.Encoders, cursor);
|
||||
}
|
||||
|
||||
cursor = (cursor + 1) & _totalCommandBuffersMask;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Out of command buffers (In use: {_inUseCount}, queued: {_queuedCount}, total: {_totalCommandBuffers})");
|
||||
}
|
||||
|
||||
public void Return(CommandBufferScoped cbs)
|
||||
{
|
||||
// Ensure the encoder is committed.
|
||||
cbs.Encoders.EndCurrentPass();
|
||||
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
int cbIndex = cbs.CommandBufferIndex;
|
||||
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[cbIndex];
|
||||
|
||||
Debug.Assert(entry.InUse);
|
||||
Debug.Assert(entry.CommandBuffer.NativePtr == cbs.CommandBuffer.NativePtr);
|
||||
entry.InUse = false;
|
||||
entry.InConsumption = true;
|
||||
entry.SubmissionCount++;
|
||||
_inUseCount--;
|
||||
|
||||
MTLCommandBuffer commandBuffer = entry.CommandBuffer;
|
||||
commandBuffer.Commit();
|
||||
|
||||
int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers;
|
||||
_queuedIndexes[ptr] = cbIndex;
|
||||
_queuedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitAndDecrementRef(int cbIndex)
|
||||
{
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[cbIndex];
|
||||
|
||||
if (entry.InConsumption)
|
||||
{
|
||||
entry.Fence.Wait();
|
||||
entry.InConsumption = false;
|
||||
}
|
||||
|
||||
foreach (IAuto dependant in entry.Dependants)
|
||||
{
|
||||
dependant.DecrementReferenceCount(cbIndex);
|
||||
}
|
||||
|
||||
foreach (MultiFenceHolder waitable in entry.Waitables)
|
||||
{
|
||||
waitable.RemoveFence(cbIndex);
|
||||
waitable.RemoveBufferUses(cbIndex);
|
||||
}
|
||||
|
||||
entry.Dependants.Clear();
|
||||
entry.Waitables.Clear();
|
||||
entry.Fence?.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
WaitAndDecrementRef(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
readonly struct CommandBufferScoped : IDisposable
|
||||
{
|
||||
private readonly CommandBufferPool _pool;
|
||||
public MTLCommandBuffer CommandBuffer { get; }
|
||||
public CommandBufferEncoder Encoders { get; }
|
||||
public int CommandBufferIndex { get; }
|
||||
|
||||
public CommandBufferScoped(CommandBufferPool pool, MTLCommandBuffer commandBuffer, CommandBufferEncoder encoders, int commandBufferIndex)
|
||||
{
|
||||
_pool = pool;
|
||||
CommandBuffer = commandBuffer;
|
||||
Encoders = encoders;
|
||||
CommandBufferIndex = commandBufferIndex;
|
||||
}
|
||||
|
||||
public void AddDependant(IAuto dependant)
|
||||
{
|
||||
_pool.AddDependant(CommandBufferIndex, dependant);
|
||||
}
|
||||
|
||||
public void AddWaitable(MultiFenceHolder waitable)
|
||||
{
|
||||
_pool.AddWaitable(CommandBufferIndex, waitable);
|
||||
}
|
||||
|
||||
public FenceHolder GetFence()
|
||||
{
|
||||
return _pool.GetFence(CommandBufferIndex);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pool?.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
static class Constants
|
||||
{
|
||||
public const int MaxShaderStages = 5;
|
||||
public const int MaxVertexBuffers = 16;
|
||||
public const int MaxUniformBuffersPerStage = 18;
|
||||
public const int MaxStorageBuffersPerStage = 16;
|
||||
public const int MaxTexturesPerStage = 64;
|
||||
public const int MaxImagesPerStage = 16;
|
||||
|
||||
public const int MaxUniformBufferBindings = MaxUniformBuffersPerStage * MaxShaderStages;
|
||||
public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
|
||||
public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
|
||||
public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages;
|
||||
public const int MaxColorAttachments = 8;
|
||||
public const int MaxViewports = 16;
|
||||
// TODO: Check this value
|
||||
public const int MaxVertexAttributes = 31;
|
||||
|
||||
public const int MinResourceAlignment = 16;
|
||||
|
||||
// Must match constants set in shader generation
|
||||
public const uint ZeroBufferIndex = MaxVertexBuffers;
|
||||
public const uint BaseSetIndex = MaxVertexBuffers + 1;
|
||||
|
||||
public const uint ConstantBuffersIndex = BaseSetIndex;
|
||||
public const uint StorageBuffersIndex = BaseSetIndex + 1;
|
||||
public const uint TexturesIndex = BaseSetIndex + 2;
|
||||
public const uint ImagesIndex = BaseSetIndex + 3;
|
||||
|
||||
public const uint ConstantBuffersSetIndex = 0;
|
||||
public const uint StorageBuffersSetIndex = 1;
|
||||
public const uint TexturesSetIndex = 2;
|
||||
public const uint ImagesSetIndex = 3;
|
||||
|
||||
public const uint MaximumBufferArgumentTableEntries = 31;
|
||||
|
||||
public const uint MaximumExtraSets = MaximumBufferArgumentTableEntries - ImagesIndex;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
class CounterEvent : ICounterEvent
|
||||
{
|
||||
public CounterEvent()
|
||||
{
|
||||
Invalid = false;
|
||||
}
|
||||
|
||||
public bool Invalid { get; set; }
|
||||
public bool ReserveForHostAccess()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Flush() { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
using Ryujinx.Graphics.Metal.State;
|
||||
using SharpMetal.Metal;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class DepthStencilCache : StateCache<MTLDepthStencilState, DepthStencilUid, DepthStencilUid>
|
||||
{
|
||||
private readonly MTLDevice _device;
|
||||
|
||||
public DepthStencilCache(MTLDevice device)
|
||||
{
|
||||
_device = device;
|
||||
}
|
||||
|
||||
protected override DepthStencilUid GetHash(DepthStencilUid descriptor)
|
||||
{
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
protected override MTLDepthStencilState CreateValue(DepthStencilUid descriptor)
|
||||
{
|
||||
// Create descriptors
|
||||
|
||||
ref StencilUid frontUid = ref descriptor.FrontFace;
|
||||
|
||||
using MTLStencilDescriptor frontFaceStencil = new()
|
||||
{
|
||||
StencilFailureOperation = frontUid.StencilFailureOperation,
|
||||
DepthFailureOperation = frontUid.DepthFailureOperation,
|
||||
DepthStencilPassOperation = frontUid.DepthStencilPassOperation,
|
||||
StencilCompareFunction = frontUid.StencilCompareFunction,
|
||||
ReadMask = frontUid.ReadMask,
|
||||
WriteMask = frontUid.WriteMask
|
||||
};
|
||||
|
||||
ref StencilUid backUid = ref descriptor.BackFace;
|
||||
|
||||
using MTLStencilDescriptor backFaceStencil = new()
|
||||
{
|
||||
StencilFailureOperation = backUid.StencilFailureOperation,
|
||||
DepthFailureOperation = backUid.DepthFailureOperation,
|
||||
DepthStencilPassOperation = backUid.DepthStencilPassOperation,
|
||||
StencilCompareFunction = backUid.StencilCompareFunction,
|
||||
ReadMask = backUid.ReadMask,
|
||||
WriteMask = backUid.WriteMask
|
||||
};
|
||||
|
||||
MTLDepthStencilDescriptor mtlDescriptor = new()
|
||||
{
|
||||
DepthCompareFunction = descriptor.DepthCompareFunction,
|
||||
DepthWriteEnabled = descriptor.DepthWriteEnabled
|
||||
};
|
||||
|
||||
if (descriptor.StencilTestEnabled)
|
||||
{
|
||||
mtlDescriptor.BackFaceStencil = backFaceStencil;
|
||||
mtlDescriptor.FrontFaceStencil = frontFaceStencil;
|
||||
}
|
||||
|
||||
using (mtlDescriptor)
|
||||
{
|
||||
return _device.NewDepthStencilState(mtlDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
readonly struct DisposableBuffer : IDisposable
|
||||
{
|
||||
public MTLBuffer Value { get; }
|
||||
|
||||
public DisposableBuffer(MTLBuffer buffer)
|
||||
{
|
||||
Value = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Value != IntPtr.Zero)
|
||||
{
|
||||
Value.SetPurgeableState(MTLPurgeableState.Empty);
|
||||
Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
readonly struct DisposableSampler : IDisposable
|
||||
{
|
||||
public MTLSamplerState Value { get; }
|
||||
|
||||
public DisposableSampler(MTLSamplerState sampler)
|
||||
{
|
||||
Value = sampler;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal.Effects
|
||||
{
|
||||
internal interface IPostProcessingEffect : IDisposable
|
||||
{
|
||||
const int LocalGroupSize = 64;
|
||||
Texture Run(Texture view, int width, int height);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal.Effects
|
||||
{
|
||||
internal interface IScalingFilter : IDisposable
|
||||
{
|
||||
float Level { get; set; }
|
||||
void Run(
|
||||
Texture view,
|
||||
Texture destinationTexture,
|
||||
Format format,
|
||||
int width,
|
||||
int height,
|
||||
Extents2D source,
|
||||
Extents2D destination);
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
using SharpMetal.Metal;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
public struct RenderEncoderBindings
|
||||
{
|
||||
public List<Resource> Resources = [];
|
||||
public List<BufferResource> VertexBuffers = [];
|
||||
public List<BufferResource> FragmentBuffers = [];
|
||||
|
||||
public RenderEncoderBindings() { }
|
||||
|
||||
public readonly void Clear()
|
||||
{
|
||||
Resources.Clear();
|
||||
VertexBuffers.Clear();
|
||||
FragmentBuffers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public struct ComputeEncoderBindings
|
||||
{
|
||||
public List<Resource> Resources = [];
|
||||
public List<BufferResource> Buffers = [];
|
||||
|
||||
public ComputeEncoderBindings() { }
|
||||
|
||||
public readonly void Clear()
|
||||
{
|
||||
Resources.Clear();
|
||||
Buffers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public struct BufferResource
|
||||
{
|
||||
public MTLBuffer Buffer;
|
||||
public ulong Offset;
|
||||
public ulong Binding;
|
||||
|
||||
public BufferResource(MTLBuffer buffer, ulong offset, ulong binding)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Offset = offset;
|
||||
Binding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Resource
|
||||
{
|
||||
public MTLResource MtlResource;
|
||||
public MTLResourceUsage ResourceUsage;
|
||||
public MTLRenderStages Stages;
|
||||
|
||||
public Resource(MTLResource resource, MTLResourceUsage resourceUsage, MTLRenderStages stages)
|
||||
{
|
||||
MtlResource = resource;
|
||||
ResourceUsage = resourceUsage;
|
||||
Stages = stages;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Metal.State;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[Flags]
|
||||
enum DirtyFlags
|
||||
{
|
||||
None = 0,
|
||||
RenderPipeline = 1 << 0,
|
||||
ComputePipeline = 1 << 1,
|
||||
DepthStencil = 1 << 2,
|
||||
DepthClamp = 1 << 3,
|
||||
DepthBias = 1 << 4,
|
||||
CullMode = 1 << 5,
|
||||
FrontFace = 1 << 6,
|
||||
StencilRef = 1 << 7,
|
||||
Viewports = 1 << 8,
|
||||
Scissors = 1 << 9,
|
||||
Uniforms = 1 << 10,
|
||||
Storages = 1 << 11,
|
||||
Textures = 1 << 12,
|
||||
Images = 1 << 13,
|
||||
|
||||
ArgBuffers = Uniforms | Storages | Textures | Images,
|
||||
|
||||
RenderAll = RenderPipeline | DepthStencil | DepthClamp | DepthBias | CullMode | FrontFace | StencilRef | Viewports | Scissors | ArgBuffers,
|
||||
ComputeAll = ComputePipeline | ArgBuffers,
|
||||
All = RenderAll | ComputeAll,
|
||||
}
|
||||
|
||||
record struct BufferRef
|
||||
{
|
||||
public Auto<DisposableBuffer> Buffer;
|
||||
public BufferRange? Range;
|
||||
|
||||
public BufferRef(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
Buffer = buffer;
|
||||
}
|
||||
|
||||
public BufferRef(Auto<DisposableBuffer> buffer, ref BufferRange range)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Range = range;
|
||||
}
|
||||
}
|
||||
|
||||
record struct TextureRef
|
||||
{
|
||||
public ShaderStage Stage;
|
||||
public TextureBase Storage;
|
||||
public Auto<DisposableSampler> Sampler;
|
||||
public Format ImageFormat;
|
||||
|
||||
public TextureRef(ShaderStage stage, TextureBase storage, Auto<DisposableSampler> sampler)
|
||||
{
|
||||
Stage = stage;
|
||||
Storage = storage;
|
||||
Sampler = sampler;
|
||||
}
|
||||
}
|
||||
|
||||
record struct ImageRef
|
||||
{
|
||||
public ShaderStage Stage;
|
||||
public Texture Storage;
|
||||
|
||||
public ImageRef(ShaderStage stage, Texture storage)
|
||||
{
|
||||
Stage = stage;
|
||||
Storage = storage;
|
||||
}
|
||||
}
|
||||
|
||||
struct PredrawState
|
||||
{
|
||||
public MTLCullMode CullMode;
|
||||
public DepthStencilUid DepthStencilUid;
|
||||
public PrimitiveTopology Topology;
|
||||
public MTLViewport[] Viewports;
|
||||
}
|
||||
|
||||
struct RenderTargetCopy
|
||||
{
|
||||
public MTLScissorRect[] Scissors;
|
||||
public Texture DepthStencil;
|
||||
public Texture[] RenderTargets;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
class EncoderState
|
||||
{
|
||||
public Program RenderProgram = null;
|
||||
public Program ComputeProgram = null;
|
||||
|
||||
public PipelineState Pipeline;
|
||||
public DepthStencilUid DepthStencilUid;
|
||||
|
||||
public readonly record struct ArrayRef<T>(ShaderStage Stage, T Array);
|
||||
|
||||
public readonly BufferRef[] UniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
|
||||
public readonly BufferRef[] StorageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
|
||||
public readonly TextureRef[] TextureRefs = new TextureRef[Constants.MaxTextureBindings * 2];
|
||||
public readonly ImageRef[] ImageRefs = new ImageRef[Constants.MaxImageBindings * 2];
|
||||
|
||||
public ArrayRef<TextureArray>[] TextureArrayRefs = [];
|
||||
public ArrayRef<ImageArray>[] ImageArrayRefs = [];
|
||||
|
||||
public ArrayRef<TextureArray>[] TextureArrayExtraRefs = [];
|
||||
public ArrayRef<ImageArray>[] ImageArrayExtraRefs = [];
|
||||
|
||||
public IndexBufferState IndexBuffer = default;
|
||||
|
||||
public MTLDepthClipMode DepthClipMode = MTLDepthClipMode.Clip;
|
||||
|
||||
public float DepthBias;
|
||||
public float SlopeScale;
|
||||
public float Clamp;
|
||||
|
||||
public int BackRefValue = 0;
|
||||
public int FrontRefValue = 0;
|
||||
|
||||
public PrimitiveTopology Topology = PrimitiveTopology.Triangles;
|
||||
public MTLCullMode CullMode = MTLCullMode.None;
|
||||
public MTLWinding Winding = MTLWinding.CounterClockwise;
|
||||
public bool CullBoth = false;
|
||||
|
||||
public MTLViewport[] Viewports = new MTLViewport[Constants.MaxViewports];
|
||||
public MTLScissorRect[] Scissors = new MTLScissorRect[Constants.MaxViewports];
|
||||
|
||||
// Changes to attachments take recreation!
|
||||
public Texture DepthStencil;
|
||||
public Texture[] RenderTargets = new Texture[Constants.MaxColorAttachments];
|
||||
public ITexture PreMaskDepthStencil = default;
|
||||
public ITexture[] PreMaskRenderTargets;
|
||||
public bool FramebufferUsingColorWriteMask;
|
||||
|
||||
public Array8<ColorBlendStateUid> StoredBlend;
|
||||
public ColorF BlendColor = new();
|
||||
|
||||
public readonly VertexBufferState[] VertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers];
|
||||
public readonly VertexAttribDescriptor[] VertexAttribs = new VertexAttribDescriptor[Constants.MaxVertexAttributes];
|
||||
// Dirty flags
|
||||
public DirtyFlags Dirty = DirtyFlags.None;
|
||||
|
||||
// Only to be used for present
|
||||
public bool ClearLoadAction = false;
|
||||
|
||||
public RenderEncoderBindings RenderEncoderBindings = new();
|
||||
public ComputeEncoderBindings ComputeEncoderBindings = new();
|
||||
|
||||
public EncoderState()
|
||||
{
|
||||
Pipeline.Initialize();
|
||||
DepthStencilUid.DepthCompareFunction = MTLCompareFunction.Always;
|
||||
}
|
||||
|
||||
public RenderTargetCopy InheritForClear(EncoderState other, bool depth, int singleIndex = -1)
|
||||
{
|
||||
// Inherit render target related information without causing a render encoder split.
|
||||
|
||||
RenderTargetCopy oldState = new()
|
||||
{
|
||||
Scissors = other.Scissors,
|
||||
RenderTargets = other.RenderTargets,
|
||||
DepthStencil = other.DepthStencil
|
||||
};
|
||||
|
||||
Scissors = other.Scissors;
|
||||
RenderTargets = other.RenderTargets;
|
||||
DepthStencil = other.DepthStencil;
|
||||
|
||||
Pipeline.ColorBlendAttachmentStateCount = other.Pipeline.ColorBlendAttachmentStateCount;
|
||||
Pipeline.Internal.ColorBlendState = other.Pipeline.Internal.ColorBlendState;
|
||||
Pipeline.DepthStencilFormat = other.Pipeline.DepthStencilFormat;
|
||||
|
||||
ref Array8<ColorBlendStateUid> blendStates = ref Pipeline.Internal.ColorBlendState;
|
||||
|
||||
// Mask out irrelevant attachments.
|
||||
for (int i = 0; i < blendStates.Length; i++)
|
||||
{
|
||||
if (depth || (singleIndex != -1 && singleIndex != i))
|
||||
{
|
||||
blendStates[i].WriteMask = MTLColorWriteMask.None;
|
||||
}
|
||||
}
|
||||
|
||||
return oldState;
|
||||
}
|
||||
|
||||
public void Restore(RenderTargetCopy copy)
|
||||
{
|
||||
Scissors = copy.Scissors;
|
||||
RenderTargets = copy.RenderTargets;
|
||||
DepthStencil = copy.DepthStencil;
|
||||
|
||||
Pipeline.Internal.ResetColorState();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user