Compare commits

..

5 Commits

Author SHA1 Message Date
Evan Husted
f2ade5f1f8 Merge branch 'master' into feature/system-buffer-sharing 2025-02-11 22:55:01 -06:00
Jacobwasbeast
965fb9dd5f Implement Surface Flinger shared layers.
Co-authored-by: Alula <6276139+alula@users.noreply.github.com>
2025-02-07 04:27:01 -06:00
Jacobwasbeast
e2a5e69f4c Implement IHid fix from alula/qlaunch
Co-authored-by: Alula <6276139+alula@users.noreply.github.com>
2025-02-07 04:24:37 -06:00
Jacobwasbeast
7f27b791f8 Refactor Share Buffer Implementation to Follow Code Style Guidelines 2025-02-07 04:16:27 -06:00
Jacobwasbeast
3ca8618f5f Implement changes from gdkchan/buffer-sharing-rebased
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
Co-authored-by: Alula <6276139+alula@users.noreply.github.com>
2025-02-06 03:48:54 -06:00
309 changed files with 21354 additions and 14287 deletions

View File

@@ -29,7 +29,7 @@ env:
jobs:
tag:
name: Create tag
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- name: Get version info
id: version_info
@@ -202,7 +202,7 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4

View File

@@ -18,7 +18,7 @@ env:
jobs:
tag:
name: Create tag
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- name: Get version info
id: version_info
@@ -183,7 +183,7 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4

View File

@@ -42,10 +42,11 @@
<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.1" />
<PackageVersion Include="Gommon" Version="2.7.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" />

View File

@@ -39,12 +39,12 @@
<p align="center">
Click below to join the Discord:
<br>
<a href="https://discord.gg/PEuzjrFXUA">
<a href="https://discord.gg/dHPrkBkkyA">
<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/Ryubing/Ryujinx/refs/heads/master/docs/shell.png">
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
</p>
## Usage

View File

@@ -77,6 +77,15 @@ 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
@@ -86,8 +95,6 @@ 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
@@ -210,10 +217,6 @@ 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
@@ -246,9 +249,16 @@ 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

View File

@@ -969,7 +969,6 @@
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
@@ -1159,7 +1158,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",,playable,2025-02-16 21:27:30
01000EA014150000,"FINAL FANTASY",crash,nothing,2024-09-05 20:55: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
@@ -1250,7 +1249,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;slow;gpu,ingame,2025-02-12 16:03:00
0100E1F013674000,"FUSER™",nvdec;UE4,playable,2022-10-17 20:58:32
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
@@ -1436,7 +1435,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",UE4;slow,ingame,2024-09-03 19:53:58
0100F7E00C70E000,"Hogwarts Legacy",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
@@ -1800,7 +1799,6 @@
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
@@ -2840,7 +2838,6 @@
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
,"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
@@ -2991,8 +2988,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
01006fe0096ac000,"The Jackbox Party Pack 5",ldn-untested,boots,2025-02-03 22:32:00
01005a400db52000,"The Jackbox Party Pack 6",ldn-untested,boots,2025-02-03 22:32:00
010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41
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
1 title_id game_name labels status last_updated
969 0100751007ADA000 Don't Starve: Nintendo Switch Edition nvdec playable 2022-02-05 20:43:34
970 010088B010DD2000 Dongo Adventure playable 2022-10-04 16:22:26
971 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
972 0100F2C00F060000 Doodle Derby boots 2020-12-04 22:51:48
973 0100416004C00000 DOOM gpu;slow;nvdec;online-broken ingame 2024-09-23 15:40:07
974 010018900DD00000 DOOM (1993) nvdec;online-broken menus 2022-09-06 13:32:19
1158 010095600AA36000 Fill-a-Pix: Phil's Epic Adventure playable 2020-12-22 13:48:22
1159 0100C3A00BB76000 Fimbul nvdec playable 2022-07-26 13:31:47
1160 0100C8200E942000 Fin and the Ancient Mystery nvdec playable 2020-12-17 16:40:39
1161 01000EA014150000 FINAL FANTASY crash playable nothing 2025-02-16 21:27:30 2024-09-05 20:55:30
1162 01006B7014156000 FINAL FANTASY II crash nothing 2024-04-13 19:18:04
1163 01006F000B056000 FINAL FANTASY IX audout;nvdec playable 2021-06-05 11:35:00
1164 0100AA201415C000 FINAL FANTASY V playable 2023-04-26 01:11:55
1249 0100A6B00D4EC000 Furwind playable 2021-02-19 19:44:08
1250 0100ECE00C0C4000 Fury Unleashed crash;services ingame 2020-10-18 11:52:40
1251 010070000ED9E000 Fury Unleashed Demo playable 2020-10-08 20:09:21
1252 0100E1F013674000 FUSER™ nvdec;UE4;slow;gpu nvdec;UE4 ingame playable 2025-02-12 16:03:00 2022-10-17 20:58:32
1253 0100A7A015E4C000 Fushigi no Gensokyo Lotus Labyrinth Needs Update;audio;gpu;nvdec ingame 2021-01-20 15:30:02
1254 01003C300B274000 Futari de! Nyanko Daisensou playable 2024-01-05 22:26:52
1255 010055801134E000 FUZE Player online-broken;vulkan-backend-bug ingame 2022-10-18 12:23:53
1435 010083A018262000 Hitman: Blood Money — Reprisal deadlock ingame 2024-09-28 16:28:50
1436 01004B100A5CC000 Hob: The Definitive Edition playable 2021-01-13 09:39:19
1437 0100F7300ED2C000 Hoggy2 playable 2022-10-10 13:53:35
1438 0100F7E00C70E000 Hogwarts Legacy UE4;slow slow ingame 2024-09-03 19:53:58
1439 0100633007D48000 Hollow Knight nvdec playable 2023-01-16 15:44:56
1440 0100F2100061E800 Hollow0 UE4;gpu ingame 2021-03-03 23:42:56
1441 0100342009E16000 Holy Potatoes! What The Hell?! playable 2020-07-03 10:48:56
1799 010005A00B312000 Megaton Rainfall gpu;opengl boots 2022-08-04 18:29:43
1800 0100EA100DF92000 Meiji Katsugeki Haikara Ryuuseigumi - Seibai Shimaseu, Yonaoshi Kagyou 32-bit;nvdec playable 2022-12-05 13:19:12
1801 0100B360068B2000 Mekorama gpu boots 2021-06-17 16:37:21
010012301932A000 Melatonin playable 2025-02-16 04:08:17
1802 01000FA010340000 Melbits World nvdec;online menus 2021-11-26 13:51:22
1803 0100F68019636000 Melon Journey playable 2023-04-23 21:20:01
1804 010079C012896000 Memories Off -Innocent Fille- for Dearest playable 2020-08-04 07:31:22
2838 01009B90006DC000 Super Mario Maker™ 2 online-broken;ldn-broken playable 2024-08-25 11:05:19
2839 0100000000010000 Super Mario Odyssey™ nvdec;intel-vendor-bug;mac-bug playable 2024-08-25 01:32:34
2840 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
2841 0100BC0018138000 Super Mario RPG™ gpu;audio;nvdec ingame 2024-06-19 17:43:42
2842 Super Mario World homebrew boots 2024-06-13 01:40:31
2843 010049900F546000 Super Mario™ 3D All-Stars services-horizon;slow;vulkan;amd-vendor-bug ingame 2024-05-07 02:38:16
2988 010015D003EE4000 The Jackbox Party Pack 2 online-working playable 2022-08-22 18:23:40
2989 0100CC80013D6000 The Jackbox Party Pack 3 slow;online-working playable 2022-08-22 18:41:06
2990 0100E1F003EE8000 The Jackbox Party Pack 4 online-working playable 2022-08-22 18:56:34
2991 01006fe0096ac000 The Jackbox Party Pack 5 slow;online-working ldn-untested ingame boots 2025-02-14 05:32:00 2025-02-03 22:32:00
2992 01005a400db52000 The Jackbox Party Pack 6 slow;online-working ldn-untested ingame boots 2025-02-14 05:26:00 2025-02-03 22:32:00
2993 010052C00B184000 The Journey Down: Chapter One nvdec playable 2021-02-24 13:32:41
2994 01006BC00B188000 The Journey Down: Chapter Three nvdec playable 2021-02-24 13:45:27
2995 01009AB00B186000 The Journey Down: Chapter Two nvdec playable 2021-02-24 13:32:13

View File

@@ -24,14 +24,14 @@ namespace ARMeilleure.Translation.Cache
private static JitCacheInvalidation _jitCacheInvalidator;
private static List<CacheMemoryAllocator> _cacheAllocators = [];
private static CacheMemoryAllocator _cacheAllocator;
private static readonly List<CacheEntry> _cacheEntries = [];
private static readonly Lock _lock = new();
private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = [];
private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")]
@@ -40,48 +40,37 @@ namespace ARMeilleure.Translation.Cache
public static void Initialize(IJitMemoryAllocator allocator)
{
if (_initialized)
{
return;
}
lock (_lock)
{
if (_initialized)
{
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.RemoveFunctionTableHandler(
_jitRegions[0].Pointer);
}
for (int i = 0; i < _jitRegions.Count; i++)
{
_jitRegions[i].Dispose();
}
_jitRegions.Clear();
_cacheAllocators.Clear();
return;
}
else
{
_initialized = true;
}
_activeRegionIndex = 0;
ReservedRegion firstRegion = new(allocator, CacheSize);
_jitRegions.Add(firstRegion);
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
_cacheAllocators.Add(firstCacheAllocator);
_activeRegionIndex = 0;
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
}
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
);
}
_initialized = true;
}
}
@@ -147,7 +136,7 @@ namespace ARMeilleure.Translation.Cache
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}
@@ -178,24 +167,30 @@ namespace ARMeilleure.Translation.Cache
{
codeSize = AlignCodeSize(codeSize);
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
if (allocOffset >= 0)
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
{
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
return allocOffset;
int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0)
{
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset;
}
}
int exhaustedRegion = _activeRegionIndex;
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation).");
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
if (allocOffsetNew < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");

View File

@@ -52,11 +52,6 @@ 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;
@@ -96,23 +91,6 @@ 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());

View File

@@ -6,7 +6,9 @@ namespace Ryujinx.Common.Configuration
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
public enum GraphicsBackend
{
Auto,
Vulkan,
OpenGl,
Metal
}
}

View File

@@ -5,34 +5,15 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Common.Helper
{
public enum OperatingSystemType
{
MacOS,
Linux,
Windows
}
public static class RunningPlatform
{
public static readonly OperatingSystemType CurrentOS
= IsMacOS
? OperatingSystemType.MacOS
: IsWindows
? OperatingSystemType.Windows
: IsLinux
? OperatingSystemType.Linux
: throw new PlatformNotSupportedException();
public static Architecture Architecture => RuntimeInformation.OSArchitecture;
public static Architecture CurrentProcessArchitecture => RuntimeInformation.ProcessArchitecture;
public static bool IsMacOS => OperatingSystem.IsMacOS();
public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsArm => Architecture is Architecture.Arm64;
public static bool IsArm => RuntimeInformation.OSArchitecture is Architecture.Arm64;
public static bool IsX64 => Architecture is Architecture.X64;
public static bool IsX64 => RuntimeInformation.OSArchitecture is Architecture.X64;
public static bool IsIntelMac => IsMacOS && IsX64;
public static bool IsArmMac => IsMacOS && IsArm;

View File

@@ -1,9 +0,0 @@
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";
}
}

View File

@@ -10,6 +10,55 @@ 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 Royal
"0100b880154fc000", // Persona 5 The Royal (Japan)
"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";
@@ -181,7 +230,6 @@ 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

View File

@@ -27,7 +27,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private static readonly Lock _lock = new();
private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = [];
private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")]
@@ -166,7 +166,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
int newRegionNumber = _activeRegionIndex;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize);

View File

@@ -0,0 +1,18 @@
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;
}
}
}

View File

@@ -339,6 +339,84 @@ 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>

View File

@@ -4,23 +4,22 @@ 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, ProgramPipelineState state, bool fromCache = false)
public ShaderInfo(
int fragmentOutputMap,
ResourceLayout resourceLayout,
ComputeSize computeLocalSize,
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;
}
}
}

View File

@@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
// Make sure all pending uniform buffer data is written to memory.
_3dEngine.FlushUboDirty();
uint qmdAddress = _state.State.SendPcasA;
ComputeQmd qmd = _channel.MemoryManager.Read<ComputeQmd>((ulong)qmdAddress << 8);
@@ -106,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ulong shaderGpuVa = ((ulong)_state.State.SetProgramRegionAAddressUpper << 32) | _state.State.SetProgramRegionB;
shaderGpuVa += (uint)qmd.ProgramOffset;
ShaderCache shaderCache = memoryManager.GetBackingMemory(shaderGpuVa).ShaderCache;
int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize;
@@ -142,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
CachedShaderProgram cs = shaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
@@ -156,10 +158,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
{
BufferDescriptor sb = info.SBuffers[index];
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
(PhysicalMemory physical, ulong sbDescAddress) = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
sbDescAddress += (ulong)sb.SbCbOffset * 4;
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
SbDescriptor sbDescriptor = physical.Read<SbDescriptor>(sbDescAddress);
uint size;
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
@@ -187,7 +189,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
cs = shaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
}

View File

@@ -215,7 +215,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
_channel.TextureManager.RefreshModifiedTextures();
_3dEngine.CreatePendingSyncs();
_3dEngine.FlushUboDirty();
PhysicalMemory srcPhysical = memoryManager.GetBackingMemory(srcGpuVa);
PhysicalMemory dstPhysical = memoryManager.GetBackingMemory(dstGpuVa);
if (copy2D)
{
// Buffer to texture copy.
@@ -293,7 +296,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
if (completeSource && completeDest && !srcLinear && isIdentityRemap)
{
Image.Texture source = memoryManager.Physical.TextureCache.FindTexture(
Image.Texture source = srcPhysical.TextureCache.FindTexture(
memoryManager,
srcGpuVa,
srcBpp,
@@ -309,7 +312,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
{
source.SynchronizeMemory();
Image.Texture target = memoryManager.Physical.TextureCache.FindOrCreateTexture(
Image.Texture target = dstPhysical.TextureCache.FindOrCreateTexture(
memoryManager,
source.Info.FormatInfo,
dstGpuVa,
@@ -339,7 +342,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap)
{
Image.Texture target = memoryManager.Physical.TextureCache.FindTexture(
Image.Texture target = dstPhysical.TextureCache.FindTexture(
memoryManager,
dstGpuVa,
dstBpp,
@@ -462,6 +465,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
}
else
{
BufferCache bufferCache = dstPhysical.BufferCache;
if (remap &&
_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.ConstA &&
_state.State.SetRemapComponentsDstY == SetRemapComponentsDst.ConstA &&
@@ -472,7 +476,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
_state.State.SetRemapComponentsComponentSize == SetRemapComponentsComponentSize.Four)
{
// Fast path for clears when remap is enabled.
memoryManager.Physical.BufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
bufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
}
else
{
@@ -492,7 +496,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
}
else
{
memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size);
BufferCache.CopyBuffer(_context,memoryManager, srcGpuVa, dstGpuVa, size);
}
}
}

View File

@@ -185,7 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
// Right now the copy code at the bottom assumes that it is used on both which might be incorrect.
if (!_isLinear)
{
Image.Texture target = memoryManager.Physical.TextureCache.FindTexture(
Image.Texture target = memoryManager.GetBackingMemory(_dstGpuVa).TextureCache.FindTexture(
memoryManager,
_dstGpuVa,
1,

View File

@@ -384,7 +384,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
ulong indirectBufferGpuVa = count.GpuVa;
BufferCache bufferCache = _processor.MemoryManager.Physical.BufferCache;
BufferCache bufferCache = _processor.MemoryManager.GetBackingMemory(indirectBufferGpuVa).BufferCache;
bool useBuffer = bufferCache.CheckModified(_processor.MemoryManager, indirectBufferGpuVa, IndirectIndexedDataEntrySize, out ulong indirectBufferAddress);
@@ -394,6 +394,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
_processor.ThreedClass.DrawIndirect(
topology,
bufferCache,
null,
new MultiRange(indirectBufferAddress, IndirectIndexedDataEntrySize),
default,
1,
@@ -491,22 +493,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
}
}
}
BufferCache bufferCache = _processor.MemoryManager.Physical.BufferCache;
BufferCache indirectBufferCache = _processor.MemoryManager.GetBackingMemory(indirectBufferGpuVa).BufferCache;
BufferCache parameterBufferCache = _processor.MemoryManager.GetBackingMemory(parameterBufferGpuVa).BufferCache;
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
MultiRange indirectBufferRange = indirectBufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
MultiRange parameterBufferRange = parameterBufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
_processor.ThreedClass.DrawIndirect(
topology,
indirectBufferCache,
parameterBufferCache,
indirectBufferRange,
parameterBufferRange,
maxDrawCount,
stride,
indexCount,
Threed.IndirectDrawType.DrawIndexedIndirectCount);
IndirectDrawType.DrawIndexedIndirectCount);
}
/// <summary>

View File

@@ -200,11 +200,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
_context.Renderer.Pipeline.SetUniformBuffers([new BufferAssignment(vertexInfoBinding, vertexInfoRange)]);
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: true);
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexDataRange)]);
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
_vacContext.VertexInfoBufferUpdater.Commit();
@@ -232,7 +232,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
_context.Renderer.Pipeline.SetUniformBuffers([new BufferAssignment(vertexInfoBinding, vertexInfoRange)]);
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
@@ -250,11 +250,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: true);
BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: true);
_context.Renderer.Pipeline.SetStorageBuffers([
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[]
{
new BufferAssignment(vertexDataBinding, vertexDataRange),
new BufferAssignment(geometryVbBinding, vertexBuffer),
new BufferAssignment(geometryIbBinding, indexBuffer)
]);
new BufferAssignment(geometryIbBinding, indexBuffer),
});
_context.Renderer.Pipeline.DispatchCompute(
BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize),
@@ -298,7 +299,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
_context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt);
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexBuffer)]);
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexBuffer) });
_context.Renderer.Pipeline.SetPrimitiveRestart(true, -1);
_context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive));
@@ -313,7 +314,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false);
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexDataRange)]);
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
_context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0);
}
}
@@ -370,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{
MemoryManager memoryManager = _channel.MemoryManager;
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
BufferRange range = memoryManager.GetBackingMemory(address).BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
bufferTexture.SetStorage(range);
@@ -412,7 +413,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
MemoryManager memoryManager = _channel.MemoryManager;
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(
BufferRange range = memoryManager.GetBackingMemory(address).BufferCache.GetBufferRange(
memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
BufferStage.IndexBuffer);
misalignedOffset = (int)misalign >> shift;

View File

@@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// State associated with direct uniform buffer updates.
// This state is used to attempt to batch together consecutive updates.
private ulong _ubBeginGpuAddress = 0;
private ulong _ubBeginCpuAddress = 0;
private ulong _ubFollowUpAddress = 0;
private ulong _ubByteCount = 0;
@@ -113,12 +114,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (_ubFollowUpAddress != 0)
{
MemoryManager memoryManager = _channel.MemoryManager;
PhysicalMemory physicalMemory = memoryManager.GetBackingMemory(_ubBeginGpuAddress);
Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
if (physicalMemory.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
{
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
physicalMemory.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
}
_ubFollowUpAddress = 0;

View File

@@ -641,6 +641,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public void DrawIndirect(
ThreedClass engine,
PrimitiveTopology topology,
BufferCache indirectBufferCache,
BufferCache parameterBufferCache,
MultiRange indirectBufferRange,
MultiRange parameterBufferRange,
int maxDrawCount,
@@ -662,8 +664,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return;
}
PhysicalMemory memory = _channel.MemoryManager.Physical;
bool hasCount = (drawType & IndirectDrawType.Count) != 0;
bool indexed = (drawType & IndirectDrawType.Indexed) != 0;
@@ -684,8 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (hasCount)
{
BufferRange indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
BufferRange parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
BufferRange indirectBuffer = indirectBufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
BufferRange parameterBuffer = parameterBufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
if (indexed)
{
@@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
else
{
BufferRange indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
BufferRange indirectBuffer = indirectBufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
if (indexed)
{
@@ -913,7 +913,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
Span<Rectangle<int>> scissors =
[
new(scissorX, scissorY, scissorW, scissorH)
new Rectangle<int>(scissorX, scissorY, scissorW, scissorH)
];
_context.Renderer.Pipeline.SetScissors(scissors);

View File

@@ -381,10 +381,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
BufferDescriptor sb = info.SBuffers[index];
ulong sbDescAddress = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
(PhysicalMemory physical, ulong sbDescAddress) = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
sbDescAddress += (ulong)sb.SbCbOffset * 4;
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
SbDescriptor sbDescriptor = physical.Read<SbDescriptor>(sbDescAddress);
uint size;
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
@@ -505,7 +505,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
rtNoAlphaMask |= 1u << index;
}
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
TextureCache colorTextureCache = memoryManager.GetBackingMemory(colorState.Address.Pack()).TextureCache;
Image.Texture color = colorTextureCache.FindOrCreateTexture(
memoryManager,
colorState,
_vtgWritesRtLayer || layered,
@@ -513,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
samplesInX,
samplesInY,
sizeHint);
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
if (color != null)
@@ -543,8 +545,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
RtDepthStencilState dsState = _state.State.RtDepthStencilState;
Size3D dsSize = _state.State.RtDepthStencilSize;
depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture(
TextureCache dsTextureCache = memoryManager.GetBackingMemory(dsState.Address.Pack()).TextureCache;
depthStencil = dsTextureCache.FindOrCreateTexture(
memoryManager,
dsState,
dsSize,
@@ -1409,8 +1412,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private void UpdateShaderState()
{
ShaderCache shaderCache = _channel.MemoryManager.Physical.ShaderCache;
_vtgWritesRtLayer = false;
ShaderAddresses addresses = new();
@@ -1433,6 +1434,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
? _state.State.TexturePoolState.MaximumId
: _state.State.SamplerPoolState.MaximumId;
// Shader stages on different address spaces are not supported right now,
// but it should never happen in practice anyway.
ShaderCache shaderCache = _channel.MemoryManager.GetBackingMemory(addresses.VertexB).ShaderCache;
CachedShaderProgram gs = shaderCache.GetGraphicsShader(
ref _state.State,
ref _pipeline,

View File

@@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.Memory.Range;
using System;
@@ -804,6 +805,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// Performs a indirect draw, with parameters from a GPU buffer.
/// </summary>
/// <param name="topology">Primitive topology</param>
/// <param name="indirectBufferCache">Buffer cache owning the buffer with the draw parameters</param>
/// <param name="parameterBufferCache">Buffer cache owning the buffer with the draw count</param>
/// <param name="indirectBufferRange">Memory range of the buffer with the draw parameters, such as count, first index, etc</param>
/// <param name="parameterBufferRange">Memory range of the buffer with the draw count</param>
/// <param name="maxDrawCount">Maximum number of draws that can be made</param>
@@ -812,6 +815,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// <param name="drawType">Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count</param>
public void DrawIndirect(
PrimitiveTopology topology,
BufferCache indirectBufferCache,
BufferCache parameterBufferCache,
MultiRange indirectBufferRange,
MultiRange parameterBufferRange,
int maxDrawCount,
@@ -819,7 +824,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
int indexCount,
IndirectDrawType drawType)
{
_drawManager.DrawIndirect(this, topology, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType);
_drawManager.DrawIndirect(this, topology, indirectBufferCache, parameterBufferCache, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType);
}
/// <summary>

View File

@@ -233,6 +233,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
TwodTexture dstCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetDstFormat);
TwodTexture srcCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetSrcFormat);
TextureCache srcTextureCache = memoryManager.GetBackingMemory(srcCopyTexture.Address.Pack()).TextureCache;
TextureCache dstTextureCache = memoryManager.GetBackingMemory(dstCopyTexture.Address.Pack()).TextureCache;
long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac;
long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac;
@@ -305,7 +308,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
// are the same, as we can't blit between different depth formats.
bool srcDepthAlias = srcCopyTexture.Format == dstCopyTexture.Format;
Image.Texture srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
Image.Texture srcTexture = srcTextureCache.FindOrCreateTexture(
memoryManager,
srcCopyTexture,
offset,
@@ -326,7 +329,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
return;
}
memoryManager.Physical.TextureCache.Lift(srcTexture);
srcTextureCache.Lift(srcTexture);
// When the source texture that was found has a depth format,
// we must enforce the target texture also has a depth format,
@@ -342,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
dstCopyTextureFormat = dstCopyTexture.Format.Convert();
}
Image.Texture dstTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
Image.Texture dstTexture = dstTextureCache.FindOrCreateTexture(
memoryManager,
dstCopyTexture,
0,

View File

@@ -58,22 +58,24 @@ namespace Ryujinx.Graphics.Gpu
public void BindMemory(MemoryManager memoryManager)
{
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, memoryManager ?? throw new ArgumentNullException(nameof(memoryManager)));
if (oldMemoryManager == memoryManager)
{
return;
}
memoryManager.Physical.IncrementReferenceCount();
memoryManager.AttachToChannel(BufferManager.Rebind);
if (oldMemoryManager != null)
{
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
oldMemoryManager.Physical.DecrementReferenceCount();
oldMemoryManager.DetachFromChannel(BufferManager.Rebind);
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
}
memoryManager.Physical.BufferCache.NotifyBuffersModified += BufferManager.Rebind;
memoryManager.MemoryUnmapped += MemoryUnmappedHandler;
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
TextureManager.ReloadPools();
memoryManager.Physical.BufferCache.QueuePrune();
memoryManager.QueuePrune();
}
/// <summary>
@@ -86,7 +88,7 @@ namespace Ryujinx.Graphics.Gpu
TextureManager.ReloadPools();
MemoryManager memoryManager = Volatile.Read(ref _memoryManager);
memoryManager?.Physical.BufferCache.QueuePrune();
memoryManager?.QueuePrune();
}
/// <summary>
@@ -141,8 +143,7 @@ namespace Ryujinx.Graphics.Gpu
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
if (oldMemoryManager != null)
{
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
oldMemoryManager.Physical.DecrementReferenceCount();
oldMemoryManager.DetachFromChannel(BufferManager.Rebind);
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
}
}

View File

@@ -6,6 +6,7 @@ using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -172,7 +173,7 @@ namespace Ryujinx.Graphics.Gpu
throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
}
return new MemoryManager(physicalMemory, cpuMemorySize);
return new MemoryManager(this, physicalMemory, cpuMemorySize);
}
/// <summary>
@@ -197,7 +198,7 @@ namespace Ryujinx.Graphics.Gpu
/// <param name="pid">ID of the process that owns <paramref name="cpuMemory"/></param>
/// <param name="cpuMemory">Virtual memory owned by the process</param>
/// <exception cref="ArgumentException">Thrown if <paramref name="pid"/> was already registered</exception>
public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory)
public void RegisterProcess(ulong pid, IVirtualMemoryManagerTracked cpuMemory)
{
PhysicalMemory physicalMemory = new(this, cpuMemory);
if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory))

View File

@@ -1,3 +1,4 @@
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Collections.Generic;
@@ -64,7 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <param name="bindingsArrayCache">Cache of texture array bindings</param>
/// <returns>The found or newly created texture pool</returns>
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
public T FindOrCreate(GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
{
// Remove old entries from the cache, if possible.
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
@@ -99,7 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
// If not found, create a new one.
pool = CreatePool(_context, channel, address, maximumId);
pool = CreatePool(_context, channel, physicalMemory, address, maximumId);
pool.CacheNode = _pools.AddLast(pool);
pool.CacheTimestamp = _currentTimestamp;
@@ -112,9 +113,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="context">GPU context that the pool belongs to</param>
/// <param name="channel">GPU channel that the pool belongs to</param>
/// <param name="physicalMemory">GPU backing memory of the pool</param>
/// <param name="address">Address of the pool in guest memory</param>
/// <param name="maximumId">Maximum ID of the pool (equal to maximum minus one)</param>
protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId);
protected abstract T CreatePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId);
public void Dispose()
{

View File

@@ -1,3 +1,5 @@
using Ryujinx.Graphics.Gpu.Memory;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
@@ -20,11 +22,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="context">GPU context that the sampler pool belongs to</param>
/// <param name="channel">GPU channel that the texture pool belongs to</param>
/// <param name="physicalMemory">GPU backing memory of the pool</param>
/// <param name="address">Address of the sampler pool in guest memory</param>
/// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId)
{
return new SamplerPool(context, channel.MemoryManager.Physical, address, maximumId);
return new SamplerPool(context, physicalMemory, address, maximumId);
}
}
}

View File

@@ -660,6 +660,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
BufferCache bufferCache = null;
for (int index = 0; index < length; index++)
{
@@ -673,7 +674,7 @@ namespace Ryujinx.Graphics.Gpu.Image
else
{
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, bindingInfo.FormatInfo, out texture);
bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
if (texture != null)
{
entry.Textures[texture] = texture.InvalidatedSequence;
@@ -702,11 +703,10 @@ namespace Ryujinx.Graphics.Gpu.Image
// to ensure we're not using a old buffer that was already deleted.
if (isImage)
{
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
}
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, bufferCache, texture.Range, bindingInfo, index); }
else
{
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
}
}
else if (isImage)
@@ -797,11 +797,11 @@ namespace Ryujinx.Graphics.Gpu.Image
return;
}
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(textureBufferBounds.Physical.GetSpan(textureBufferBounds.Range));
if (separateSamplerBuffer)
{
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(samplerBufferBounds.Physical.GetSpan(samplerBufferBounds.Range));
}
else
{
@@ -828,11 +828,10 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(textureBufferBounds.Physical.GetSpan(textureBufferBounds.Range));
if (separateSamplerBuffer)
{
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(samplerBufferBounds.Physical.GetSpan(samplerBufferBounds.Range));
}
else
{
@@ -901,16 +900,18 @@ namespace Ryujinx.Graphics.Gpu.Image
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
BufferCache bufferCache = textureBufferBounds.BufferCache;
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
if (isImage)
{
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
}
else
{
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
}
}
else if (isImage)

View File

@@ -396,7 +396,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Range));
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
cachedTextureBufferIndex = textureBufferIndex;
if (samplerBufferIndex == textureBufferIndex)
@@ -410,7 +410,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Range));
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
cachedSamplerBufferIndex = samplerBufferIndex;
}
}
@@ -524,7 +524,8 @@ namespace Ryujinx.Graphics.Gpu.Image
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false);
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, bufferCache, texture.Range, bindingInfo, false);
// Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null;
@@ -659,7 +660,8 @@ namespace Ryujinx.Graphics.Gpu.Image
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true);
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, bufferCache, texture.Range, bindingInfo, true);
// Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null;
@@ -715,9 +717,10 @@ namespace Ryujinx.Graphics.Gpu.Image
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
int textureId = TextureHandle.UnpackTextureId(packedId);
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(poolGpuVa);
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, maximumId, _bindingsArrayCache);
TextureDescriptor descriptor;
@@ -751,12 +754,12 @@ namespace Ryujinx.Graphics.Gpu.Image
{
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
ulong textureBufferAddress = _isCompute
(PhysicalMemory texturePhysicalMemory, ulong textureBufferAddress) = _isCompute
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
int handle = textureBufferAddress != MemoryManager.PteUnmapped
? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
? texturePhysicalMemory.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
: 0;
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
@@ -771,12 +774,12 @@ namespace Ryujinx.Graphics.Gpu.Image
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
{
ulong samplerBufferAddress = _isCompute
(PhysicalMemory samplerPhysicalMemory, ulong samplerBufferAddress) = _isCompute
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped
? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
? samplerPhysicalMemory.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
: 0;
}
else
@@ -813,7 +816,8 @@ namespace Ryujinx.Graphics.Gpu.Image
if (poolAddress != MemoryManager.PteUnmapped)
{
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(_texturePoolGpuVa);
texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
_texturePool = texturePool;
}
}
@@ -824,7 +828,8 @@ namespace Ryujinx.Graphics.Gpu.Image
if (poolAddress != MemoryManager.PteUnmapped)
{
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(_samplerPoolGpuVa);
samplerPool = _samplerPoolCache.FindOrCreate(_channel, physical, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
_samplerPool = samplerPool;
}
}

View File

@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using System;
@@ -385,8 +386,9 @@ namespace Ryujinx.Graphics.Gpu.Image
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
{
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(poolAddress);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, maximumId, _bindingsArrayCache);
return texturePool;
}

View File

@@ -160,9 +160,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="context">GPU context that the texture pool belongs to</param>
/// <param name="channel">GPU channel that the texture pool belongs to</param>
/// <param name="physicalMemory">Backing memory of the pool</param>
/// <param name="address">Address of the texture pool in guest memory</param>
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
public TexturePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId) : base(context, physicalMemory, address, maximumId)
{
_channel = channel;
_aliasLists = new Dictionary<Texture, TextureAliasList>();
@@ -193,7 +194,9 @@ namespace Ryujinx.Graphics.Gpu.Image
}
TextureInfo info = GetInfo(descriptor, out int layerSize);
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
MemoryManager memoryManager = _channel.MemoryManager;
TextureCache textureCache = memoryManager.GetBackingMemory(descriptor.UnpackAddress()).TextureCache;
texture = textureCache.FindOrCreateTexture(memoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
@@ -421,7 +424,8 @@ namespace Ryujinx.Graphics.Gpu.Image
continue;
}
MultiRange range = _channel.MemoryManager.Physical.TextureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
TextureCache textureCache = _channel.MemoryManager.GetBackingMemory(address).TextureCache;
MultiRange range = textureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
// If the texture is not mapped at all, delete its reference.
@@ -446,7 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!range.Equals(texture.Range))
{
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
if (!textureCache.UpdateMapping(texture, range))
{
// Texture could not be remapped due to a collision, just delete it.
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
@@ -481,6 +485,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="size">Size of the range being invalidated</param>
protected override void InvalidateRangeImpl(ulong address, ulong size)
{
MemoryManager memoryManager = _channel.MemoryManager;
ProcessDereferenceQueue();
ulong endAddress = address + size;
@@ -505,7 +510,8 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture.HasOneReference())
{
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
TextureCache textureCache = memoryManager.GetBackingMemory(descriptor.UnpackAddress()).TextureCache;
textureCache.AddShortCache(texture, ref cachedDescriptor);
}
if (Interlocked.Exchange(ref Items[id], null) != null)

View File

@@ -1,3 +1,5 @@
using Ryujinx.Graphics.Gpu.Memory;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
@@ -20,11 +22,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="context">GPU context that the texture pool belongs to</param>
/// <param name="channel">GPU channel that the texture pool belongs to</param>
/// <param name="physicalMemory">Backing memory of the pool</param>
/// <param name="address">Address of the texture pool in guest memory</param>
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
protected override TexturePool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
protected override TexturePool CreatePool(
GpuContext context,
GpuChannel channel,
PhysicalMemory physicalMemory,
ulong address,
int maximumId)
{
return new TexturePool(context, channel, address, maximumId);
return new TexturePool(context, channel, physicalMemory, address, maximumId);
}
}
}

View File

@@ -9,6 +9,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
readonly struct BufferBounds : IEquatable<BufferBounds>
{
/// <summary>
/// Physical memory backing the buffer.
/// </summary>
public PhysicalMemory Physical { get; }
/// <summary>
/// Buffer cache that owns the buffer.
/// </summary>
public BufferCache BufferCache => Physical.BufferCache;
/// <summary>
/// Physical memory ranges where the buffer is mapped.
/// </summary>
@@ -29,8 +39,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
/// <param name="range">Physical memory ranges where the buffer is mapped</param>
/// <param name="flags">Buffer usage flags</param>
public BufferBounds(MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
public BufferBounds(PhysicalMemory physical, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
{
Physical = physical;
Range = range;
Flags = flags;
}

View File

@@ -735,18 +735,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <remarks>
/// This does a GPU side copy.
/// </remarks>
/// <param name="context">GPU context</param>
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="srcVa">GPU virtual address of the copy source</param>
/// <param name="dstVa">GPU virtual address of the copy destination</param>
/// <param name="size">Size in bytes of the copy</param>
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
public static void CopyBuffer(GpuContext context, MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
{
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
PhysicalMemory srcPhysical = memoryManager.GetBackingMemory(srcVa);
PhysicalMemory dstPhysical = memoryManager.GetBackingMemory(dstVa);
MultiRange srcRange = srcPhysical.BufferCache.TranslateAndCreateBuffer(memoryManager, srcVa, size, BufferStage.Copy);
MultiRange dstRange = dstPhysical.BufferCache.TranslateAndCreateBuffer(memoryManager, dstVa, size, BufferStage.Copy);
if (srcRange.Count == 1 && dstRange.Count == 1)
{
CopyBufferSingleRange(memoryManager, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
CopyBufferSingleRange(context, srcPhysical, dstPhysical, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
}
else
{
@@ -777,7 +781,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong dstSize = dstSubRange.Size - dstOffset;
ulong copySize = Math.Min(srcSize, dstSize);
CopyBufferSingleRange(memoryManager, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
CopyBufferSingleRange(context, srcPhysical, dstPhysical, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
srcOffset += copySize;
dstOffset += copySize;
@@ -793,18 +797,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// This does a GPU side copy.
/// </remarks>
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="srcPhysical">Physical memory backing the source buffer.</param>
/// <param name="dstPhysical">Physical memory backing the destination buffer.</param>
/// <param name="srcAddress">Physical address of the copy source</param>
/// <param name="dstAddress">Physical address of the copy destination</param>
/// <param name="size">Size in bytes of the copy</param>
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
private static void CopyBufferSingleRange(
GpuContext context,
PhysicalMemory srcPhysical,
PhysicalMemory dstPhysical,
ulong srcAddress,
ulong dstAddress,
ulong size)
{
Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy);
Buffer srcBuffer = srcPhysical.BufferCache.GetBuffer(srcAddress, size, BufferStage.Copy);
Buffer dstBuffer = dstPhysical.BufferCache.GetBuffer(dstAddress, size, BufferStage.Copy);
int srcOffset = (int)(srcAddress - srcBuffer.Address);
int dstOffset = (int)(dstAddress - dstBuffer.Address);
_context.Renderer.Pipeline.CopyBuffer(
context.Renderer.Pipeline.CopyBuffer(
srcBuffer.Handle,
dstBuffer.Handle,
srcOffset,
@@ -820,7 +832,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU.
dstBuffer.ClearModified(dstAddress, size);
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
dstPhysical.WriteTrackedResource(dstAddress, srcPhysical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
}
dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
@@ -849,7 +861,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
memoryManager.GetBackingMemory(gpuVa).FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
}

View File

@@ -66,18 +66,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
Buffers = new BufferBounds[count];
Unaligned = new bool[count];
Buffers.AsSpan().Fill(new BufferBounds(new MultiRange(MemoryManager.PteUnmapped, 0UL)));
Buffers.AsSpan().Fill(new BufferBounds(null, new MultiRange(MemoryManager.PteUnmapped, 0UL)));
}
/// <summary>
/// Sets the region of a buffer at a given slot.
/// </summary>
/// <param name="index">Buffer slot</param>
/// <param name="physical">Physical memory backing the buffer</param>
/// <param name="range">Physical memory regions where the buffer is mapped</param>
/// <param name="flags">Buffer usage flags</param>
public void SetBounds(int index, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
public void SetBounds(int index, PhysicalMemory physical, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
{
Buffers[index] = new BufferBounds(range, flags);
Buffers[index] = new BufferBounds(physical, range, flags);
}
/// <summary>
@@ -156,8 +157,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="type">Type of each index buffer element</param>
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
{
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache;
MultiRange range = bufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
_indexBuffer.BufferCache = bufferCache;
_indexBuffer.Range = range;
_indexBuffer.Type = type;
@@ -186,11 +189,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param>
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
{
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache;
MultiRange range = bufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
_vertexBuffers[index].Range = range;
_vertexBuffers[index].Stride = stride;
_vertexBuffers[index].Divisor = divisor;
ref VertexBuffer vb = ref _vertexBuffers[index];
vb.BufferCache = bufferCache;
vb.Range = range;
vb.Stride = stride;
vb.Divisor = divisor;
_vertexBuffersDirty = true;
@@ -213,9 +220,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the transform feedback buffer</param>
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
{
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
_transformFeedbackBuffers[index] = new BufferBounds(range);
_transformFeedbackBuffers[index] = new BufferBounds(physical, range);
_transformFeedbackBuffersDirty = true;
}
@@ -258,11 +266,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
RecordStorageAlignment(_cpStorageBuffers, index, gpuVa);
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
_cpStorageBuffers.SetBounds(index, range, flags);
_cpStorageBuffers.SetBounds(index, physical, range, flags);
}
/// <summary>
@@ -282,16 +291,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
RecordStorageAlignment(buffers, index, gpuVa);
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
if (!buffers.Buffers[index].Range.Equals(range))
{
_gpStorageBuffersDirty = true;
}
buffers.SetBounds(index, range, flags);
buffers.SetBounds(index, physical, range, flags);
}
/// <summary>
@@ -303,9 +313,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the storage buffer</param>
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
{
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
MultiRange range = physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
_cpUniformBuffers.SetBounds(index, range);
_cpUniformBuffers.SetBounds(index, physical, range);
}
/// <summary>
@@ -318,9 +329,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the storage buffer</param>
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
{
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
MultiRange range = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
_gpUniformBuffers[stage].SetBounds(index, range);
_gpUniformBuffers[stage].SetBounds(index, physical, range);
_gpUniformBuffersDirty = true;
}
@@ -416,9 +428,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer address, or an undefined value if the buffer is not currently bound</returns>
public ulong GetComputeUniformBufferAddress(int index)
public (PhysicalMemory, ulong) GetComputeUniformBufferAddress(int index)
{
return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address;
ref BufferBounds buffer = ref _cpUniformBuffers.Buffers[index];
return (buffer.Physical, buffer.Range.GetSubRange(0).Address);
}
/// <summary>
@@ -437,9 +450,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">Index of the shader stage</param>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer address, or an undefined value if the buffer is not currently bound</returns>
public ulong GetGraphicsUniformBufferAddress(int stage, int index)
public (PhysicalMemory, ulong) GetGraphicsUniformBufferAddress(int stage, int index)
{
return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address;
ref BufferBounds buffer = ref _gpUniformBuffers[stage].Buffers[index];
return (buffer.Physical, buffer.Range.GetSubRange(0).Address);
}
/// <summary>
@@ -478,12 +492,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
public void CommitComputeBindings()
{
BufferCache bufferCache = _channel.MemoryManager.Physical.BufferCache;
BindBuffers(_cpStorageBuffers, isStorage: true);
BindBuffers(_cpUniformBuffers, isStorage: false);
BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true);
BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false);
CommitBufferTextureBindings(bufferCache);
CommitBufferTextureBindings();
// Force rebind after doing compute work.
Rebind();
@@ -495,14 +507,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Commit any queued buffer texture bindings.
/// </summary>
/// <param name="bufferCache">Buffer cache</param>
private void CommitBufferTextureBindings(BufferCache bufferCache)
private void CommitBufferTextureBindings()
{
if (_bufferTextures.Count > 0)
{
foreach (BufferTextureBinding binding in _bufferTextures)
{
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
binding.Texture.SetStorage(range);
// The texture must be rebound to use the new storage if it was updated.
@@ -526,7 +538,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (BufferTextureArrayBinding<ITextureArray> binding in _bufferTextureArrays)
{
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStage.None);
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStage.None);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
@@ -536,7 +548,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (BufferTextureArrayBinding<IImageArray> binding in _bufferImageArrays)
{
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
@@ -555,8 +567,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="indexed">True if the index buffer is in use</param>
public void CommitGraphicsBindings(bool indexed)
{
BufferCache bufferCache = _channel.MemoryManager.Physical.BufferCache;
if (indexed)
{
if (_indexBufferDirty || _rebind)
@@ -565,14 +575,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (!_indexBuffer.Range.IsUnmapped)
{
BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
BufferRange buffer = _indexBuffer.BufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
}
}
else if (!_indexBuffer.Range.IsUnmapped)
{
bufferCache.SynchronizeBufferRange(_indexBuffer.Range);
_indexBuffer.BufferCache.SynchronizeBufferRange(_indexBuffer.Range);
}
}
else if (_rebind)
@@ -597,7 +607,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
BufferRange buffer = vb.BufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
}
@@ -615,7 +625,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
bufferCache.SynchronizeBufferRange(vb.Range);
vb.BufferCache.SynchronizeBufferRange(vb.Range);
}
}
@@ -637,7 +647,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
tfbs[index] = tfb.BufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
}
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
@@ -684,7 +694,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
buffers[index] = new BufferAssignment(index, tfb.BufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
}
}
@@ -702,7 +712,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
bufferCache.SynchronizeBufferRange(tfb.Range);
tfb.BufferCache.SynchronizeBufferRange(tfb.Range);
}
}
@@ -710,7 +720,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
_gpStorageBuffersDirty = false;
BindBuffers(bufferCache, _gpStorageBuffers, isStorage: true);
BindBuffers(_gpStorageBuffers, isStorage: true);
}
else
{
@@ -721,14 +731,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
_gpUniformBuffersDirty = false;
BindBuffers(bufferCache, _gpUniformBuffers, isStorage: false);
BindBuffers(_gpUniformBuffers, isStorage: false);
}
else
{
UpdateBuffers(_gpUniformBuffers);
}
CommitBufferTextureBindings(bufferCache);
CommitBufferTextureBindings();
_rebind = false;
@@ -742,7 +752,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="bindings">Buffer memory ranges to bind</param>
/// <param name="isStorage">True to bind as storage buffer, false to bind as uniform buffer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage)
private void BindBuffers(BuffersPerStage[] bindings, bool isStorage)
{
int rangesCount = 0;
@@ -763,8 +773,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
BufferRange range = isStorage
? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
: bufferCache.GetBufferRange(bounds.Range, bufferStage);
? bounds.BufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
: bounds.BufferCache.GetBufferRange(bounds.Range, bufferStage);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
@@ -780,11 +790,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Bind respective buffer bindings on the host API.
/// </summary>
/// <param name="bufferCache">Buffer cache holding the buffers for the specified ranges</param>
/// <param name="buffers">Buffer memory ranges to bind</param>
/// <param name="isStorage">True to bind as storage buffer, false to bind as uniform buffer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage)
private void BindBuffers(BuffersPerStage buffers, bool isStorage)
{
int rangesCount = 0;
@@ -800,8 +809,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
BufferRange range = isStorage
? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
: bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
? bounds.BufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
: bounds.BufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
@@ -854,7 +863,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
_channel.MemoryManager.Physical.BufferCache.SynchronizeBufferRange(bounds.Range);
bounds.BufferCache.SynchronizeBufferRange(bounds.Range);
}
}
}
@@ -871,13 +880,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void SetBufferTextureStorage(
ShaderStage stage,
ITexture texture,
BufferCache bufferCache,
MultiRange range,
TextureBindingInfo bindingInfo,
bool isImage)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, isImage));
_bufferTextures.Add(new BufferTextureBinding(stage, texture, bufferCache, range, bindingInfo, isImage));
}
/// <summary>
@@ -894,13 +904,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
ShaderStage stage,
ITextureArray array,
ITexture texture,
BufferCache bufferCache,
MultiRange range,
TextureBindingInfo bindingInfo,
int index)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index));
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, bufferCache, range, bindingInfo, index));
}
/// <summary>
@@ -917,13 +928,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
ShaderStage stage,
IImageArray array,
ITexture texture,
BufferCache bufferCache,
MultiRange range,
TextureBindingInfo bindingInfo,
int index)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index));
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, bufferCache, range, bindingInfo, index));
}
/// <summary>

View File

@@ -19,6 +19,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
public ITexture Texture { get; }
/// <summary>
/// Buffer cache that owns the buffer.
/// </summary>
public BufferCache BufferCache { get; }
/// <summary>
/// Physical ranges of memory where the buffer texture data is located.
/// </summary>
@@ -39,18 +44,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
/// <param name="array">Array</param>
/// <param name="texture">Buffer texture</param>
/// <param name="bufferCache">Buffer cache that owns the buffer</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info</param>
/// <param name="index">Index of the binding on the array</param>
public BufferTextureArrayBinding(
T array,
ITexture texture,
BufferCache bufferCache,
MultiRange range,
TextureBindingInfo bindingInfo,
int index)
{
Array = array;
Texture = texture;
BufferCache = bufferCache;
Range = range;
BindingInfo = bindingInfo;
Index = index;

View File

@@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
public ITexture Texture { get; }
/// <summary>
/// Buffer cache that owns the buffer.
/// </summary>
public BufferCache BufferCache { get; }
/// <summary>
/// Physical ranges of memory where the buffer texture data is located.
/// </summary>
@@ -40,18 +45,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
/// <param name="stage">Shader stage accessing the texture</param>
/// <param name="texture">Buffer texture</param>
/// <param name="bufferCache">Buffer cache that owns the buffer</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info</param>
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
public BufferTextureBinding(
ShaderStage stage,
ITexture texture,
BufferCache bufferCache,
MultiRange range,
TextureBindingInfo bindingInfo,
bool isImage)
{
Stage = stage;
Texture = texture;
BufferCache = bufferCache;
Range = range;
BindingInfo = bindingInfo;
IsImage = isImage;

View File

@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
struct IndexBuffer
{
public BufferCache BufferCache;
public MultiRange Range;
public IndexType Type;
}

View File

@@ -1,4 +1,5 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
@@ -35,10 +36,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
public event EventHandler<UnmapEventArgs> MemoryUnmapped;
/// <summary>
/// Physical memory where the virtual memory is mapped into.
/// </summary>
internal PhysicalMemory Physical { get; }
private readonly GpuContext _context;
private readonly List<PhysicalMemory> _physicalMemoryList;
private readonly Dictionary<PhysicalMemory, byte> _physicalMemoryMap;
/// <summary>
/// Virtual range cache.
@@ -53,19 +53,65 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Creates a new instance of the GPU memory manager.
/// </summary>
/// <param name="context">GPU context</param>
/// <param name="physicalMemory">Physical memory that this memory manager will map into</param>
/// <param name="cpuMemorySize">The amount of physical CPU Memory Avaiable on the device.</param>
internal MemoryManager(PhysicalMemory physicalMemory, ulong cpuMemorySize)
internal MemoryManager(GpuContext context, PhysicalMemory physicalMemory, ulong cpuMemorySize)
{
Physical = physicalMemory;
_context = context;
_physicalMemoryList = new List<PhysicalMemory>()
{
physicalMemory
};
_physicalMemoryMap = new Dictionary<PhysicalMemory, byte>
{
{ physicalMemory, 0 }
};
VirtualRangeCache = new VirtualRangeCache(this);
CounterCache = new CounterCache();
_pageTable = new ulong[PtLvl0Size][];
MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler;
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
MemoryUnmapped += physicalMemory.TextureCache.MemoryUnmappedHandler;
MemoryUnmapped += physicalMemory.BufferCache.MemoryUnmappedHandler;
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
Physical.TextureCache.Initialize(cpuMemorySize);
physicalMemory.TextureCache.Initialize(cpuMemorySize);
}
/// <summary>
/// Attaches the memory manager to a new GPU channel.
/// </summary>
/// <param name="rebind">Action to be performed when the buffer cache changes</param>
internal void AttachToChannel(Action rebind)
{
PhysicalMemory physicalMemory = GetOwnPhysicalMemory();
physicalMemory.IncrementReferenceCount();
physicalMemory.BufferCache.NotifyBuffersModified += rebind;
physicalMemory.BufferCache.QueuePrune();
}
/// <summary>
/// Attaches the memory manager to a new GPU channel.
/// </summary>
/// <param name="rebind">Action that was performed when the buffer cache changed</param>
internal void DetachFromChannel(Action rebind)
{
PhysicalMemory physicalMemory = GetOwnPhysicalMemory();
physicalMemory.BufferCache.NotifyBuffersModified -= rebind;
physicalMemory.DecrementReferenceCount();
}
/// <summary>
/// Queues a prune of invalid entries on the buffer cache.
/// </summary>
internal void QueuePrune()
{
GetOwnPhysicalMemory().BufferCache.QueuePrune();
}
/// <summary>
@@ -81,15 +127,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (IsContiguous(va, size))
{
ulong address = Translate(va);
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
if (tracked)
{
return Physical.ReadTracked<T>(address);
return physicalMemory.ReadTracked<T>(address);
}
else
{
return Physical.Read<T>(address);
return physicalMemory.Read<T>(address);
}
}
else
@@ -113,7 +159,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (IsContiguous(va, size))
{
return Physical.GetSpan(Translate(va), size, tracked);
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
return physicalMemory.GetSpan(address, size, tracked);
}
else
{
@@ -138,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
bool isContiguous = true;
int mappedSize;
if (ValidateAddress(va) && GetPte(va) != PteUnmapped && Physical.IsMapped(Translate(va)))
if (ValidateAddress(va) && IsMappedOnGpuAndPhysical(va))
{
ulong endVa = va + (ulong)size;
ulong endVaAligned = (endVa + PageMask) & ~PageMask;
@@ -151,7 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong nextVa = currentVa + PageSize;
ulong nextPa = Translate(nextVa);
if (!ValidateAddress(nextVa) || GetPte(nextVa) == PteUnmapped || !Physical.IsMapped(nextPa))
if (!ValidateAddress(nextVa) || !IsMappedOnGpuAndPhysical(nextVa))
{
break;
}
@@ -180,7 +228,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (isContiguous)
{
return Physical.GetSpan(Translate(va), mappedSize, tracked);
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
return physicalMemory.GetSpan(address, mappedSize, tracked);
}
else
{
@@ -192,6 +242,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
/// <summary>
/// Checks if a page of memory is mapped on the GPU and its backing memory.
/// </summary>
/// <param name="va">GPU virtual address of the page</param>
/// <returns>True if mapped, false otherwise</returns>
private bool IsMappedOnGpuAndPhysical(ulong va)
{
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
if (address == PteUnmapped)
{
return false;
}
return physicalMemory.IsMapped(address);
}
/// <summary>
/// Reads data from a possibly non-contiguous region of GPU mapped memory.
/// </summary>
@@ -209,22 +276,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
if ((va & PageMask) != 0)
{
ulong pa = Translate(va);
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
Physical.GetSpan(pa, size, tracked).CopyTo(data[..size]);
physicalMemory.GetSpan(pa, size, tracked).CopyTo(data[..size]);
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = Translate(va + (ulong)offset);
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
size = Math.Min(data.Length - offset, (int)PageSize);
Physical.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
physicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
}
}
@@ -239,15 +306,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (IsContiguous(va, size))
{
return Physical.GetWritableRegion(Translate(va), size, tracked);
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
return physicalMemory.GetWritableRegion(address, size, tracked);
}
else
{
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
Memory<byte> memory = new byte[size];
ReadImpl(va, memoryOwner.Span, tracked);
GetSpan(va, size).CopyTo(memory.Span);
return new WritableRegion(this, va, memoryOwner, tracked);
return new WritableRegion(this, va, memory, tracked);
}
}
@@ -269,7 +338,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="data">The data to be written</param>
public void Write(ulong va, ReadOnlySpan<byte> data)
{
WriteImpl(va, data, Physical.Write);
WriteImpl(va, data, (physical, va, data) => physical.Write(va, data));
}
/// <summary>
@@ -279,7 +348,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="data">The data to be written</param>
public void WriteTrackedResource(ulong va, ReadOnlySpan<byte> data)
{
WriteImpl(va, data, Physical.WriteTrackedResource);
WriteImpl(va, data, (physical, va, data) => physical.WriteTrackedResource(va, data));
}
/// <summary>
@@ -289,10 +358,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="data">The data to be written</param>
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
WriteImpl(va, data, Physical.WriteUntracked);
WriteImpl(va, data, (physical, va, data) => physical.WriteUntracked(va, data));
}
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
private delegate void WriteCallback(PhysicalMemory physicalMemory, ulong address, ReadOnlySpan<byte> data);
/// <summary>
/// Writes data to possibly non-contiguous GPU mapped memory.
@@ -304,7 +373,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (IsContiguous(va, data.Length))
{
writeCallback(Translate(va), data);
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
writeCallback(physicalMemory, address, data);
}
else
{
@@ -312,22 +383,67 @@ namespace Ryujinx.Graphics.Gpu.Memory
if ((va & PageMask) != 0)
{
ulong pa = Translate(va);
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
writeCallback(pa, data[..size]);
writeCallback(physicalMemory, pa, data[..size]);
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = Translate(va + (ulong)offset);
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
size = Math.Min(data.Length - offset, (int)PageSize);
writeCallback(pa, data.Slice(offset, size));
writeCallback(physicalMemory, pa, data.Slice(offset, size));
}
}
}
/// <summary>
/// Writes data to GPU mapped memory, stopping at the first unmapped page at the memory region, if any.
/// </summary>
/// <param name="va">GPU virtual address to write the data into</param>
/// <param name="data">The data to be written</param>
public void WriteMapped(ulong va, ReadOnlySpan<byte> data)
{
if (IsContiguous(va, data.Length))
{
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
physicalMemory.Write(address, data);
}
else
{
int offset = 0, size;
if ((va & PageMask) != 0)
{
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
if (pa != PteUnmapped && physicalMemory.IsMapped(pa))
{
physicalMemory.Write(pa, data[..size]);
}
offset += size;
}
for (; offset < data.Length; offset += size)
{
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
size = Math.Min(data.Length - offset, (int)PageSize);
if (pa != PteUnmapped && physicalMemory.IsMapped(pa))
{
physicalMemory.Write(pa, data.Slice(offset, size));
}
}
}
}
@@ -359,15 +475,51 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the mapping</param>
/// <param name="kind">Kind of the resource located at the mapping</param>
public void Map(ulong pa, ulong va, ulong size, PteKind kind)
{
MapImpl(pa, va, size, kind);
}
/// <summary>
/// Maps a given range of pages to the specified CPU virtual address from a different process.
/// </summary>
/// <remarks>
/// All addresses and sizes must be page aligned.
/// </remarks>
/// <param name="pa">CPU virtual address to map into</param>
/// <param name="va">GPU virtual address to be mapped</param>
/// <param name="size">Size in bytes of the mapping</param>
/// <param name="kind">Kind of the resource located at the mapping</param>
/// <param name="ownedPid">PID of the process that owns the mapping</param>
public void MapForeign(ulong pa, ulong va, ulong size, PteKind kind, ulong ownedPid)
{
if (_context.PhysicalMemoryRegistry.TryGetValue(ownedPid, out PhysicalMemory physicalMemory))
{
MapImpl(pa, va, size, kind, physicalMemory);
}
}
/// <summary>
/// Maps a given range of pages to the specified CPU virtual address.
/// </summary>
/// <remarks>
/// All addresses and sizes must be page aligned.
/// </remarks>
/// <param name="pa">CPU virtual address to map into</param>
/// <param name="va">GPU virtual address to be mapped</param>
/// <param name="size">Size in bytes of the mapping</param>
/// <param name="kind">Kind of the resource located at the mapping</param>
/// <param name="physicalMemory">Optional physical memory to import for the mapping</param>
private void MapImpl(ulong pa, ulong va, ulong size, PteKind kind, PhysicalMemory physicalMemory = null)
{
lock (_pageTable)
{
UnmapEventArgs e = new(va, size);
MemoryUnmapped?.Invoke(this, e);
byte pIndex = physicalMemory != null ? GetOrAddPhysicalMemory(physicalMemory) : (byte)0;
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(va + offset, PackPte(pa + offset, kind));
SetPte(va + offset, PackPte(pa + offset, pIndex, kind));
}
RunRemapActions(e);
@@ -418,12 +570,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int page = 0; page < pages - 1; page++)
{
if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped)
ulong nextPte = GetPte(va + PageSize);
if (!ValidateAddress(va + PageSize) || nextPte == PteUnmapped)
{
return false;
}
if (Translate(va) + PageSize != Translate(va + PageSize))
if (GetPte(va) + PageSize != nextPte)
{
return false;
}
@@ -457,7 +611,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int pages = (int)((endVaRounded - va) / PageSize);
List<MemoryRange> regions = [];
List<MemoryRange> regions = new();
for (int page = 0; page < pages - 1; page++)
{
@@ -535,6 +689,49 @@ namespace Ryujinx.Graphics.Gpu.Memory
return true;
}
/// <summary>
/// Gets the backing memory for a given GPU virtual address.
/// </summary>
/// <param name="va">GPU virtual address to get the backing memory from</param>
/// <returns>The backing memory for the specified GPU virtual address</returns>
internal PhysicalMemory GetBackingMemory(ulong va)
{
ulong pte = GetPte(va);
if (pte == PteUnmapped)
{
return GetOwnPhysicalMemory();
}
return _physicalMemoryList[UnpackPIndexFromPte(pte)];
}
/// <summary>
/// Gets the backing memory that is owned by this GPU memory manager.
/// </summary>
/// <returns>The backing memory owned by this memory manager</returns>
private PhysicalMemory GetOwnPhysicalMemory()
{
return _physicalMemoryList[0];
}
/// <summary>
/// Gets the index for a given physical memory on the list, adding it to the list if needed.
/// </summary>
/// <param name="physicalMemory">Physical memory to get the index from</param>
/// <returns>The index of the physical memory on the list</returns>
private byte GetOrAddPhysicalMemory(PhysicalMemory physicalMemory)
{
if (!_physicalMemoryMap.TryGetValue(physicalMemory, out byte pIndex))
{
pIndex = checked((byte)_physicalMemoryList.Count);
_physicalMemoryList.Add(physicalMemory);
_physicalMemoryMap.Add(physicalMemory, pIndex);
}
return pIndex;
}
/// <summary>
/// Validates a GPU virtual address.
/// </summary>
@@ -636,6 +833,28 @@ namespace Ryujinx.Graphics.Gpu.Memory
return Math.Min(maxSize, va - startVa);
}
/// <summary>
/// Translates a GPU virtual address to a CPU virtual address and the associated physical memory.
/// </summary>
/// <param name="va">GPU virtual address to be translated</param>
/// <returns>CPU virtual address with the physical memory, or <see cref="PteUnmapped"/> if unmapped</returns>
private (PhysicalMemory, ulong) TranslateWithPhysicalMemory(ulong va)
{
if (!ValidateAddress(va))
{
return (GetOwnPhysicalMemory(), PteUnmapped);
}
ulong pte = GetPte(va);
if (pte == PteUnmapped)
{
return (GetOwnPhysicalMemory(), PteUnmapped);
}
return (_physicalMemoryList[UnpackPIndexFromPte(pte)], UnpackPaFromPte(pte) + (va & PageMask));
}
/// <summary>
/// Gets the kind of a given memory page.
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.
@@ -659,6 +878,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
return UnpackKindFromPte(pte);
}
public bool IsForeignMapping(ulong va)
{
ulong pte = GetPte(va);
if (pte == PteUnmapped)
{
return false;
}
return UnpackPIndexFromPte(pte) != 0;
}
/// <summary>
/// Gets the Page Table entry for a given GPU virtual address.
/// </summary>
@@ -704,11 +935,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Creates a page table entry from a physical address and kind.
/// </summary>
/// <param name="pa">Physical address</param>
/// <param name="pIndex">Index of the physical memory on the list</param>
/// <param name="kind">Kind</param>
/// <returns>Page table entry</returns>
private static ulong PackPte(ulong pa, PteKind kind)
private static ulong PackPte(ulong pa, byte pIndex, PteKind kind)
{
return pa | ((ulong)kind << 56);
return pa | ((ulong)pIndex << 48) | ((ulong)kind << 56);
}
/// <summary>
@@ -721,6 +953,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
return (PteKind)(pte >> 56);
}
/// <summary>
/// Unpacks the physical memory index in the list from a page table entry.
/// </summary>
/// <param name="pte">Page table entry</param>
/// <returns>Physical memory index</returns>
private static byte UnpackPIndexFromPte(ulong pte)
{
return (byte)(pte >> 48);
}
/// <summary>
/// Unpacks physical address from a page table entry.
/// </summary>
@@ -728,7 +970,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <returns>Physical address</returns>
private static ulong UnpackPaFromPte(ulong pte)
{
return pte & 0xffffffffffffffUL;
return pte & 0xffffffffffffUL;
}
}
}

View File

@@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
struct VertexBuffer
{
public BufferCache BufferCache;
public MultiRange Range;
public int Stride;
public int Divisor;

View File

@@ -324,6 +324,11 @@ 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();
@@ -392,7 +397,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
context,
shaders,
specState.PipelineState,
specState.TransformFeedbackDescriptors != null);
specState.TransformFeedbackDescriptors != null,
specState.ComputeState.GetLocalSize());
IProgram hostProgram;
@@ -629,7 +635,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return;
}
WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
if (context.Capabilities.Api != TargetApi.Metal)
{
WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
}
}
/// <summary>

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
@@ -366,6 +367,9 @@ 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);
}
@@ -490,7 +494,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
ShaderInfoBuilder shaderInfoBuilder = new(_context, compilation.SpecializationState.TransformFeedbackDescriptors != null);
ref GpuChannelComputeState computeState = ref compilation.SpecializationState.ComputeState;
ShaderInfoBuilder shaderInfoBuilder = new(
_context,
compilation.SpecializationState.TransformFeedbackDescriptors != null,
computeLocalSize: computeState.GetLocalSize());
for (int index = 0; index < compilation.TranslatedStages.Length; index++)
{
@@ -720,7 +729,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
ShaderProgram program = translatorContext.Translate();
CachedShaderStage[] shaders = [new(program.Info, shader.Code, shader.Cb1Data)];
CachedShaderStage[] shaders = [new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data)];
_compilationQueue.Enqueue(new ProgramCompilation([program], shaders, newSpecState, programIndex, isCompute: true));
}

View File

@@ -1,5 +1,6 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
@@ -17,7 +18,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly GpuAccessorState _state;
private readonly int _stageIndex;
private readonly bool _compute;
private readonly bool _isVulkan;
private readonly bool _isOpenGL;
private readonly bool _hasGeometryShader;
private readonly bool _supportsQuads;
@@ -39,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_channel = channel;
_state = state;
_stageIndex = stageIndex;
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_isOpenGL = context.Capabilities.Api == TargetApi.OpenGL;
_hasGeometryShader = hasGeometryShader;
_supportsQuads = context.Capabilities.SupportsQuads;
@@ -66,11 +67,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <inheritdoc/>
public uint ConstantBuffer1Read(int offset)
{
ulong baseAddress = _compute
(PhysicalMemory physical, ulong baseAddress) = _compute
? _channel.BufferManager.GetComputeUniformBufferAddress(1)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(_stageIndex, 1);
return _channel.MemoryManager.Physical.Read<uint>(baseAddress + (ulong)offset);
return physical.Read<uint>(baseAddress + (ulong)offset);
}
/// <inheritdoc/>
@@ -117,10 +118,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
public GpuGraphicsState QueryGraphicsState()
{
return _state.GraphicsState.CreateShaderGraphicsState(
!_isVulkan,
_isOpenGL,
_supportsQuads,
_hasGeometryShader,
_isVulkan || _state.GraphicsState.YNegateEnabled);
!_isOpenGL || _state.GraphicsState.YNegateEnabled);
}
/// <inheritdoc/>

View File

@@ -55,7 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer");
}
@@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
if (count == 1)
{
@@ -103,7 +103,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
}
@@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
if (count == 1)
{

View File

@@ -1,3 +1,5 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.Shader
{
/// <summary>
@@ -61,5 +63,14 @@ 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);
}
}
}

View File

@@ -117,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private static string GetDiskCachePath()
{
return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader")
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId.ToLower(), "cache", "shader")
: null;
}
@@ -204,7 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuChannelComputeState computeState,
ulong gpuVa)
{
if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa))
if (_cpPrograms.TryGetValue(gpuVa, out CachedShaderProgram cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa))
{
return cpShader;
}
@@ -223,8 +223,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false);
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
ShaderSource[] shaderSourcesArray = [CreateShaderSource(translatedShader.Program)];
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(
_context,
translatedShader.Program.Info,
computeState.GetLocalSize());
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
@@ -251,8 +254,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
channel.TextureManager.UpdateRenderTargets();
var rtControl = state.RtControl;
var msaaMode = state.RtMsaaMode;
RtControl rtControl = state.RtControl;
TextureMsaaMode msaaMode = state.RtMsaaMode;
pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY();
@@ -262,7 +265,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int rtIndex = rtControl.UnpackPermutationIndex(index);
var colorState = state.RtColorState[rtIndex];
RtColorState colorState = state.RtColorState[rtIndex];
if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0)
{
@@ -307,12 +310,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
ref GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses)
{
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, ref poolState, ref graphicsState, gpShaders, addresses))
if (_gpPrograms.TryGetValue(addresses, out CachedShaderProgram gpShaders) && IsShaderEqual(channel, ref poolState, ref graphicsState, gpShaders, addresses))
{
return gpShaders;
}
if (_graphicsShaderCache.TryFind(channel, ref poolState, ref graphicsState, addresses, out gpShaders, out var cachedGuestCode))
if (_graphicsShaderCache.TryFind(channel, ref poolState, ref graphicsState, addresses, out gpShaders, out CachedGraphicsGuestCode cachedGuestCode))
{
_gpPrograms[addresses] = gpShaders;
return gpShaders;
@@ -365,7 +368,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore);
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
List<ShaderSource> shaderSources = new();
List<ShaderSource> shaderSources = [];
TranslatorContext previousStage = null;
ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute);
@@ -425,7 +428,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
(program, ShaderProgramInfo vacInfo) = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
infoBuilder.AddStageInfoVac(vacInfo);
}
else
{
@@ -530,9 +534,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, tfEnabled);
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, context.GetVertexAsComputeInfo(), tfEnabled);
return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations());
return new(_context.Renderer.CreateProgram([source], info), program.Info, context.GetResourceReservations());
}
/// <summary>
@@ -582,7 +586,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++)
{
var tf = state.TfState[i];
TfState tf = state.TfState[i];
descs[i] = new TransformFeedbackDescriptor(
tf.BufferIndex,
@@ -688,7 +692,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>The generated translator context</returns>
public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa)
{
var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
TranslationOptions options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
return Translator.CreateContext(gpuVa, gpuAccessor, options);
}
@@ -705,7 +709,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>The generated translator context</returns>
public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa)
{
var options = CreateTranslationOptions(api, flags);
TranslationOptions options = CreateTranslationOptions(api, flags);
return Translator.CreateContext(gpuVa, gpuAccessor, options);
}
@@ -729,15 +733,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
byte[] codeB,
bool asCompute)
{
ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
var memoryManager = channel.MemoryManager;
(PhysicalMemory physical, ulong cb1DataAddress) = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
MemoryManager memoryManager = channel.MemoryManager;
codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
byte[] cb1DataA = ReadArray(memoryManager, cb1DataAddress, vertexA.Cb1DataSize);
byte[] cb1DataB = ReadArray(memoryManager, cb1DataAddress, currentStage.Cb1DataSize);
byte[] cb1DataA = ReadArray(physical, cb1DataAddress, vertexA.Cb1DataSize);
byte[] cb1DataB = ReadArray(physical, cb1DataAddress, currentStage.Cb1DataSize);
ShaderDumpPaths pathsA = default;
ShaderDumpPaths pathsB = default;
@@ -769,13 +773,13 @@ 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)
{
var memoryManager = channel.MemoryManager;
MemoryManager memoryManager = channel.MemoryManager;
ulong cb1DataAddress = context.Stage == ShaderStage.Compute
(PhysicalMemory physical, ulong cb1DataAddress) = context.Stage == ShaderStage.Compute
? channel.BufferManager.GetComputeUniformBufferAddress(1)
: channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1);
byte[] cb1Data = ReadArray(memoryManager, cb1DataAddress, context.Cb1DataSize);
byte[] cb1Data = ReadArray(physical, cb1DataAddress, context.Cb1DataSize);
code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
@@ -789,18 +793,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Reads data from physical memory, returns an empty array if the memory is unmapped or size is 0.
/// </summary>
/// <param name="memoryManager">Memory manager with the physical memory to read from</param>
/// <param name="physicalMemory">Physical memory to read the data from, might be null</param>
/// <param name="address">Physical address of the region to read</param>
/// <param name="size">Size in bytes of the data</param>
/// <returns>An array with the data at the specified memory location</returns>
private static byte[] ReadArray(MemoryManager memoryManager, ulong address, int size)
private static byte[] ReadArray(PhysicalMemory physicalMemory, ulong address, int size)
{
if (address == MemoryManager.PteUnmapped || size == 0)
if (address == MemoryManager.PteUnmapped || size == 0 || physicalMemory == null)
{
return Array.Empty<byte>();
return [];
}
return memoryManager.Physical.GetSpan(address, size).ToArray();
return physicalMemory.GetSpan(address, size).ToArray();
}
/// <summary>
@@ -822,16 +826,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Creates shader translation options with the requested graphics API and flags.
/// The shader language is choosen based on the current configuration and graphics API.
/// The shader language is chosen 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 = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
? TargetLanguage.Spirv
: TargetLanguage.Glsl;
TargetLanguage lang = api switch
{
TargetApi.OpenGL => TargetLanguage.Glsl,
TargetApi.Vulkan => GraphicsConfig.EnableSpirvCompilationOnVulkan ? TargetLanguage.Spirv : TargetLanguage.Glsl,
TargetApi.Metal => TargetLanguage.Msl,
_ => throw new NotImplementedException()
};
return new TranslationOptions(lang, api, flags);
}

View File

@@ -22,6 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
ResourceStages.Geometry;
private readonly GpuContext _context;
private readonly ComputeSize _computeLocalSize;
private int _fragmentOutputMap;
@@ -39,9 +40,11 @@ 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>
public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false)
/// <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)
{
_context = context;
_computeLocalSize = computeLocalSize;
_fragmentOutputMap = -1;
@@ -95,7 +98,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>
@@ -159,6 +162,25 @@ 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>
@@ -361,14 +383,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
ResourceLayout resourceLayout = new(descriptors.AsReadOnly(), usages.AsReadOnly());
if (pipeline.HasValue)
{
return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache);
}
else
{
return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache);
}
return new ShaderInfo(_fragmentOutputMap, resourceLayout, _computeLocalSize, pipeline, fromCache);
}
/// <summary>
@@ -378,14 +393,16 @@ 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)
bool tfEnabled,
ComputeSize computeLocalSize)
{
ShaderInfoBuilder builder = new(context, tfEnabled);
ShaderInfoBuilder builder = new(context, tfEnabled, computeLocalSize: computeLocalSize);
foreach (CachedShaderStage program in programs)
{
@@ -403,11 +420,12 @@ 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, bool fromCache = false)
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, ComputeSize computeLocalSize, bool fromCache = false)
{
ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false);
ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false, computeLocalSize: computeLocalSize);
builder.AddStageInfo(info);
@@ -422,10 +440,11 @@ 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, bool tfEnabled, bool fromCache = false)
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, ShaderProgramInfo info2, bool tfEnabled, bool fromCache = false)
{
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true);
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true, computeLocalSize: ComputeSize.VtgAsCompute);
builder.AddStageInfoVac(info2);
builder.AddStageInfo(info, vertexAsCompute: true);
return builder.Build(null, fromCache);

View File

@@ -696,7 +696,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
cachedTextureBufferIndex = textureBufferIndex;
if (samplerBufferIndex == textureBufferIndex)
@@ -710,7 +710,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
cachedSamplerBufferIndex = samplerBufferIndex;
}

View File

@@ -9,11 +9,18 @@ using System.Threading;
namespace Ryujinx.Graphics.Gpu
{
using Texture = Image.Texture;
public record TextureData(int Width, int Height, byte[] Data);
/// <summary>
/// GPU image presentation window.
/// </summary>
public class Window
{
private const int CaptureTextureWidth = 1280;
private const int CaptureTextureHeight = 720;
private readonly GpuContext _context;
/// <summary>
@@ -85,7 +92,21 @@ namespace Ryujinx.Graphics.Gpu
}
}
private class PresentedTexture
{
public readonly Texture Texture;
public readonly ImageCrop Crop;
public PresentedTexture(Texture texture, ImageCrop crop)
{
Texture = texture;
Crop = crop;
}
}
private readonly ConcurrentQueue<PresentationTexture> _frameQueue;
private PresentedTexture _lastPresentedTexture;
private ITexture _captureTexture;
private int _framesAvailable;
@@ -188,6 +209,51 @@ namespace Ryujinx.Graphics.Gpu
return true;
}
public TextureData GetLastPresentedData()
{
PresentedTexture pt = Volatile.Read(ref _lastPresentedTexture);
if (pt != null)
{
byte[] inputData = CaptureLastFrame(pt.Texture.HostTexture, pt.Crop);
int size = SizeCalculator.GetBlockLinearTextureSize(
CaptureTextureWidth,
CaptureTextureHeight,
1,
1,
1,
1,
1,
4,
16,
1,
1).TotalSize;
byte[] data = new byte[size];
LayoutConverter.ConvertLinearToBlockLinear(data, CaptureTextureWidth, CaptureTextureHeight, CaptureTextureWidth * 4, 4, 16, inputData);
return new TextureData(CaptureTextureWidth, CaptureTextureHeight, data);
}
return new TextureData(0, 0, Array.Empty<byte>());
}
public TextureData GetLastPresentedDataLinear()
{
PresentedTexture pt = Volatile.Read(ref _lastPresentedTexture);
if (pt != null)
{
byte[] inputData = CaptureLastFrame(pt.Texture.HostTexture, new ImageCrop());
return new TextureData(pt.Texture.Info.Width, pt.Texture.Info.Height, inputData);
}
return new TextureData(0, 0, Array.Empty<byte>());
}
/// <summary>
/// Presents a texture on the queue.
/// If the queue is empty, then no texture is presented.
@@ -205,6 +271,10 @@ namespace Ryujinx.Graphics.Gpu
pt.Cache.Tick();
EnsureCaptureTexture();
Volatile.Write(ref _lastPresentedTexture, new PresentedTexture(texture, pt.Crop));
texture.SynchronizeMemory();
ImageCrop crop = new(
@@ -244,6 +314,96 @@ namespace Ryujinx.Graphics.Gpu
}
}
private void EnsureCaptureTexture()
{
if (_captureTexture == null)
{
_captureTexture = _context.Renderer.CreateTexture(new TextureCreateInfo(
1280,
720,
1,
1,
1,
1,
1,
4,
Format.R8G8B8A8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha));
}
}
private byte[] CaptureLastFrame(ITexture lastFrame, ImageCrop crop)
{
int cropLeft, cropRight, cropTop, cropBottom;
if (crop.Left == 0 && crop.Right == 0)
{
cropLeft = 0;
cropRight = lastFrame.Width;
}
else
{
cropLeft = crop.Left;
cropRight = crop.Right;
}
if (crop.Top == 0 && crop.Bottom == 0)
{
cropTop = 0;
cropBottom = lastFrame.Height;
}
else
{
cropTop = crop.Top;
cropBottom = crop.Bottom;
}
int x1, y1, x2, y2;
if (crop.FlipX)
{
x1 = cropRight;
x2 = cropLeft;
}
else
{
x1 = cropLeft;
x2 = cropRight;
}
if (crop.FlipY)
{
y1 = cropBottom;
y2 = cropTop;
}
else
{
y1 = cropTop;
y2 = cropBottom;
}
Extents2D srcRegion = new(x1, y1, x2, y2);
Extents2D dstRegion = new(0, 0, CaptureTextureWidth, CaptureTextureHeight);
byte[] outputData = null;
_context.Renderer.BackgroundContextAction(() =>
{
lastFrame.CopyTo(_captureTexture, srcRegion, dstRegion, true);
using var data = _captureTexture.GetData();
outputData = data.Get().ToArray();
});
return outputData;
}
/// <summary>
/// Indicate that a frame on the queue is ready to be acquired.
/// </summary>

View File

@@ -0,0 +1,146 @@
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;
}
}
}
}

View File

@@ -0,0 +1,107 @@
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();
}
}
}
}
}

View File

@@ -0,0 +1,157 @@
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;
}
}
}
}

View File

@@ -0,0 +1,385 @@
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();
}
}
}

View File

@@ -0,0 +1,237 @@
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();
}
}
}
}

View File

@@ -0,0 +1,85 @@
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);
}
}
}

View File

@@ -0,0 +1,294 @@
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();
}
}
}

View File

@@ -0,0 +1,170 @@
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;
}
}

View File

@@ -0,0 +1,289 @@
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);
}
}
}
}

View File

@@ -0,0 +1,43 @@
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);
}
}
}

View File

@@ -0,0 +1,41 @@
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;
}
}

View File

@@ -0,0 +1,22 @@
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() { }
}
}

View File

@@ -0,0 +1,68 @@
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);
}
}
}
}

View File

@@ -0,0 +1,26 @@
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();
}
}
}
}

View File

@@ -0,0 +1,22 @@
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();
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Ryujinx.Graphics.Metal.Effects
{
internal interface IPostProcessingEffect : IDisposable
{
const int LocalGroupSize = 64;
Texture Run(Texture view, int width, int height);
}
}

View File

@@ -0,0 +1,18 @@
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);
}
}

View File

@@ -0,0 +1,63 @@
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;
}
}
}

View File

@@ -0,0 +1,206 @@
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

View File

@@ -0,0 +1,292 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
static class EnumConversion
{
public static MTLSamplerAddressMode Convert(this AddressMode mode)
{
return mode switch
{
AddressMode.Clamp => MTLSamplerAddressMode.ClampToEdge, // TODO: Should be clamp.
AddressMode.Repeat => MTLSamplerAddressMode.Repeat,
AddressMode.MirrorClamp => MTLSamplerAddressMode.MirrorClampToEdge, // TODO: Should be mirror clamp.
AddressMode.MirroredRepeat => MTLSamplerAddressMode.MirrorRepeat,
AddressMode.ClampToBorder => MTLSamplerAddressMode.ClampToBorderColor,
AddressMode.ClampToEdge => MTLSamplerAddressMode.ClampToEdge,
AddressMode.MirrorClampToEdge => MTLSamplerAddressMode.MirrorClampToEdge,
AddressMode.MirrorClampToBorder => MTLSamplerAddressMode.ClampToBorderColor, // TODO: Should be mirror clamp to border.
_ => LogInvalidAndReturn(mode, nameof(AddressMode), MTLSamplerAddressMode.ClampToEdge) // TODO: Should be clamp.
};
}
public static MTLBlendFactor Convert(this BlendFactor factor)
{
return factor switch
{
BlendFactor.Zero or BlendFactor.ZeroGl => MTLBlendFactor.Zero,
BlendFactor.One or BlendFactor.OneGl => MTLBlendFactor.One,
BlendFactor.SrcColor or BlendFactor.SrcColorGl => MTLBlendFactor.SourceColor,
BlendFactor.OneMinusSrcColor or BlendFactor.OneMinusSrcColorGl => MTLBlendFactor.OneMinusSourceColor,
BlendFactor.SrcAlpha or BlendFactor.SrcAlphaGl => MTLBlendFactor.SourceAlpha,
BlendFactor.OneMinusSrcAlpha or BlendFactor.OneMinusSrcAlphaGl => MTLBlendFactor.OneMinusSourceAlpha,
BlendFactor.DstAlpha or BlendFactor.DstAlphaGl => MTLBlendFactor.DestinationAlpha,
BlendFactor.OneMinusDstAlpha or BlendFactor.OneMinusDstAlphaGl => MTLBlendFactor.OneMinusDestinationAlpha,
BlendFactor.DstColor or BlendFactor.DstColorGl => MTLBlendFactor.DestinationColor,
BlendFactor.OneMinusDstColor or BlendFactor.OneMinusDstColorGl => MTLBlendFactor.OneMinusDestinationColor,
BlendFactor.SrcAlphaSaturate or BlendFactor.SrcAlphaSaturateGl => MTLBlendFactor.SourceAlphaSaturated,
BlendFactor.Src1Color or BlendFactor.Src1ColorGl => MTLBlendFactor.Source1Color,
BlendFactor.OneMinusSrc1Color or BlendFactor.OneMinusSrc1ColorGl => MTLBlendFactor.OneMinusSource1Color,
BlendFactor.Src1Alpha or BlendFactor.Src1AlphaGl => MTLBlendFactor.Source1Alpha,
BlendFactor.OneMinusSrc1Alpha or BlendFactor.OneMinusSrc1AlphaGl => MTLBlendFactor.OneMinusSource1Alpha,
BlendFactor.ConstantColor => MTLBlendFactor.BlendColor,
BlendFactor.OneMinusConstantColor => MTLBlendFactor.OneMinusBlendColor,
BlendFactor.ConstantAlpha => MTLBlendFactor.BlendAlpha,
BlendFactor.OneMinusConstantAlpha => MTLBlendFactor.OneMinusBlendAlpha,
_ => LogInvalidAndReturn(factor, nameof(BlendFactor), MTLBlendFactor.Zero)
};
}
public static MTLBlendOperation Convert(this BlendOp op)
{
return op switch
{
BlendOp.Add or BlendOp.AddGl => MTLBlendOperation.Add,
BlendOp.Subtract or BlendOp.SubtractGl => MTLBlendOperation.Subtract,
BlendOp.ReverseSubtract or BlendOp.ReverseSubtractGl => MTLBlendOperation.ReverseSubtract,
BlendOp.Minimum => MTLBlendOperation.Min,
BlendOp.Maximum => MTLBlendOperation.Max,
_ => LogInvalidAndReturn(op, nameof(BlendOp), MTLBlendOperation.Add)
};
}
public static MTLCompareFunction Convert(this CompareOp op)
{
return op switch
{
CompareOp.Never or CompareOp.NeverGl => MTLCompareFunction.Never,
CompareOp.Less or CompareOp.LessGl => MTLCompareFunction.Less,
CompareOp.Equal or CompareOp.EqualGl => MTLCompareFunction.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => MTLCompareFunction.LessEqual,
CompareOp.Greater or CompareOp.GreaterGl => MTLCompareFunction.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => MTLCompareFunction.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => MTLCompareFunction.GreaterEqual,
CompareOp.Always or CompareOp.AlwaysGl => MTLCompareFunction.Always,
_ => LogInvalidAndReturn(op, nameof(CompareOp), MTLCompareFunction.Never)
};
}
public static MTLCullMode Convert(this Face face)
{
return face switch
{
Face.Back => MTLCullMode.Back,
Face.Front => MTLCullMode.Front,
Face.FrontAndBack => MTLCullMode.None,
_ => LogInvalidAndReturn(face, nameof(Face), MTLCullMode.Back)
};
}
public static MTLWinding Convert(this FrontFace frontFace)
{
// The viewport is flipped vertically, therefore we need to switch the winding order as well
return frontFace switch
{
FrontFace.Clockwise => MTLWinding.CounterClockwise,
FrontFace.CounterClockwise => MTLWinding.Clockwise,
_ => LogInvalidAndReturn(frontFace, nameof(FrontFace), MTLWinding.Clockwise)
};
}
public static MTLIndexType Convert(this IndexType type)
{
return type switch
{
IndexType.UShort => MTLIndexType.UInt16,
IndexType.UInt => MTLIndexType.UInt32,
_ => LogInvalidAndReturn(type, nameof(IndexType), MTLIndexType.UInt16)
};
}
public static MTLLogicOperation Convert(this LogicalOp op)
{
return op switch
{
LogicalOp.Clear => MTLLogicOperation.Clear,
LogicalOp.And => MTLLogicOperation.And,
LogicalOp.AndReverse => MTLLogicOperation.AndReverse,
LogicalOp.Copy => MTLLogicOperation.Copy,
LogicalOp.AndInverted => MTLLogicOperation.AndInverted,
LogicalOp.Noop => MTLLogicOperation.Noop,
LogicalOp.Xor => MTLLogicOperation.Xor,
LogicalOp.Or => MTLLogicOperation.Or,
LogicalOp.Nor => MTLLogicOperation.Nor,
LogicalOp.Equiv => MTLLogicOperation.Equivalence,
LogicalOp.Invert => MTLLogicOperation.Invert,
LogicalOp.OrReverse => MTLLogicOperation.OrReverse,
LogicalOp.CopyInverted => MTLLogicOperation.CopyInverted,
LogicalOp.OrInverted => MTLLogicOperation.OrInverted,
LogicalOp.Nand => MTLLogicOperation.Nand,
LogicalOp.Set => MTLLogicOperation.Set,
_ => LogInvalidAndReturn(op, nameof(LogicalOp), MTLLogicOperation.And)
};
}
public static MTLSamplerMinMagFilter Convert(this MagFilter filter)
{
return filter switch
{
MagFilter.Nearest => MTLSamplerMinMagFilter.Nearest,
MagFilter.Linear => MTLSamplerMinMagFilter.Linear,
_ => LogInvalidAndReturn(filter, nameof(MagFilter), MTLSamplerMinMagFilter.Nearest)
};
}
public static (MTLSamplerMinMagFilter, MTLSamplerMipFilter) Convert(this MinFilter filter)
{
return filter switch
{
MinFilter.Nearest => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest),
MinFilter.Linear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear),
MinFilter.NearestMipmapNearest => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest),
MinFilter.LinearMipmapNearest => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Nearest),
MinFilter.NearestMipmapLinear => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Linear),
MinFilter.LinearMipmapLinear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear),
_ => LogInvalidAndReturn(filter, nameof(MinFilter), (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest))
};
}
public static MTLPrimitiveType Convert(this PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.Points => MTLPrimitiveType.Point,
PrimitiveTopology.Lines => MTLPrimitiveType.Line,
PrimitiveTopology.LineStrip => MTLPrimitiveType.LineStrip,
PrimitiveTopology.Triangles => MTLPrimitiveType.Triangle,
PrimitiveTopology.TriangleStrip => MTLPrimitiveType.TriangleStrip,
_ => LogInvalidAndReturn(topology, nameof(PrimitiveTopology), MTLPrimitiveType.Triangle)
};
}
public static MTLStencilOperation Convert(this StencilOp op)
{
return op switch
{
StencilOp.Keep or StencilOp.KeepGl => MTLStencilOperation.Keep,
StencilOp.Zero or StencilOp.ZeroGl => MTLStencilOperation.Zero,
StencilOp.Replace or StencilOp.ReplaceGl => MTLStencilOperation.Replace,
StencilOp.IncrementAndClamp or StencilOp.IncrementAndClampGl => MTLStencilOperation.IncrementClamp,
StencilOp.DecrementAndClamp or StencilOp.DecrementAndClampGl => MTLStencilOperation.DecrementClamp,
StencilOp.Invert or StencilOp.InvertGl => MTLStencilOperation.Invert,
StencilOp.IncrementAndWrap or StencilOp.IncrementAndWrapGl => MTLStencilOperation.IncrementWrap,
StencilOp.DecrementAndWrap or StencilOp.DecrementAndWrapGl => MTLStencilOperation.DecrementWrap,
_ => LogInvalidAndReturn(op, nameof(StencilOp), MTLStencilOperation.Keep)
};
}
public static MTLTextureType Convert(this Target target)
{
return target switch
{
Target.TextureBuffer => MTLTextureType.TextureBuffer,
Target.Texture1D => MTLTextureType.Type1D,
Target.Texture1DArray => MTLTextureType.Type1DArray,
Target.Texture2D => MTLTextureType.Type2D,
Target.Texture2DArray => MTLTextureType.Type2DArray,
Target.Texture2DMultisample => MTLTextureType.Type2DMultisample,
Target.Texture2DMultisampleArray => MTLTextureType.Type2DMultisampleArray,
Target.Texture3D => MTLTextureType.Type3D,
Target.Cubemap => MTLTextureType.Cube,
Target.CubemapArray => MTLTextureType.CubeArray,
_ => LogInvalidAndReturn(target, nameof(Target), MTLTextureType.Type2D)
};
}
public static MTLTextureSwizzle Convert(this SwizzleComponent swizzleComponent)
{
return swizzleComponent switch
{
SwizzleComponent.Zero => MTLTextureSwizzle.Zero,
SwizzleComponent.One => MTLTextureSwizzle.One,
SwizzleComponent.Red => MTLTextureSwizzle.Red,
SwizzleComponent.Green => MTLTextureSwizzle.Green,
SwizzleComponent.Blue => MTLTextureSwizzle.Blue,
SwizzleComponent.Alpha => MTLTextureSwizzle.Alpha,
_ => LogInvalidAndReturn(swizzleComponent, nameof(SwizzleComponent), MTLTextureSwizzle.Zero)
};
}
public static MTLVertexFormat Convert(this Format format)
{
return format switch
{
Format.R16Float => MTLVertexFormat.Half,
Format.R16G16Float => MTLVertexFormat.Half2,
Format.R16G16B16Float => MTLVertexFormat.Half3,
Format.R16G16B16A16Float => MTLVertexFormat.Half4,
Format.R32Float => MTLVertexFormat.Float,
Format.R32G32Float => MTLVertexFormat.Float2,
Format.R32G32B32Float => MTLVertexFormat.Float3,
Format.R11G11B10Float => MTLVertexFormat.FloatRG11B10,
Format.R32G32B32A32Float => MTLVertexFormat.Float4,
Format.R8Uint => MTLVertexFormat.UChar,
Format.R8G8Uint => MTLVertexFormat.UChar2,
Format.R8G8B8Uint => MTLVertexFormat.UChar3,
Format.R8G8B8A8Uint => MTLVertexFormat.UChar4,
Format.R16Uint => MTLVertexFormat.UShort,
Format.R16G16Uint => MTLVertexFormat.UShort2,
Format.R16G16B16Uint => MTLVertexFormat.UShort3,
Format.R16G16B16A16Uint => MTLVertexFormat.UShort4,
Format.R32Uint => MTLVertexFormat.UInt,
Format.R32G32Uint => MTLVertexFormat.UInt2,
Format.R32G32B32Uint => MTLVertexFormat.UInt3,
Format.R32G32B32A32Uint => MTLVertexFormat.UInt4,
Format.R8Sint => MTLVertexFormat.Char,
Format.R8G8Sint => MTLVertexFormat.Char2,
Format.R8G8B8Sint => MTLVertexFormat.Char3,
Format.R8G8B8A8Sint => MTLVertexFormat.Char4,
Format.R16Sint => MTLVertexFormat.Short,
Format.R16G16Sint => MTLVertexFormat.Short2,
Format.R16G16B16Sint => MTLVertexFormat.Short3,
Format.R16G16B16A16Sint => MTLVertexFormat.Short4,
Format.R32Sint => MTLVertexFormat.Int,
Format.R32G32Sint => MTLVertexFormat.Int2,
Format.R32G32B32Sint => MTLVertexFormat.Int3,
Format.R32G32B32A32Sint => MTLVertexFormat.Int4,
Format.R8Unorm => MTLVertexFormat.UCharNormalized,
Format.R8G8Unorm => MTLVertexFormat.UChar2Normalized,
Format.R8G8B8Unorm => MTLVertexFormat.UChar3Normalized,
Format.R8G8B8A8Unorm => MTLVertexFormat.UChar4Normalized,
Format.R16Unorm => MTLVertexFormat.UShortNormalized,
Format.R16G16Unorm => MTLVertexFormat.UShort2Normalized,
Format.R16G16B16Unorm => MTLVertexFormat.UShort3Normalized,
Format.R16G16B16A16Unorm => MTLVertexFormat.UShort4Normalized,
Format.R10G10B10A2Unorm => MTLVertexFormat.UInt1010102Normalized,
Format.R8Snorm => MTLVertexFormat.CharNormalized,
Format.R8G8Snorm => MTLVertexFormat.Char2Normalized,
Format.R8G8B8Snorm => MTLVertexFormat.Char3Normalized,
Format.R8G8B8A8Snorm => MTLVertexFormat.Char4Normalized,
Format.R16Snorm => MTLVertexFormat.ShortNormalized,
Format.R16G16Snorm => MTLVertexFormat.Short2Normalized,
Format.R16G16B16Snorm => MTLVertexFormat.Short3Normalized,
Format.R16G16B16A16Snorm => MTLVertexFormat.Short4Normalized,
Format.R10G10B10A2Snorm => MTLVertexFormat.Int1010102Normalized,
_ => LogInvalidAndReturn(format, nameof(Format), MTLVertexFormat.Float4)
};
}
private static T2 LogInvalidAndReturn<T1, T2>(T1 value, string name, T2 defaultValue = default)
{
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}.");
return defaultValue;
}
}
}

View File

@@ -0,0 +1,77 @@
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class FenceHolder : IDisposable
{
private MTLCommandBuffer _fence;
private int _referenceCount;
private bool _disposed;
public FenceHolder(MTLCommandBuffer fence)
{
_fence = fence;
_referenceCount = 1;
}
public MTLCommandBuffer GetUnsafe()
{
return _fence;
}
public bool TryGet(out MTLCommandBuffer fence)
{
int lastValue;
do
{
lastValue = _referenceCount;
if (lastValue == 0)
{
fence = default;
return false;
}
} while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
fence = _fence;
return true;
}
public MTLCommandBuffer Get()
{
Interlocked.Increment(ref _referenceCount);
return _fence;
}
public void Put()
{
if (Interlocked.Decrement(ref _referenceCount) == 0)
{
_fence = default;
}
}
public void Wait()
{
_fence.WaitUntilCompleted();
}
public bool IsSignaled()
{
return _fence.Status == MTLCommandBufferStatus.Completed;
}
public void Dispose()
{
if (!_disposed)
{
Put();
_disposed = true;
}
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Metal
{
class FormatConverter
{
public static void ConvertD24S8ToD32FS8(Span<byte> output, ReadOnlySpan<byte> input)
{
const float UnormToFloat = 1f / 0xffffff;
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output);
ReadOnlySpan<uint> inputUint = MemoryMarshal.Cast<byte, uint>(input);
int i = 0;
for (; i < inputUint.Length; i++)
{
uint depthStencil = inputUint[i];
uint depth = depthStencil >> 8;
uint stencil = depthStencil & 0xff;
int j = i * 2;
outputUint[j] = (uint)BitConverter.SingleToInt32Bits(depth * UnormToFloat);
outputUint[j + 1] = stencil;
}
}
public static void ConvertD32FS8ToD24S8(Span<byte> output, ReadOnlySpan<byte> input)
{
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output);
ReadOnlySpan<uint> inputUint = MemoryMarshal.Cast<byte, uint>(input);
int i = 0;
for (; i < inputUint.Length; i += 2)
{
float depth = BitConverter.Int32BitsToSingle((int)inputUint[i]);
uint stencil = inputUint[i + 1];
uint depthStencil = (Math.Clamp((uint)(depth * 0xffffff), 0, 0xffffff) << 8) | (stencil & 0xff);
int j = i >> 1;
outputUint[j] = depthStencil;
}
}
}
}

View File

@@ -0,0 +1,196 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
static class FormatTable
{
private static readonly MTLPixelFormat[] _table;
static FormatTable()
{
_table = new MTLPixelFormat[Enum.GetNames(typeof(Format)).Length];
Add(Format.R8Unorm, MTLPixelFormat.R8Unorm);
Add(Format.R8Snorm, MTLPixelFormat.R8Snorm);
Add(Format.R8Uint, MTLPixelFormat.R8Uint);
Add(Format.R8Sint, MTLPixelFormat.R8Sint);
Add(Format.R16Float, MTLPixelFormat.R16Float);
Add(Format.R16Unorm, MTLPixelFormat.R16Unorm);
Add(Format.R16Snorm, MTLPixelFormat.R16Snorm);
Add(Format.R16Uint, MTLPixelFormat.R16Uint);
Add(Format.R16Sint, MTLPixelFormat.R16Sint);
Add(Format.R32Float, MTLPixelFormat.R32Float);
Add(Format.R32Uint, MTLPixelFormat.R32Uint);
Add(Format.R32Sint, MTLPixelFormat.R32Sint);
Add(Format.R8G8Unorm, MTLPixelFormat.RG8Unorm);
Add(Format.R8G8Snorm, MTLPixelFormat.RG8Snorm);
Add(Format.R8G8Uint, MTLPixelFormat.RG8Uint);
Add(Format.R8G8Sint, MTLPixelFormat.RG8Sint);
Add(Format.R16G16Float, MTLPixelFormat.RG16Float);
Add(Format.R16G16Unorm, MTLPixelFormat.RG16Unorm);
Add(Format.R16G16Snorm, MTLPixelFormat.RG16Snorm);
Add(Format.R16G16Uint, MTLPixelFormat.RG16Uint);
Add(Format.R16G16Sint, MTLPixelFormat.RG16Sint);
Add(Format.R32G32Float, MTLPixelFormat.RG32Float);
Add(Format.R32G32Uint, MTLPixelFormat.RG32Uint);
Add(Format.R32G32Sint, MTLPixelFormat.RG32Sint);
// Add(Format.R8G8B8Unorm, MTLPixelFormat.R8G8B8Unorm);
// Add(Format.R8G8B8Snorm, MTLPixelFormat.R8G8B8Snorm);
// Add(Format.R8G8B8Uint, MTLPixelFormat.R8G8B8Uint);
// Add(Format.R8G8B8Sint, MTLPixelFormat.R8G8B8Sint);
// Add(Format.R16G16B16Float, MTLPixelFormat.R16G16B16Float);
// Add(Format.R16G16B16Unorm, MTLPixelFormat.R16G16B16Unorm);
// Add(Format.R16G16B16Snorm, MTLPixelFormat.R16G16B16SNorm);
// Add(Format.R16G16B16Uint, MTLPixelFormat.R16G16B16Uint);
// Add(Format.R16G16B16Sint, MTLPixelFormat.R16G16B16Sint);
// Add(Format.R32G32B32Float, MTLPixelFormat.R32G32B32Sfloat);
// Add(Format.R32G32B32Uint, MTLPixelFormat.R32G32B32Uint);
// Add(Format.R32G32B32Sint, MTLPixelFormat.R32G32B32Sint);
Add(Format.R8G8B8A8Unorm, MTLPixelFormat.RGBA8Unorm);
Add(Format.R8G8B8A8Snorm, MTLPixelFormat.RGBA8Snorm);
Add(Format.R8G8B8A8Uint, MTLPixelFormat.RGBA8Uint);
Add(Format.R8G8B8A8Sint, MTLPixelFormat.RGBA8Sint);
Add(Format.R16G16B16A16Float, MTLPixelFormat.RGBA16Float);
Add(Format.R16G16B16A16Unorm, MTLPixelFormat.RGBA16Unorm);
Add(Format.R16G16B16A16Snorm, MTLPixelFormat.RGBA16Snorm);
Add(Format.R16G16B16A16Uint, MTLPixelFormat.RGBA16Uint);
Add(Format.R16G16B16A16Sint, MTLPixelFormat.RGBA16Sint);
Add(Format.R32G32B32A32Float, MTLPixelFormat.RGBA32Float);
Add(Format.R32G32B32A32Uint, MTLPixelFormat.RGBA32Uint);
Add(Format.R32G32B32A32Sint, MTLPixelFormat.RGBA32Sint);
Add(Format.S8Uint, MTLPixelFormat.Stencil8);
Add(Format.D16Unorm, MTLPixelFormat.Depth16Unorm);
Add(Format.S8UintD24Unorm, MTLPixelFormat.Depth24UnormStencil8);
Add(Format.X8UintD24Unorm, MTLPixelFormat.Depth24UnormStencil8);
Add(Format.D32Float, MTLPixelFormat.Depth32Float);
Add(Format.D24UnormS8Uint, MTLPixelFormat.Depth24UnormStencil8);
Add(Format.D32FloatS8Uint, MTLPixelFormat.Depth32FloatStencil8);
Add(Format.R8G8B8A8Srgb, MTLPixelFormat.RGBA8UnormsRGB);
// Add(Format.R4G4Unorm, MTLPixelFormat.R4G4Unorm);
Add(Format.R4G4B4A4Unorm, MTLPixelFormat.RGBA8Unorm);
// Add(Format.R5G5B5X1Unorm, MTLPixelFormat.R5G5B5X1Unorm);
Add(Format.R5G5B5A1Unorm, MTLPixelFormat.BGR5A1Unorm);
Add(Format.R5G6B5Unorm, MTLPixelFormat.B5G6R5Unorm);
Add(Format.R10G10B10A2Unorm, MTLPixelFormat.RGB10A2Unorm);
Add(Format.R10G10B10A2Uint, MTLPixelFormat.RGB10A2Uint);
Add(Format.R11G11B10Float, MTLPixelFormat.RG11B10Float);
Add(Format.R9G9B9E5Float, MTLPixelFormat.RGB9E5Float);
Add(Format.Bc1RgbaUnorm, MTLPixelFormat.BC1RGBA);
Add(Format.Bc2Unorm, MTLPixelFormat.BC2RGBA);
Add(Format.Bc3Unorm, MTLPixelFormat.BC3RGBA);
Add(Format.Bc1RgbaSrgb, MTLPixelFormat.BC1RGBAsRGB);
Add(Format.Bc2Srgb, MTLPixelFormat.BC2RGBAsRGB);
Add(Format.Bc3Srgb, MTLPixelFormat.BC3RGBAsRGB);
Add(Format.Bc4Unorm, MTLPixelFormat.BC4RUnorm);
Add(Format.Bc4Snorm, MTLPixelFormat.BC4RSnorm);
Add(Format.Bc5Unorm, MTLPixelFormat.BC5RGUnorm);
Add(Format.Bc5Snorm, MTLPixelFormat.BC5RGSnorm);
Add(Format.Bc7Unorm, MTLPixelFormat.BC7RGBAUnorm);
Add(Format.Bc7Srgb, MTLPixelFormat.BC7RGBAUnormsRGB);
Add(Format.Bc6HSfloat, MTLPixelFormat.BC6HRGBFloat);
Add(Format.Bc6HUfloat, MTLPixelFormat.BC6HRGBUfloat);
Add(Format.Etc2RgbUnorm, MTLPixelFormat.ETC2RGB8);
// Add(Format.Etc2RgbaUnorm, MTLPixelFormat.ETC2RGBA8);
Add(Format.Etc2RgbPtaUnorm, MTLPixelFormat.ETC2RGB8A1);
Add(Format.Etc2RgbSrgb, MTLPixelFormat.ETC2RGB8sRGB);
// Add(Format.Etc2RgbaSrgb, MTLPixelFormat.ETC2RGBA8sRGB);
Add(Format.Etc2RgbPtaSrgb, MTLPixelFormat.ETC2RGB8A1sRGB);
// Add(Format.R8Uscaled, MTLPixelFormat.R8Uscaled);
// Add(Format.R8Sscaled, MTLPixelFormat.R8Sscaled);
// Add(Format.R16Uscaled, MTLPixelFormat.R16Uscaled);
// Add(Format.R16Sscaled, MTLPixelFormat.R16Sscaled);
// Add(Format.R32Uscaled, MTLPixelFormat.R32Uscaled);
// Add(Format.R32Sscaled, MTLPixelFormat.R32Sscaled);
// Add(Format.R8G8Uscaled, MTLPixelFormat.R8G8Uscaled);
// Add(Format.R8G8Sscaled, MTLPixelFormat.R8G8Sscaled);
// Add(Format.R16G16Uscaled, MTLPixelFormat.R16G16Uscaled);
// Add(Format.R16G16Sscaled, MTLPixelFormat.R16G16Sscaled);
// Add(Format.R32G32Uscaled, MTLPixelFormat.R32G32Uscaled);
// Add(Format.R32G32Sscaled, MTLPixelFormat.R32G32Sscaled);
// Add(Format.R8G8B8Uscaled, MTLPixelFormat.R8G8B8Uscaled);
// Add(Format.R8G8B8Sscaled, MTLPixelFormat.R8G8B8Sscaled);
// Add(Format.R16G16B16Uscaled, MTLPixelFormat.R16G16B16Uscaled);
// Add(Format.R16G16B16Sscaled, MTLPixelFormat.R16G16B16Sscaled);
// Add(Format.R32G32B32Uscaled, MTLPixelFormat.R32G32B32Uscaled);
// Add(Format.R32G32B32Sscaled, MTLPixelFormat.R32G32B32Sscaled);
// Add(Format.R8G8B8A8Uscaled, MTLPixelFormat.R8G8B8A8Uscaled);
// Add(Format.R8G8B8A8Sscaled, MTLPixelFormat.R8G8B8A8Sscaled);
// Add(Format.R16G16B16A16Uscaled, MTLPixelFormat.R16G16B16A16Uscaled);
// Add(Format.R16G16B16A16Sscaled, MTLPixelFormat.R16G16B16A16Sscaled);
// Add(Format.R32G32B32A32Uscaled, MTLPixelFormat.R32G32B32A32Uscaled);
// Add(Format.R32G32B32A32Sscaled, MTLPixelFormat.R32G32B32A32Sscaled);
// Add(Format.R10G10B10A2Snorm, MTLPixelFormat.A2B10G10R10SNormPack32);
// Add(Format.R10G10B10A2Sint, MTLPixelFormat.A2B10G10R10SintPack32);
// Add(Format.R10G10B10A2Uscaled, MTLPixelFormat.A2B10G10R10UscaledPack32);
// Add(Format.R10G10B10A2Sscaled, MTLPixelFormat.A2B10G10R10SscaledPack32);
Add(Format.Astc4x4Unorm, MTLPixelFormat.ASTC4x4LDR);
Add(Format.Astc5x4Unorm, MTLPixelFormat.ASTC5x4LDR);
Add(Format.Astc5x5Unorm, MTLPixelFormat.ASTC5x5LDR);
Add(Format.Astc6x5Unorm, MTLPixelFormat.ASTC6x5LDR);
Add(Format.Astc6x6Unorm, MTLPixelFormat.ASTC6x6LDR);
Add(Format.Astc8x5Unorm, MTLPixelFormat.ASTC8x5LDR);
Add(Format.Astc8x6Unorm, MTLPixelFormat.ASTC8x6LDR);
Add(Format.Astc8x8Unorm, MTLPixelFormat.ASTC8x8LDR);
Add(Format.Astc10x5Unorm, MTLPixelFormat.ASTC10x5LDR);
Add(Format.Astc10x6Unorm, MTLPixelFormat.ASTC10x6LDR);
Add(Format.Astc10x8Unorm, MTLPixelFormat.ASTC10x8LDR);
Add(Format.Astc10x10Unorm, MTLPixelFormat.ASTC10x10LDR);
Add(Format.Astc12x10Unorm, MTLPixelFormat.ASTC12x10LDR);
Add(Format.Astc12x12Unorm, MTLPixelFormat.ASTC12x12LDR);
Add(Format.Astc4x4Srgb, MTLPixelFormat.ASTC4x4sRGB);
Add(Format.Astc5x4Srgb, MTLPixelFormat.ASTC5x4sRGB);
Add(Format.Astc5x5Srgb, MTLPixelFormat.ASTC5x5sRGB);
Add(Format.Astc6x5Srgb, MTLPixelFormat.ASTC6x5sRGB);
Add(Format.Astc6x6Srgb, MTLPixelFormat.ASTC6x6sRGB);
Add(Format.Astc8x5Srgb, MTLPixelFormat.ASTC8x5sRGB);
Add(Format.Astc8x6Srgb, MTLPixelFormat.ASTC8x6sRGB);
Add(Format.Astc8x8Srgb, MTLPixelFormat.ASTC8x8sRGB);
Add(Format.Astc10x5Srgb, MTLPixelFormat.ASTC10x5sRGB);
Add(Format.Astc10x6Srgb, MTLPixelFormat.ASTC10x6sRGB);
Add(Format.Astc10x8Srgb, MTLPixelFormat.ASTC10x8sRGB);
Add(Format.Astc10x10Srgb, MTLPixelFormat.ASTC10x10sRGB);
Add(Format.Astc12x10Srgb, MTLPixelFormat.ASTC12x10sRGB);
Add(Format.Astc12x12Srgb, MTLPixelFormat.ASTC12x12sRGB);
Add(Format.B5G6R5Unorm, MTLPixelFormat.B5G6R5Unorm);
Add(Format.B5G5R5A1Unorm, MTLPixelFormat.BGR5A1Unorm);
Add(Format.A1B5G5R5Unorm, MTLPixelFormat.A1BGR5Unorm);
Add(Format.B8G8R8A8Unorm, MTLPixelFormat.BGRA8Unorm);
Add(Format.B8G8R8A8Srgb, MTLPixelFormat.BGRA8UnormsRGB);
}
private static void Add(Format format, MTLPixelFormat mtlFormat)
{
_table[(int)format] = mtlFormat;
}
public static MTLPixelFormat GetFormat(Format format)
{
MTLPixelFormat mtlFormat = _table[(int)format];
if (IsD24S8(format))
{
if (!MTLDevice.CreateSystemDefaultDevice().Depth24Stencil8PixelFormatSupported)
{
mtlFormat = MTLPixelFormat.Depth32FloatStencil8;
}
}
if (mtlFormat == MTLPixelFormat.Invalid)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Format {format} is not supported by the host.");
}
return mtlFormat;
}
public static bool IsD24S8(Format format)
{
return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm || format == Format.X8UintD24Unorm;
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Metal
{
static partial class HardwareInfoTools
{
private readonly static IntPtr _kCFAllocatorDefault = IntPtr.Zero;
private readonly static UInt32 _kCFStringEncodingASCII = 0x0600;
private const string IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit";
private const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
[LibraryImport(IOKit, StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr IOServiceMatching(string name);
[LibraryImport(IOKit)]
private static partial IntPtr IOServiceGetMatchingService(IntPtr mainPort, IntPtr matching);
[LibraryImport(IOKit)]
private static partial IntPtr IORegistryEntryCreateCFProperty(IntPtr entry, IntPtr key, IntPtr allocator, UInt32 options);
[LibraryImport(CoreFoundation, StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr CFStringCreateWithCString(IntPtr allocator, string cString, UInt32 encoding);
[LibraryImport(CoreFoundation)]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool CFStringGetCString(IntPtr theString, IntPtr buffer, long bufferSizes, UInt32 encoding);
[LibraryImport(CoreFoundation)]
public static partial IntPtr CFDataGetBytePtr(IntPtr theData);
static string GetNameFromId(uint id)
{
return id switch
{
0x1002 => "AMD",
0x106B => "Apple",
0x10DE => "NVIDIA",
0x13B5 => "ARM",
0x8086 => "Intel",
_ => $"0x{id:X}"
};
}
public static string GetVendor()
{
IntPtr serviceDict = IOServiceMatching("IOGPU");
IntPtr service = IOServiceGetMatchingService(IntPtr.Zero, serviceDict);
IntPtr cfString = CFStringCreateWithCString(_kCFAllocatorDefault, "vendor-id", _kCFStringEncodingASCII);
IntPtr cfProperty = IORegistryEntryCreateCFProperty(service, cfString, _kCFAllocatorDefault, 0);
byte[] buffer = new byte[4];
IntPtr bufferPtr = CFDataGetBytePtr(cfProperty);
Marshal.Copy(bufferPtr, buffer, 0, buffer.Length);
uint vendorId = BitConverter.ToUInt32(buffer);
return GetNameFromId(vendorId);
}
public static string GetModel()
{
IntPtr serviceDict = IOServiceMatching("IOGPU");
IntPtr service = IOServiceGetMatchingService(IntPtr.Zero, serviceDict);
IntPtr cfString = CFStringCreateWithCString(_kCFAllocatorDefault, "model", _kCFStringEncodingASCII);
IntPtr cfProperty = IORegistryEntryCreateCFProperty(service, cfString, _kCFAllocatorDefault, 0);
char[] buffer = new char[64];
IntPtr bufferPtr = Marshal.AllocHGlobal(buffer.Length);
if (CFStringGetCString(cfProperty, bufferPtr, buffer.Length, _kCFStringEncodingASCII))
{
string model = Marshal.PtrToStringUTF8(bufferPtr);
Marshal.FreeHGlobal(bufferPtr);
return model;
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Metal
{
interface IRefEquatable<T>
{
bool Equals(ref T other);
}
class HashTableSlim<TKey, TValue> where TKey : IRefEquatable<TKey>
{
private const int TotalBuckets = 16; // Must be power of 2
private const int TotalBucketsMask = TotalBuckets - 1;
private struct Entry
{
public int Hash;
public TKey Key;
public TValue Value;
}
private struct Bucket
{
public int Length;
public Entry[] Entries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<Entry> AsSpan()
{
return Entries == null ? Span<Entry>.Empty : Entries.AsSpan(0, Length);
}
}
private readonly Bucket[] _hashTable = new Bucket[TotalBuckets];
public IEnumerable<TKey> Keys
{
get
{
foreach (Bucket bucket in _hashTable)
{
for (int i = 0; i < bucket.Length; i++)
{
yield return bucket.Entries[i].Key;
}
}
}
}
public IEnumerable<TValue> Values
{
get
{
foreach (Bucket bucket in _hashTable)
{
for (int i = 0; i < bucket.Length; i++)
{
yield return bucket.Entries[i].Value;
}
}
}
}
public void Add(ref TKey key, TValue value)
{
Entry entry = new()
{
Hash = key.GetHashCode(),
Key = key,
Value = value,
};
int hashCode = key.GetHashCode();
int bucketIndex = hashCode & TotalBucketsMask;
ref Bucket bucket = ref _hashTable[bucketIndex];
if (bucket.Entries != null)
{
int index = bucket.Length;
if (index >= bucket.Entries.Length)
{
Array.Resize(ref bucket.Entries, index + 1);
}
bucket.Entries[index] = entry;
}
else
{
bucket.Entries =
[
entry
];
}
bucket.Length++;
}
public bool Remove(ref TKey key)
{
int hashCode = key.GetHashCode();
ref Bucket bucket = ref _hashTable[hashCode & TotalBucketsMask];
Span<Entry> entries = bucket.AsSpan();
for (int i = 0; i < entries.Length; i++)
{
ref Entry entry = ref entries[i];
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
entries[(i + 1)..].CopyTo(entries[i..]);
bucket.Length--;
return true;
}
}
return false;
}
public bool TryGetValue(ref TKey key, out TValue value)
{
int hashCode = key.GetHashCode();
Span<Entry> entries = _hashTable[hashCode & TotalBucketsMask].AsSpan();
for (int i = 0; i < entries.Length; i++)
{
ref Entry entry = ref entries[i];
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
value = entry.Value;
return true;
}
}
value = default;
return false;
}
}
}

View File

@@ -0,0 +1,868 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using SharpMetal.Metal;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class HelperShader : IDisposable
{
private const int ConvertElementsPerWorkgroup = 32 * 100; // Work group size of 32 times 100 elements.
private const string ShadersSourcePath = "/Ryujinx.Graphics.Metal/Shaders";
private readonly MetalRenderer _renderer;
private readonly Pipeline _pipeline;
private MTLDevice _device;
private readonly ISampler _samplerLinear;
private readonly ISampler _samplerNearest;
private readonly IProgram _programColorBlitF;
private readonly IProgram _programColorBlitI;
private readonly IProgram _programColorBlitU;
private readonly IProgram _programColorBlitMsF;
private readonly IProgram _programColorBlitMsI;
private readonly IProgram _programColorBlitMsU;
private readonly List<IProgram> _programsColorClearF = [];
private readonly List<IProgram> _programsColorClearI = [];
private readonly List<IProgram> _programsColorClearU = [];
private readonly IProgram _programDepthStencilClear;
private readonly IProgram _programStrideChange;
private readonly IProgram _programConvertD32S8ToD24S8;
private readonly IProgram _programConvertIndexBuffer;
private readonly IProgram _programDepthBlit;
private readonly IProgram _programDepthBlitMs;
private readonly IProgram _programStencilBlit;
private readonly IProgram _programStencilBlitMs;
private readonly EncoderState _helperShaderState = new();
public HelperShader(MTLDevice device, MetalRenderer renderer, Pipeline pipeline)
{
_device = device;
_renderer = renderer;
_pipeline = pipeline;
_samplerNearest = new SamplerHolder(renderer, _device, SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
_samplerLinear = new SamplerHolder(renderer, _device, SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
ResourceLayout blitResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Vertex, ResourceType.UniformBuffer, 0)
.Add(ResourceStages.Fragment, ResourceType.TextureAndSampler, 0).Build();
string blitSource = ReadMsl("Blit.metal");
string blitSourceF = blitSource.Replace("FORMAT", "float", StringComparison.Ordinal);
_programColorBlitF = new Program(renderer, device, [
new ShaderSource(blitSourceF, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
string blitSourceI = blitSource.Replace("FORMAT", "int");
_programColorBlitI = new Program(renderer, device, [
new ShaderSource(blitSourceI, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceI, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
string blitSourceU = blitSource.Replace("FORMAT", "uint");
_programColorBlitU = new Program(renderer, device, [
new ShaderSource(blitSourceU, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceU, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
string blitMsSource = ReadMsl("BlitMs.metal");
string blitMsSourceF = blitMsSource.Replace("FORMAT", "float");
_programColorBlitMsF = new Program(renderer, device, [
new ShaderSource(blitMsSourceF, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitMsSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
string blitMsSourceI = blitMsSource.Replace("FORMAT", "int");
_programColorBlitMsI = new Program(renderer, device, [
new ShaderSource(blitMsSourceI, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitMsSourceI, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
string blitMsSourceU = blitMsSource.Replace("FORMAT", "uint");
_programColorBlitMsU = new Program(renderer, device, [
new ShaderSource(blitMsSourceU, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitMsSourceU, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
ResourceLayout colorClearResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Fragment, ResourceType.UniformBuffer, 0).Build();
string colorClearSource = ReadMsl("ColorClear.metal");
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
string crntSource = colorClearSource.Replace("COLOR_ATTACHMENT_INDEX", i.ToString()).Replace("FORMAT", "float");
_programsColorClearF.Add(new Program(renderer, device, [
new ShaderSource(crntSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(crntSource, ShaderStage.Vertex, TargetLanguage.Msl)
], colorClearResourceLayout));
}
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
string crntSource = colorClearSource.Replace("COLOR_ATTACHMENT_INDEX", i.ToString()).Replace("FORMAT", "int");
_programsColorClearI.Add(new Program(renderer, device, [
new ShaderSource(crntSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(crntSource, ShaderStage.Vertex, TargetLanguage.Msl)
], colorClearResourceLayout));
}
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
string crntSource = colorClearSource.Replace("COLOR_ATTACHMENT_INDEX", i.ToString()).Replace("FORMAT", "uint");
_programsColorClearU.Add(new Program(renderer, device, [
new ShaderSource(crntSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(crntSource, ShaderStage.Vertex, TargetLanguage.Msl)
], colorClearResourceLayout));
}
string depthStencilClearSource = ReadMsl("DepthStencilClear.metal");
_programDepthStencilClear = new Program(renderer, device, [
new ShaderSource(depthStencilClearSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(depthStencilClearSource, ShaderStage.Vertex, TargetLanguage.Msl)
], colorClearResourceLayout);
ResourceLayout strideChangeResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
string strideChangeSource = ReadMsl("ChangeBufferStride.metal");
_programStrideChange = new Program(renderer, device, [
new ShaderSource(strideChangeSource, ShaderStage.Compute, TargetLanguage.Msl)
], strideChangeResourceLayout, new ComputeSize(64, 1, 1));
ResourceLayout convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
string convertD32S8ToD24S8Source = ReadMsl("ConvertD32S8ToD24S8.metal");
_programConvertD32S8ToD24S8 = new Program(renderer, device, [
new ShaderSource(convertD32S8ToD24S8Source, ShaderStage.Compute, TargetLanguage.Msl)
], convertD32S8ToD24S8ResourceLayout, new ComputeSize(64, 1, 1));
ResourceLayout convertIndexBufferLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
string convertIndexBufferSource = ReadMsl("ConvertIndexBuffer.metal");
_programConvertIndexBuffer = new Program(renderer, device, [
new ShaderSource(convertIndexBufferSource, ShaderStage.Compute, TargetLanguage.Msl)
], convertIndexBufferLayout, new ComputeSize(16, 1, 1));
string depthBlitSource = ReadMsl("DepthBlit.metal");
_programDepthBlit = new Program(renderer, device, [
new ShaderSource(depthBlitSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
string depthBlitMsSource = ReadMsl("DepthBlitMs.metal");
_programDepthBlitMs = new Program(renderer, device, [
new ShaderSource(depthBlitMsSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
string stencilBlitSource = ReadMsl("StencilBlit.metal");
_programStencilBlit = new Program(renderer, device, [
new ShaderSource(stencilBlitSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
string stencilBlitMsSource = ReadMsl("StencilBlitMs.metal");
_programStencilBlitMs = new Program(renderer, device, [
new ShaderSource(stencilBlitMsSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
}
private static string ReadMsl(string fileName)
{
string msl = EmbeddedResources.ReadAllText(string.Join('/', ShadersSourcePath, fileName));
#pragma warning disable IDE0055 // Disable formatting
msl = msl.Replace("CONSTANT_BUFFERS_INDEX", $"{Constants.ConstantBuffersIndex}")
.Replace("STORAGE_BUFFERS_INDEX", $"{Constants.StorageBuffersIndex}")
.Replace("TEXTURES_INDEX", $"{Constants.TexturesIndex}")
.Replace("IMAGES_INDEX", $"{Constants.ImagesIndex}");
#pragma warning restore IDE0055
return msl;
}
public unsafe void BlitColor(
CommandBufferScoped cbs,
Texture src,
Texture dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool linearFilter,
bool clear = false)
{
_pipeline.SwapState(_helperShaderState);
const int RegionBufferSize = 16;
ISampler sampler = linearFilter ? _samplerLinear : _samplerNearest;
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, sampler);
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
region[0] = srcRegion.X1 / (float)src.Width;
region[1] = srcRegion.X2 / (float)src.Width;
region[2] = srcRegion.Y1 / (float)src.Height;
region[3] = srcRegion.Y2 / (float)src.Height;
if (dstRegion.X1 > dstRegion.X2)
{
(region[0], region[1]) = (region[1], region[0]);
}
if (dstRegion.Y1 > dstRegion.Y2)
{
(region[2], region[3]) = (region[3], region[2]);
}
using ScopedTemporaryBuffer buffer = _renderer.BufferManager.ReserveOrCreate(cbs, RegionBufferSize);
buffer.Holder.SetDataUnchecked<float>(buffer.Offset, region);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Rectangle<float> rect = new(
MathF.Min(dstRegion.X1, dstRegion.X2),
MathF.Min(dstRegion.Y1, dstRegion.Y2),
MathF.Abs(dstRegion.X2 - dstRegion.X1),
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
Span<Viewport> viewports = stackalloc Viewport[16];
viewports[0] = new Viewport(
rect,
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
if (dstIsDepthOrStencil)
{
// TODO: Depth & stencil blit!
Logger.Warning?.PrintMsg(LogClass.Gpu, "Requested a depth or stencil blit!");
_pipeline.SwapState(null);
return;
}
string debugGroupName = "Blit Color ";
if (src.Info.Target.IsMultisample())
{
if (dst.Info.Format.IsSint())
{
debugGroupName += "MS Int";
_pipeline.SetProgram(_programColorBlitMsI);
}
else if (dst.Info.Format.IsUint())
{
debugGroupName += "MS UInt";
_pipeline.SetProgram(_programColorBlitMsU);
}
else
{
debugGroupName += "MS Float";
_pipeline.SetProgram(_programColorBlitMsF);
}
}
else
{
if (dst.Info.Format.IsSint())
{
debugGroupName += "Int";
_pipeline.SetProgram(_programColorBlitI);
}
else if (dst.Info.Format.IsUint())
{
debugGroupName += "UInt";
_pipeline.SetProgram(_programColorBlitU);
}
else
{
debugGroupName += "Float";
_pipeline.SetProgram(_programColorBlitF);
}
}
int dstWidth = dst.Width;
int dstHeight = dst.Height;
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[16];
scissors[0] = new Rectangle<int>(0, 0, dstWidth, dstHeight);
_pipeline.SetRenderTargets([dst], null);
_pipeline.SetScissors(scissors);
_pipeline.SetClearLoadAction(clear);
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0, debugGroupName);
// Cleanup
if (clear)
{
_pipeline.SetClearLoadAction(false);
}
// Restore previous state
_pipeline.SwapState(null);
}
public unsafe void BlitDepthStencil(
CommandBufferScoped cbs,
Texture src,
Texture dst,
Extents2D srcRegion,
Extents2D dstRegion)
{
_pipeline.SwapState(_helperShaderState);
const int RegionBufferSize = 16;
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
region[0] = srcRegion.X1 / (float)src.Width;
region[1] = srcRegion.X2 / (float)src.Width;
region[2] = srcRegion.Y1 / (float)src.Height;
region[3] = srcRegion.Y2 / (float)src.Height;
if (dstRegion.X1 > dstRegion.X2)
{
(region[0], region[1]) = (region[1], region[0]);
}
if (dstRegion.Y1 > dstRegion.Y2)
{
(region[2], region[3]) = (region[3], region[2]);
}
using ScopedTemporaryBuffer buffer = _renderer.BufferManager.ReserveOrCreate(cbs, RegionBufferSize);
buffer.Holder.SetDataUnchecked<float>(buffer.Offset, region);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Viewport> viewports = stackalloc Viewport[16];
Rectangle<float> rect = new(
MathF.Min(dstRegion.X1, dstRegion.X2),
MathF.Min(dstRegion.Y1, dstRegion.Y2),
MathF.Abs(dstRegion.X2 - dstRegion.X1),
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
viewports[0] = new Viewport(
rect,
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
int dstWidth = dst.Width;
int dstHeight = dst.Height;
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[16];
scissors[0] = new Rectangle<int>(0, 0, dstWidth, dstHeight);
_pipeline.SetRenderTargets([], dst);
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
if (src.Info.Format is
Format.D16Unorm or
Format.D32Float or
Format.X8UintD24Unorm or
Format.D24UnormS8Uint or
Format.D32FloatS8Uint or
Format.S8UintD24Unorm)
{
Texture depthTexture = CreateDepthOrStencilView(src, DepthStencilMode.Depth);
BlitDepthStencilDraw(depthTexture, isDepth: true);
if (depthTexture != src)
{
depthTexture.Release();
}
}
if (src.Info.Format is
Format.S8Uint or
Format.D24UnormS8Uint or
Format.D32FloatS8Uint or
Format.S8UintD24Unorm)
{
Texture stencilTexture = CreateDepthOrStencilView(src, DepthStencilMode.Stencil);
BlitDepthStencilDraw(stencilTexture, isDepth: false);
if (stencilTexture != src)
{
stencilTexture.Release();
}
}
// Restore previous state
_pipeline.SwapState(null);
}
private static Texture CreateDepthOrStencilView(Texture depthStencilTexture, DepthStencilMode depthStencilMode)
{
if (depthStencilTexture.Info.DepthStencilMode == depthStencilMode)
{
return depthStencilTexture;
}
return (Texture)depthStencilTexture.CreateView(new TextureCreateInfo(
depthStencilTexture.Info.Width,
depthStencilTexture.Info.Height,
depthStencilTexture.Info.Depth,
depthStencilTexture.Info.Levels,
depthStencilTexture.Info.Samples,
depthStencilTexture.Info.BlockWidth,
depthStencilTexture.Info.BlockHeight,
depthStencilTexture.Info.BytesPerPixel,
depthStencilTexture.Info.Format,
depthStencilMode,
depthStencilTexture.Info.Target,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha), 0, 0);
}
private void BlitDepthStencilDraw(Texture src, bool isDepth)
{
// TODO: Check this https://github.com/Ryujinx/Ryujinx/pull/5003/
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, _samplerNearest);
string debugGroupName;
if (isDepth)
{
debugGroupName = "Depth Blit";
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
}
else
{
debugGroupName = "Stencil Blit";
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit);
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
}
_pipeline.Draw(4, 1, 0, 0, debugGroupName);
if (isDepth)
{
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
}
else
{
_pipeline.SetStencilTest(CreateStencilTestDescriptor(false));
}
}
public unsafe void DrawTexture(
ITexture src,
ISampler srcSampler,
Extents2DF srcRegion,
Extents2DF dstRegion)
{
// Save current state
PredrawState state = _pipeline.SavePredrawState();
_pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetStencilTest(new StencilTestDescriptor());
_pipeline.SetDepthTest(new DepthTestDescriptor());
const int RegionBufferSize = 16;
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, srcSampler);
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
region[0] = srcRegion.X1 / src.Width;
region[1] = srcRegion.X2 / src.Width;
region[2] = srcRegion.Y1 / src.Height;
region[3] = srcRegion.Y2 / src.Height;
if (dstRegion.X1 > dstRegion.X2)
{
(region[0], region[1]) = (region[1], region[0]);
}
if (dstRegion.Y1 > dstRegion.Y2)
{
(region[2], region[3]) = (region[3], region[2]);
}
BufferHandle bufferHandle = _renderer.BufferManager.CreateWithHandle(RegionBufferSize);
_renderer.BufferManager.SetData<float>(bufferHandle, 0, region);
_pipeline.SetUniformBuffers([new BufferAssignment(0, new BufferRange(bufferHandle, 0, RegionBufferSize))]);
Span<Viewport> viewports = stackalloc Viewport[16];
Rectangle<float> rect = new(
MathF.Min(dstRegion.X1, dstRegion.X2),
MathF.Min(dstRegion.Y1, dstRegion.Y2),
MathF.Abs(dstRegion.X2 - dstRegion.X1),
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
viewports[0] = new Viewport(
rect,
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
_pipeline.SetProgram(_programColorBlitF);
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0, "Draw Texture");
_renderer.BufferManager.Delete(bufferHandle);
// Restore previous state
_pipeline.RestorePredrawState(state);
}
public void ConvertI8ToI16(CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size)
{
ChangeStride(cbs, src, dst, srcOffset, size, 1, 2);
}
public unsafe void ChangeStride(
CommandBufferScoped cbs,
BufferHolder src,
BufferHolder dst,
int srcOffset,
int size,
int stride,
int newStride)
{
int elems = size / stride;
Auto<DisposableBuffer> srcBuffer = src.GetBuffer();
Auto<DisposableBuffer> dstBuffer = dst.GetBuffer();
const int ParamsBufferSize = 4 * sizeof(int);
// Save current state
_pipeline.SwapState(_helperShaderState);
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
shaderParams[0] = stride;
shaderParams[1] = newStride;
shaderParams[2] = size;
shaderParams[3] = srcOffset;
using ScopedTemporaryBuffer buffer = _renderer.BufferManager.ReserveOrCreate(cbs, ParamsBufferSize);
buffer.Holder.SetDataUnchecked<int>(buffer.Offset, shaderParams);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
sbRanges[0] = srcBuffer;
sbRanges[1] = dstBuffer;
_pipeline.SetStorageBuffers(1, sbRanges);
_pipeline.SetProgram(_programStrideChange);
_pipeline.DispatchCompute(1 + elems / ConvertElementsPerWorkgroup, 1, 1, "Change Stride");
// Restore previous state
_pipeline.SwapState(null);
}
public unsafe void ConvertD32S8ToD24S8(CommandBufferScoped cbs, BufferHolder src, Auto<DisposableBuffer> dstBuffer, int pixelCount, int dstOffset)
{
int inSize = pixelCount * 2 * sizeof(int);
Auto<DisposableBuffer> srcBuffer = src.GetBuffer();
const int ParamsBufferSize = sizeof(int) * 2;
// Save current state
_pipeline.SwapState(_helperShaderState);
Span<int> shaderParams = stackalloc int[2];
shaderParams[0] = pixelCount;
shaderParams[1] = dstOffset;
using ScopedTemporaryBuffer buffer = _renderer.BufferManager.ReserveOrCreate(cbs, ParamsBufferSize);
buffer.Holder.SetDataUnchecked<int>(buffer.Offset, shaderParams);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
sbRanges[0] = srcBuffer;
sbRanges[1] = dstBuffer;
_pipeline.SetStorageBuffers(1, sbRanges);
_pipeline.SetProgram(_programConvertD32S8ToD24S8);
_pipeline.DispatchCompute(1 + inSize / ConvertElementsPerWorkgroup, 1, 1, "D32S8 to D24S8 Conversion");
// Restore previous state
_pipeline.SwapState(null);
}
public void ConvertIndexBuffer(
CommandBufferScoped cbs,
BufferHolder src,
BufferHolder dst,
IndexBufferPattern pattern,
int indexSize,
int srcOffset,
int indexCount)
{
// TODO: Support conversion with primitive restart enabled.
int primitiveCount = pattern.GetPrimitiveCount(indexCount);
int outputIndexSize = 4;
Auto<DisposableBuffer> srcBuffer = src.GetBuffer();
Auto<DisposableBuffer> dstBuffer = dst.GetBuffer();
const int ParamsBufferSize = 16 * sizeof(int);
// Save current state
_pipeline.SwapState(_helperShaderState);
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
shaderParams[8] = pattern.PrimitiveVertices;
shaderParams[9] = pattern.PrimitiveVerticesOut;
shaderParams[10] = indexSize;
shaderParams[11] = outputIndexSize;
shaderParams[12] = pattern.BaseIndex;
shaderParams[13] = pattern.IndexStride;
shaderParams[14] = srcOffset;
shaderParams[15] = primitiveCount;
pattern.OffsetIndex.CopyTo(shaderParams[..pattern.OffsetIndex.Length]);
using ScopedTemporaryBuffer patternScoped = _renderer.BufferManager.ReserveOrCreate(cbs, ParamsBufferSize);
patternScoped.Holder.SetDataUnchecked<int>(patternScoped.Offset, shaderParams);
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
sbRanges[0] = srcBuffer;
sbRanges[1] = dstBuffer;
_pipeline.SetStorageBuffers(1, sbRanges);
_pipeline.SetStorageBuffers([new BufferAssignment(3, patternScoped.Range)]);
_pipeline.SetProgram(_programConvertIndexBuffer);
_pipeline.DispatchCompute(BitUtils.DivRoundUp(primitiveCount, 16), 1, 1, "Convert Index Buffer");
// Restore previous state
_pipeline.SwapState(null);
}
public unsafe void ClearColor(
int index,
ReadOnlySpan<float> clearColor,
uint componentMask,
int dstWidth,
int dstHeight,
Format format)
{
// Keep original scissor
DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors);
// Save current state
EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags, false);
// Inherit some state without fully recreating render pipeline.
RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, false, index);
const int ClearColorBufferSize = 16;
// TODO: Flush
using ScopedTemporaryBuffer buffer = _renderer.BufferManager.ReserveOrCreate(_pipeline.Cbs, ClearColorBufferSize);
buffer.Holder.SetDataUnchecked(buffer.Offset, clearColor);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Viewport> viewports = stackalloc Viewport[16];
// TODO: Set exact viewport!
viewports[0] = new Viewport(
new Rectangle<float>(0, 0, dstWidth, dstHeight),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
Span<uint> componentMasks = stackalloc uint[index + 1];
componentMasks[index] = componentMask;
string debugGroupName = "Clear Color ";
if (format.IsSint())
{
debugGroupName += "Int";
_pipeline.SetProgram(_programsColorClearI[index]);
}
else if (format.IsUint())
{
debugGroupName += "UInt";
_pipeline.SetProgram(_programsColorClearU[index]);
}
else
{
debugGroupName += "Float";
_pipeline.SetProgram(_programsColorClearF[index]);
}
_pipeline.SetBlendState(index, new BlendDescriptor());
_pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
_pipeline.SetRenderTargetColorMasks(componentMasks);
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0, debugGroupName);
// Restore previous state
_pipeline.SwapState(null, clearFlags, false);
_helperShaderState.Restore(save);
}
public unsafe void ClearDepthStencil(
float depthValue,
bool depthMask,
int stencilValue,
int stencilMask,
int dstWidth,
int dstHeight)
{
// Keep original scissor
DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors);
MTLScissorRect[] helperScissors = _helperShaderState.Scissors;
// Save current state
EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags, false);
// Inherit some state without fully recreating render pipeline.
RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, true);
const int ClearDepthBufferSize = 16;
using ScopedTemporaryBuffer buffer = _renderer.BufferManager.ReserveOrCreate(_pipeline.Cbs, ClearDepthBufferSize);
buffer.Holder.SetDataUnchecked(buffer.Offset, new ReadOnlySpan<float>(ref depthValue));
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Viewport> viewports = stackalloc Viewport[1];
viewports[0] = new Viewport(
new Rectangle<float>(0, 0, dstWidth, dstHeight),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
_pipeline.SetProgram(_programDepthStencilClear);
_pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.SetViewports(viewports);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always));
_pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xFF, stencilMask));
_pipeline.Draw(4, 1, 0, 0, "Clear Depth Stencil");
// Cleanup
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
_pipeline.SetStencilTest(CreateStencilTestDescriptor(false));
// Restore previous state
_pipeline.SwapState(null, clearFlags, false);
_helperShaderState.Restore(save);
}
private static StencilTestDescriptor CreateStencilTestDescriptor(
bool enabled,
int refValue = 0,
int compareMask = 0xff,
int writeMask = 0xff)
{
return new StencilTestDescriptor(
enabled,
CompareOp.Always,
StencilOp.Replace,
StencilOp.Replace,
StencilOp.Replace,
refValue,
compareMask,
writeMask,
CompareOp.Always,
StencilOp.Replace,
StencilOp.Replace,
StencilOp.Replace,
refValue,
compareMask,
writeMask);
}
public void Dispose()
{
_programColorBlitF.Dispose();
_programColorBlitI.Dispose();
_programColorBlitU.Dispose();
_programColorBlitMsF.Dispose();
_programColorBlitMsI.Dispose();
_programColorBlitMsU.Dispose();
foreach (IProgram programColorClear in _programsColorClearF)
{
programColorClear.Dispose();
}
foreach (IProgram programColorClear in _programsColorClearU)
{
programColorClear.Dispose();
}
foreach (IProgram programColorClear in _programsColorClearI)
{
programColorClear.Dispose();
}
_programDepthStencilClear.Dispose();
_pipeline.Dispose();
_samplerLinear.Dispose();
_samplerNearest.Dispose();
}
}
}

View File

@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Metal
{
class IdList<T> where T : class
{
private readonly List<T> _list;
private int _freeMin;
public IdList()
{
_list = [];
_freeMin = 0;
}
public int Add(T value)
{
int id;
int count = _list.Count;
id = _list.IndexOf(null, _freeMin);
if ((uint)id < (uint)count)
{
_list[id] = value;
}
else
{
id = count;
_freeMin = id + 1;
_list.Add(value);
}
return id + 1;
}
public void Remove(int id)
{
id--;
int count = _list.Count;
if ((uint)id >= (uint)count)
{
return;
}
if (id + 1 == count)
{
// Trim unused items.
int removeIndex = id;
while (removeIndex > 0 && _list[removeIndex - 1] == null)
{
removeIndex--;
}
_list.RemoveRange(removeIndex, count - removeIndex);
if (_freeMin > removeIndex)
{
_freeMin = removeIndex;
}
}
else
{
_list[id] = null;
if (_freeMin > id)
{
_freeMin = id;
}
}
}
public bool TryGetValue(int id, out T value)
{
id--;
try
{
if ((uint)id < (uint)_list.Count)
{
value = _list[id];
return value != null;
}
value = null;
return false;
}
catch (ArgumentOutOfRangeException)
{
value = null;
return false;
}
catch (IndexOutOfRangeException)
{
value = null;
return false;
}
}
public void Clear()
{
_list.Clear();
_freeMin = 0;
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < _list.Count; i++)
{
if (_list[i] != null)
{
yield return _list[i];
}
}
}
}
}

View File

@@ -0,0 +1,74 @@
using Ryujinx.Graphics.GAL;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
internal class ImageArray : IImageArray
{
private readonly TextureRef[] _textureRefs;
private readonly TextureBuffer[] _bufferTextureRefs;
private readonly bool _isBuffer;
private readonly Pipeline _pipeline;
public ImageArray(int size, bool isBuffer, Pipeline pipeline)
{
if (isBuffer)
{
_bufferTextureRefs = new TextureBuffer[size];
}
else
{
_textureRefs = new TextureRef[size];
}
_isBuffer = isBuffer;
_pipeline = pipeline;
}
public void SetImages(int index, ITexture[] images)
{
for (int i = 0; i < images.Length; i++)
{
ITexture image = images[i];
if (image is TextureBuffer textureBuffer)
{
_bufferTextureRefs[index + i] = textureBuffer;
}
else if (image is Texture texture)
{
_textureRefs[index + i].Storage = texture;
}
else if (!_isBuffer)
{
_textureRefs[index + i].Storage = null;
}
else
{
_bufferTextureRefs[index + i] = null;
}
}
SetDirty();
}
public TextureRef[] GetTextureRefs()
{
return _textureRefs;
}
public TextureBuffer[] GetBufferTextureRefs()
{
return _bufferTextureRefs;
}
private void SetDirty()
{
_pipeline.DirtyImages();
}
public void Dispose() { }
}
}

View File

@@ -0,0 +1,118 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
internal class IndexBufferPattern : IDisposable
{
public int PrimitiveVertices { get; }
public int PrimitiveVerticesOut { get; }
public int BaseIndex { get; }
public int[] OffsetIndex { get; }
public int IndexStride { get; }
public bool RepeatStart { get; }
private readonly MetalRenderer _renderer;
private int _currentSize;
private BufferHandle _repeatingBuffer;
public IndexBufferPattern(MetalRenderer renderer,
int primitiveVertices,
int primitiveVerticesOut,
int baseIndex,
int[] offsetIndex,
int indexStride,
bool repeatStart)
{
PrimitiveVertices = primitiveVertices;
PrimitiveVerticesOut = primitiveVerticesOut;
BaseIndex = baseIndex;
OffsetIndex = offsetIndex;
IndexStride = indexStride;
RepeatStart = repeatStart;
_renderer = renderer;
}
public int GetPrimitiveCount(int vertexCount)
{
return Math.Max(0, (vertexCount - BaseIndex) / IndexStride);
}
public int GetConvertedCount(int indexCount)
{
int primitiveCount = GetPrimitiveCount(indexCount);
return primitiveCount * OffsetIndex.Length;
}
public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
{
int primitiveCount = GetPrimitiveCount(vertexCount);
indexCount = primitiveCount * PrimitiveVerticesOut;
int expectedSize = primitiveCount * OffsetIndex.Length;
if (expectedSize <= _currentSize && _repeatingBuffer != BufferHandle.Null)
{
return _repeatingBuffer;
}
// Expand the repeating pattern to the number of requested primitives.
BufferHandle newBuffer = _renderer.BufferManager.CreateWithHandle(expectedSize * sizeof(int));
// Copy the old data to the new one.
if (_repeatingBuffer != BufferHandle.Null)
{
_renderer.Pipeline.CopyBuffer(_repeatingBuffer, newBuffer, 0, 0, _currentSize * sizeof(int));
_renderer.BufferManager.Delete(_repeatingBuffer);
}
_repeatingBuffer = newBuffer;
// Add the additional repeats on top.
int newPrimitives = primitiveCount;
int oldPrimitives = (_currentSize) / OffsetIndex.Length;
int[] newData;
newPrimitives -= oldPrimitives;
newData = new int[expectedSize - _currentSize];
int outOffset = 0;
int index = oldPrimitives * IndexStride + BaseIndex;
for (int i = 0; i < newPrimitives; i++)
{
if (RepeatStart)
{
// Used for triangle fan
newData[outOffset++] = 0;
}
for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
{
newData[outOffset++] = index + OffsetIndex[j];
}
index += IndexStride;
}
_renderer.SetBufferData(newBuffer, _currentSize * sizeof(int), MemoryMarshal.Cast<int, byte>(newData));
_currentSize = expectedSize;
return newBuffer;
}
public void Dispose()
{
if (_repeatingBuffer != BufferHandle.Null)
{
_renderer.BufferManager.Delete(_repeatingBuffer);
_repeatingBuffer = BufferHandle.Null;
}
}
}
}

View File

@@ -0,0 +1,103 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
readonly internal struct IndexBufferState
{
public static IndexBufferState Null => new(BufferHandle.Null, 0, 0);
private readonly int _offset;
private readonly int _size;
private readonly IndexType _type;
private readonly BufferHandle _handle;
public IndexBufferState(BufferHandle handle, int offset, int size, IndexType type = IndexType.UInt)
{
_handle = handle;
_offset = offset;
_size = size;
_type = type;
}
public (MTLBuffer, int, MTLIndexType) GetIndexBuffer(MetalRenderer renderer, CommandBufferScoped cbs)
{
Auto<DisposableBuffer> autoBuffer;
int offset, size;
MTLIndexType type;
if (_type == IndexType.UByte)
{
// Index type is not supported. Convert to I16.
autoBuffer = renderer.BufferManager.GetBufferI8ToI16(cbs, _handle, _offset, _size);
type = MTLIndexType.UInt16;
offset = 0;
size = _size * 2;
}
else
{
autoBuffer = renderer.BufferManager.GetBuffer(_handle, false, out int bufferSize);
if (_offset >= bufferSize)
{
autoBuffer = null;
}
type = _type.Convert();
offset = _offset;
size = _size;
}
if (autoBuffer != null)
{
DisposableBuffer buffer = autoBuffer.Get(cbs, offset, size);
return (buffer.Value, offset, type);
}
return (new MTLBuffer(IntPtr.Zero), 0, MTLIndexType.UInt16);
}
public (MTLBuffer, int, MTLIndexType) GetConvertedIndexBuffer(
MetalRenderer renderer,
CommandBufferScoped cbs,
int firstIndex,
int indexCount,
int convertedCount,
IndexBufferPattern pattern)
{
// Convert the index buffer using the given pattern.
int indexSize = GetIndexSize();
int firstIndexOffset = firstIndex * indexSize;
Auto<DisposableBuffer> autoBuffer = renderer.BufferManager.GetBufferTopologyConversion(cbs, _handle, _offset + firstIndexOffset, indexCount * indexSize, pattern, indexSize);
int size = convertedCount * 4;
if (autoBuffer != null)
{
DisposableBuffer buffer = autoBuffer.Get(cbs, 0, size);
return (buffer.Value, 0, MTLIndexType.UInt32);
}
return (new MTLBuffer(IntPtr.Zero), 0, MTLIndexType.UInt32);
}
private int GetIndexSize()
{
return _type switch
{
IndexType.UInt => 4,
IndexType.UShort => 2,
_ => 1,
};
}
}
}

View File

@@ -0,0 +1,312 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader.Translation;
using SharpMetal.Metal;
using SharpMetal.QuartzCore;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
public sealed class MetalRenderer : IRenderer
{
public const int TotalSets = 4;
private readonly MTLDevice _device;
private readonly MTLCommandQueue _queue;
private readonly Func<CAMetalLayer> _getMetalLayer;
private Pipeline _pipeline;
private Window _window;
public uint ProgramCount { get; set; }
#pragma warning disable CS0067 // The event is never used
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
#pragma warning restore CS0067
public bool PreferThreading => true;
public IPipeline Pipeline => _pipeline;
public IWindow Window => _window;
internal MTLCommandQueue BackgroundQueue { get; private set; }
internal HelperShader HelperShader { get; private set; }
internal BufferManager BufferManager { get; private set; }
internal CommandBufferPool CommandBufferPool { get; private set; }
internal BackgroundResources BackgroundResources { get; private set; }
internal Action<Action> InterruptAction { get; private set; }
internal SyncManager SyncManager { get; private set; }
internal HashSet<Program> Programs { get; }
internal HashSet<SamplerHolder> Samplers { get; }
public MetalRenderer(Func<CAMetalLayer> metalLayer)
{
_device = MTLDevice.CreateSystemDefaultDevice();
Programs = [];
Samplers = [];
if (_device.ArgumentBuffersSupport != MTLArgumentBuffersTier.Tier2)
{
throw new NotSupportedException("Metal backend requires Tier 2 Argument Buffer support.");
}
_queue = _device.NewCommandQueue(CommandBufferPool.MaxCommandBuffers + 1);
BackgroundQueue = _device.NewCommandQueue(CommandBufferPool.MaxCommandBuffers);
_getMetalLayer = metalLayer;
}
public void Initialize(GraphicsDebugLevel logLevel)
{
CAMetalLayer layer = _getMetalLayer();
layer.Device = _device;
layer.FramebufferOnly = false;
CommandBufferPool = new CommandBufferPool(_queue);
_window = new Window(this, layer);
_pipeline = new Pipeline(_device, this);
BufferManager = new BufferManager(_device, this, _pipeline);
_pipeline.InitEncoderStateManager(BufferManager);
BackgroundResources = new BackgroundResources(this);
HelperShader = new HelperShader(_device, this, _pipeline);
SyncManager = new SyncManager(this);
}
public void BackgroundContextAction(Action action, bool alwaysBackground = false)
{
// GetData methods should be thread safe, so we can call this directly.
// Texture copy (scaled) may also happen in here, so that should also be thread safe.
action();
}
public BufferHandle CreateBuffer(int size, BufferAccess access)
{
return BufferManager.CreateWithHandle(size);
}
public BufferHandle CreateBuffer(IntPtr pointer, int size)
{
return BufferManager.Create(pointer, size);
}
public BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers)
{
throw new NotImplementedException();
}
public IImageArray CreateImageArray(int size, bool isBuffer)
{
return new ImageArray(size, isBuffer, _pipeline);
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
ProgramCount++;
return new Program(this, _device, shaders, info.ResourceLayout, info.ComputeLocalSize);
}
public ISampler CreateSampler(SamplerCreateInfo info)
{
return new SamplerHolder(this, _device, info);
}
public ITexture CreateTexture(TextureCreateInfo info)
{
if (info.Target == Target.TextureBuffer)
{
return new TextureBuffer(_device, this, _pipeline, info);
}
return new Texture(_device, this, _pipeline, info);
}
public ITextureArray CreateTextureArray(int size, bool isBuffer)
{
return new TextureArray(size, isBuffer, _pipeline);
}
public bool PrepareHostMapping(IntPtr address, ulong size)
{
// TODO: Metal Host Mapping
return false;
}
public void CreateSync(ulong id, bool strict)
{
SyncManager.Create(id, strict);
}
public void DeleteBuffer(BufferHandle buffer)
{
BufferManager.Delete(buffer);
}
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{
return BufferManager.GetData(buffer, offset, size);
}
public Capabilities GetCapabilities()
{
// TODO: Finalize these values
return new Capabilities(
api: TargetApi.Metal,
vendorName: HardwareInfoTools.GetVendor(),
SystemMemoryType.UnifiedMemory,
hasFrontFacingBug: false,
hasVectorIndexingBug: false,
needsFragmentOutputSpecialization: true,
reduceShaderPrecision: true,
supportsAstcCompression: true,
supportsBc123Compression: true,
supportsBc45Compression: true,
supportsBc67Compression: true,
supportsEtc2Compression: true,
supports3DTextureCompression: true,
supportsBgraFormat: true,
supportsR4G4Format: false,
supportsR4G4B4A4Format: true,
supportsScaledVertexFormats: false,
supportsSnormBufferTextureFormat: true,
supportsSparseBuffer: false,
supports5BitComponentFormat: true,
supportsBlendEquationAdvanced: false,
supportsFragmentShaderInterlock: true,
supportsFragmentShaderOrderingIntel: false,
supportsGeometryShader: false,
supportsGeometryShaderPassthrough: false,
supportsTransformFeedback: false,
supportsImageLoadFormatted: false,
supportsLayerVertexTessellation: false,
supportsMismatchingViewFormat: true,
supportsCubemapView: true,
supportsNonConstantTextureOffset: false,
supportsQuads: false,
supportsSeparateSampler: true,
supportsShaderBallot: false,
supportsShaderBarrierDivergence: false,
supportsShaderFloat64: false,
supportsTextureGatherOffsets: false,
supportsTextureShadowLod: false,
supportsVertexStoreAndAtomics: false,
supportsViewportIndexVertexTessellation: false,
supportsViewportMask: false,
supportsViewportSwizzle: false,
supportsIndirectParameters: true,
supportsDepthClipControl: false,
uniformBufferSetIndex: (int)Constants.ConstantBuffersSetIndex,
storageBufferSetIndex: (int)Constants.StorageBuffersSetIndex,
textureSetIndex: (int)Constants.TexturesSetIndex,
imageSetIndex: (int)Constants.ImagesSetIndex,
extraSetBaseIndex: TotalSets,
maximumExtraSets: (int)Constants.MaximumExtraSets,
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: Constants.MaxTexturesPerStage,
maximumImagesPerStage: Constants.MaxImagesPerStage,
maximumComputeSharedMemorySize: (int)_device.MaxThreadgroupMemoryLength,
maximumSupportedAnisotropy: 16,
shaderSubgroupSize: 256,
storageBufferOffsetAlignment: 16,
textureBufferOffsetAlignment: 16,
gatherBiasPrecision: 0,
maximumGpuMemory: 0
);
}
public ulong GetCurrentSync()
{
return SyncManager.GetCurrent();
}
public HardwareInfo GetHardwareInfo()
{
return new HardwareInfo(HardwareInfoTools.GetVendor(), HardwareInfoTools.GetModel(), "Apple");
}
public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info)
{
throw new NotImplementedException();
}
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
{
BufferManager.SetData(buffer, offset, data, _pipeline.Cbs);
}
public void UpdateCounters()
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
}
public void PreFrame()
{
SyncManager.Cleanup();
}
public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, float divisor, bool hostReserved)
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
CounterEvent counterEvent = new();
resultHandler?.Invoke(counterEvent, type == CounterType.SamplesPassed ? (ulong)1 : 0);
return counterEvent;
}
public void ResetCounter(CounterType type)
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
}
public void WaitSync(ulong id)
{
SyncManager.Wait(id);
}
public void FlushAllCommands()
{
_pipeline.FlushCommandsImpl();
}
public void RegisterFlush()
{
SyncManager.RegisterFlush();
// Periodically free unused regions of the staging buffer to avoid doing it all at once.
BufferManager.StagingBuffer.FreeCompleted();
}
public void SetInterruptAction(Action<Action> interruptAction)
{
InterruptAction = interruptAction;
}
public void Screenshot()
{
// TODO: Screenshots
}
public void Dispose()
{
BackgroundResources.Dispose();
foreach (Program program in Programs)
{
program.Dispose();
}
foreach (SamplerHolder sampler in Samplers)
{
sampler.Dispose();
}
_pipeline.Dispose();
_window.Dispose();
}
}
}

View File

@@ -0,0 +1,262 @@
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
/// <summary>
/// Holder for multiple host GPU fences.
/// </summary>
[SupportedOSPlatform("macos")]
class MultiFenceHolder
{
private const int BufferUsageTrackingGranularity = 4096;
private readonly FenceHolder[] _fences;
private readonly BufferUsageBitmap _bufferUsageBitmap;
/// <summary>
/// Creates a new instance of the multiple fence holder.
/// </summary>
public MultiFenceHolder()
{
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
}
/// <summary>
/// Creates a new instance of the multiple fence holder, with a given buffer size in mind.
/// </summary>
/// <param name="size">Size of the buffer</param>
public MultiFenceHolder(int size)
{
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
}
/// <summary>
/// Adds read/write buffer usage information to the uses list.
/// </summary>
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
/// <param name="offset">Offset of the buffer being used</param>
/// <param name="size">Size of the buffer region being used, in bytes</param>
/// <param name="write">Whether the access is a write or not</param>
public void AddBufferUse(int cbIndex, int offset, int size, bool write)
{
_bufferUsageBitmap.Add(cbIndex, offset, size, false);
if (write)
{
_bufferUsageBitmap.Add(cbIndex, offset, size, true);
}
}
/// <summary>
/// Removes all buffer usage information for a given command buffer.
/// </summary>
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
public void RemoveBufferUses(int cbIndex)
{
_bufferUsageBitmap?.Clear(cbIndex);
}
/// <summary>
/// Checks if a given range of a buffer is being used by a command buffer still being processed by the GPU.
/// </summary>
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
/// <param name="offset">Offset of the buffer being used</param>
/// <param name="size">Size of the buffer region being used, in bytes</param>
/// <returns>True if in use, false otherwise</returns>
public bool IsBufferRangeInUse(int cbIndex, int offset, int size)
{
return _bufferUsageBitmap.OverlapsWith(cbIndex, offset, size);
}
/// <summary>
/// Checks if a given range of a buffer is being used by any command buffer still being processed by the GPU.
/// </summary>
/// <param name="offset">Offset of the buffer being used</param>
/// <param name="size">Size of the buffer region being used, in bytes</param>
/// <param name="write">True if only write usages should count</param>
/// <returns>True if in use, false otherwise</returns>
public bool IsBufferRangeInUse(int offset, int size, bool write)
{
return _bufferUsageBitmap.OverlapsWith(offset, size, write);
}
/// <summary>
/// Adds a fence to the holder.
/// </summary>
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
/// <param name="fence">Fence to be added</param>
/// <returns>True if the command buffer's previous fence value was null</returns>
public bool AddFence(int cbIndex, FenceHolder fence)
{
ref FenceHolder fenceRef = ref _fences[cbIndex];
if (fenceRef == null)
{
fenceRef = fence;
return true;
}
return false;
}
/// <summary>
/// Removes a fence from the holder.
/// </summary>
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
public void RemoveFence(int cbIndex)
{
_fences[cbIndex] = null;
}
/// <summary>
/// Determines if a fence referenced on the given command buffer.
/// </summary>
/// <param name="cbIndex">Index of the command buffer to check if it's used</param>
/// <returns>True if referenced, false otherwise</returns>
public bool HasFence(int cbIndex)
{
return _fences[cbIndex] != null;
}
/// <summary>
/// Wait until all the fences on the holder are signaled.
/// </summary>
public void WaitForFences()
{
WaitForFencesImpl(0, 0, true);
}
/// <summary>
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
/// </summary>
/// <param name="offset">Start offset of the buffer range</param>
/// <param name="size">Size of the buffer range in bytes</param>
public void WaitForFences(int offset, int size)
{
WaitForFencesImpl(offset, size, true);
}
/// <summary>
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
/// </summary>
// TODO: Add a proper timeout!
public bool WaitForFences(bool indefinite)
{
return WaitForFencesImpl(0, 0, indefinite);
}
/// <summary>
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
/// </summary>
/// <param name="offset">Start offset of the buffer range</param>
/// <param name="size">Size of the buffer range in bytes</param>
/// <param name="indefinite">Indicates if this should wait indefinitely</param>
/// <returns>True if all fences were signaled before the timeout expired, false otherwise</returns>
private bool WaitForFencesImpl(int offset, int size, bool indefinite)
{
Span<FenceHolder> fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
Span<MTLCommandBuffer> fences = stackalloc MTLCommandBuffer[count];
int fenceCount = 0;
for (int i = 0; i < count; i++)
{
if (fenceHolders[i].TryGet(out MTLCommandBuffer fence))
{
fences[fenceCount] = fence;
if (fenceCount < i)
{
fenceHolders[fenceCount] = fenceHolders[i];
}
fenceCount++;
}
}
if (fenceCount == 0)
{
return true;
}
bool signaled = true;
if (indefinite)
{
foreach (MTLCommandBuffer fence in fences)
{
fence.WaitUntilCompleted();
}
}
else
{
foreach (MTLCommandBuffer fence in fences)
{
if (fence.Status != MTLCommandBufferStatus.Completed)
{
signaled = false;
}
}
}
for (int i = 0; i < fenceCount; i++)
{
fenceHolders[i].Put();
}
return signaled;
}
/// <summary>
/// Gets fences to wait for.
/// </summary>
/// <param name="storage">Span to store fences in</param>
/// <returns>Number of fences placed in storage</returns>
private int GetFences(Span<FenceHolder> storage)
{
int count = 0;
for (int i = 0; i < _fences.Length; i++)
{
FenceHolder fence = _fences[i];
if (fence != null)
{
storage[count++] = fence;
}
}
return count;
}
/// <summary>
/// Gets fences to wait for use of a given buffer region.
/// </summary>
/// <param name="storage">Span to store overlapping fences in</param>
/// <param name="offset">Offset of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>Number of fences for the specified region placed in storage</returns>
private int GetOverlappingFences(Span<FenceHolder> storage, int offset, int size)
{
int count = 0;
for (int i = 0; i < _fences.Length; i++)
{
FenceHolder fence = _fences[i];
if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size))
{
storage[count++] = fence;
}
}
return count;
}
}
}

View File

@@ -0,0 +1,100 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
internal class PersistentFlushBuffer : IDisposable
{
private readonly MetalRenderer _renderer;
private BufferHolder _flushStorage;
public PersistentFlushBuffer(MetalRenderer renderer)
{
_renderer = renderer;
}
private BufferHolder ResizeIfNeeded(int size)
{
BufferHolder flushStorage = _flushStorage;
if (flushStorage == null || size > _flushStorage.Size)
{
flushStorage?.Dispose();
flushStorage = _renderer.BufferManager.Create(size);
_flushStorage = flushStorage;
}
return flushStorage;
}
public Span<byte> GetBufferData(CommandBufferPool cbp, BufferHolder buffer, int offset, int size)
{
BufferHolder flushStorage = ResizeIfNeeded(size);
Auto<DisposableBuffer> srcBuffer;
using (CommandBufferScoped cbs = cbp.Rent())
{
srcBuffer = buffer.GetBuffer();
Auto<DisposableBuffer> dstBuffer = flushStorage.GetBuffer();
if (srcBuffer.TryIncrementReferenceCount())
{
BufferHolder.Copy(cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false);
}
else
{
// Source buffer is no longer alive, don't copy anything to flush storage.
srcBuffer = null;
}
}
flushStorage.WaitForFences();
srcBuffer?.DecrementReferenceCount();
return flushStorage.GetDataStorage(0, size);
}
public Span<byte> GetTextureData(CommandBufferPool cbp, Texture view, int size)
{
TextureCreateInfo info = view.Info;
BufferHolder flushStorage = ResizeIfNeeded(size);
using (CommandBufferScoped cbs = cbp.Rent())
{
MTLBuffer buffer = flushStorage.GetBuffer().Get(cbs).Value;
MTLTexture image = view.GetHandle();
view.CopyFromOrToBuffer(cbs, buffer, image, size, true, 0, 0, info.GetLayers(), info.Levels, singleSlice: false);
}
flushStorage.WaitForFences();
return flushStorage.GetDataStorage(0, size);
}
public Span<byte> GetTextureData(CommandBufferPool cbp, Texture view, int size, int layer, int level)
{
BufferHolder flushStorage = ResizeIfNeeded(size);
using (CommandBufferScoped cbs = cbp.Rent())
{
MTLBuffer buffer = flushStorage.GetBuffer().Get(cbs).Value;
MTLTexture image = view.GetHandle();
view.CopyFromOrToBuffer(cbs, buffer, image, size, true, layer, level, 1, 1, singleSlice: true);
}
flushStorage.WaitForFences();
return flushStorage.GetDataStorage(0, size);
}
public void Dispose()
{
_flushStorage.Dispose();
}
}
}

View File

@@ -0,0 +1,877 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using SharpMetal.QuartzCore;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
public enum EncoderType
{
Blit,
Compute,
Render,
None
}
[SupportedOSPlatform("macos")]
class Pipeline : IPipeline, IEncoderFactory, IDisposable
{
private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB
private readonly MTLDevice _device;
private readonly MetalRenderer _renderer;
private EncoderStateManager _encoderStateManager;
private ulong _byteWeight;
public MTLCommandBuffer CommandBuffer;
public IndexBufferPattern QuadsToTrisPattern;
public IndexBufferPattern TriFanToTrisPattern;
internal CommandBufferScoped? PreloadCbs { get; private set; }
internal CommandBufferScoped Cbs { get; private set; }
internal CommandBufferEncoder Encoders => Cbs.Encoders;
internal EncoderType CurrentEncoderType => Encoders.CurrentEncoderType;
public Pipeline(MTLDevice device, MetalRenderer renderer)
{
_device = device;
_renderer = renderer;
renderer.CommandBufferPool.Initialize(this);
CommandBuffer = (Cbs = _renderer.CommandBufferPool.Rent()).CommandBuffer;
}
internal void InitEncoderStateManager(BufferManager bufferManager)
{
_encoderStateManager = new EncoderStateManager(_device, bufferManager, this);
QuadsToTrisPattern = new IndexBufferPattern(_renderer, 4, 6, 0, [0, 1, 2, 0, 2, 3], 4, false);
TriFanToTrisPattern = new IndexBufferPattern(_renderer, 3, 3, 2, [int.MinValue, -1, 0], 1, true);
}
public EncoderState SwapState(EncoderState state, DirtyFlags flags = DirtyFlags.All, bool endRenderPass = true)
{
if (endRenderPass && CurrentEncoderType == EncoderType.Render)
{
EndCurrentPass();
}
return _encoderStateManager.SwapState(state, flags);
}
public PredrawState SavePredrawState()
{
return _encoderStateManager.SavePredrawState();
}
public void RestorePredrawState(PredrawState state)
{
_encoderStateManager.RestorePredrawState(state);
}
public void SetClearLoadAction(bool clear)
{
_encoderStateManager.SetClearLoadAction(clear);
}
public MTLRenderCommandEncoder GetOrCreateRenderEncoder(bool forDraw = false)
{
// Mark all state as dirty to ensure it is set on the new encoder
if (Cbs.Encoders.CurrentEncoderType != EncoderType.Render)
{
_encoderStateManager.SignalRenderDirty();
}
if (forDraw)
{
_encoderStateManager.RenderResourcesPrepass();
}
MTLRenderCommandEncoder renderCommandEncoder = Cbs.Encoders.EnsureRenderEncoder();
if (forDraw)
{
_encoderStateManager.RebindRenderState(renderCommandEncoder);
}
return renderCommandEncoder;
}
public MTLBlitCommandEncoder GetOrCreateBlitEncoder()
{
return Cbs.Encoders.EnsureBlitEncoder();
}
public MTLComputeCommandEncoder GetOrCreateComputeEncoder(bool forDispatch = false)
{
// Mark all state as dirty to ensure it is set on the new encoder
if (Cbs.Encoders.CurrentEncoderType != EncoderType.Compute)
{
_encoderStateManager.SignalComputeDirty();
}
if (forDispatch)
{
_encoderStateManager.ComputeResourcesPrepass();
}
MTLComputeCommandEncoder computeCommandEncoder = Cbs.Encoders.EnsureComputeEncoder();
if (forDispatch)
{
_encoderStateManager.RebindComputeState(computeCommandEncoder);
}
return computeCommandEncoder;
}
public void EndCurrentPass()
{
Cbs.Encoders.EndCurrentPass();
}
public MTLRenderCommandEncoder CreateRenderCommandEncoder()
{
return _encoderStateManager.CreateRenderCommandEncoder();
}
public MTLComputeCommandEncoder CreateComputeCommandEncoder()
{
return _encoderStateManager.CreateComputeCommandEncoder();
}
public void Present(CAMetalDrawable drawable, Texture src, Extents2D srcRegion, Extents2D dstRegion, bool isLinear)
{
// TODO: Clean this up
TextureCreateInfo textureInfo = new((int)drawable.Texture.Width, (int)drawable.Texture.Height, (int)drawable.Texture.Depth, (int)drawable.Texture.MipmapLevelCount, (int)drawable.Texture.SampleCount, 0, 0, 0, Format.B8G8R8A8Unorm, 0, Target.Texture2D, SwizzleComponent.Red, SwizzleComponent.Green, SwizzleComponent.Blue, SwizzleComponent.Alpha);
Texture dst = new(_device, _renderer, this, textureInfo, drawable.Texture, 0, 0);
_renderer.HelperShader.BlitColor(Cbs, src, dst, srcRegion, dstRegion, isLinear, true);
EndCurrentPass();
Cbs.CommandBuffer.PresentDrawable(drawable);
FlushCommandsImpl();
// TODO: Auto flush counting
_renderer.SyncManager.GetAndResetWaitTicks();
// Cleanup
dst.Dispose();
}
public CommandBufferScoped GetPreloadCommandBuffer()
{
PreloadCbs ??= _renderer.CommandBufferPool.Rent();
return PreloadCbs.Value;
}
public void FlushCommandsIfWeightExceeding(IAuto disposedResource, ulong byteWeight)
{
bool usedByCurrentCb = disposedResource.HasCommandBufferDependency(Cbs);
if (PreloadCbs != null && !usedByCurrentCb)
{
usedByCurrentCb = disposedResource.HasCommandBufferDependency(PreloadCbs.Value);
}
if (usedByCurrentCb)
{
// Since we can only free memory after the command buffer that uses a given resource was executed,
// keeping the command buffer might cause a high amount of memory to be in use.
// To prevent that, we force submit command buffers if the memory usage by resources
// in use by the current command buffer is above a given limit, and those resources were disposed.
_byteWeight += byteWeight;
if (_byteWeight >= MinByteWeightForFlush)
{
FlushCommandsImpl();
}
}
}
public void FlushCommandsImpl()
{
EndCurrentPass();
_byteWeight = 0;
if (PreloadCbs != null)
{
PreloadCbs.Value.Dispose();
PreloadCbs = null;
}
CommandBuffer = (Cbs = _renderer.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
_renderer.RegisterFlush();
}
public void DirtyTextures()
{
_encoderStateManager.DirtyTextures();
}
public void DirtyImages()
{
_encoderStateManager.DirtyImages();
}
public void Blit(
Texture src,
Texture dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool isDepthOrStencil,
bool linearFilter)
{
if (isDepthOrStencil)
{
_renderer.HelperShader.BlitDepthStencil(Cbs, src, dst, srcRegion, dstRegion);
}
else
{
_renderer.HelperShader.BlitColor(Cbs, src, dst, srcRegion, dstRegion, linearFilter);
}
}
public void Barrier()
{
switch (CurrentEncoderType)
{
case EncoderType.Render:
{
MTLBarrierScope scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets;
MTLRenderStages stages = MTLRenderStages.RenderStageVertex | MTLRenderStages.RenderStageFragment;
Encoders.RenderEncoder.MemoryBarrier(scope, stages, stages);
break;
}
case EncoderType.Compute:
{
MTLBarrierScope scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets;
Encoders.ComputeEncoder.MemoryBarrier(scope);
break;
}
}
}
public void ClearBuffer(BufferHandle destination, int offset, int size, uint value)
{
MTLBlitCommandEncoder blitCommandEncoder = GetOrCreateBlitEncoder();
MTLBuffer mtlBuffer = _renderer.BufferManager.GetBuffer(destination, offset, size, true).Get(Cbs, offset, size, true).Value;
// Might need a closer look, range's count, lower, and upper bound
// must be a multiple of 4
blitCommandEncoder.FillBuffer(mtlBuffer,
new NSRange
{
location = (ulong)offset,
length = (ulong)size
},
(byte)value);
}
public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color)
{
float[] colors = [color.Red, color.Green, color.Blue, color.Alpha];
Texture dst = _encoderStateManager.RenderTargets[index];
// TODO: Remove workaround for Wonder which has an invalid texture due to unsupported format
if (dst == null)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, "Attempted to clear invalid render target!");
return;
}
_renderer.HelperShader.ClearColor(index, colors, componentMask, dst.Width, dst.Height, dst.Info.Format);
}
public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
{
Texture depthStencil = _encoderStateManager.DepthStencil;
if (depthStencil == null)
{
return;
}
_renderer.HelperShader.ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask, depthStencil.Width, depthStencil.Height);
}
public void CommandBufferBarrier()
{
Barrier();
}
public void CopyBuffer(BufferHandle src, BufferHandle dst, int srcOffset, int dstOffset, int size)
{
Auto<DisposableBuffer> srcBuffer = _renderer.BufferManager.GetBuffer(src, srcOffset, size, false);
Auto<DisposableBuffer> dstBuffer = _renderer.BufferManager.GetBuffer(dst, dstOffset, size, true);
BufferHolder.Copy(Cbs, srcBuffer, dstBuffer, srcOffset, dstOffset, size);
}
public void PushDebugGroup(string name)
{
MTLCommandEncoder? encoder = Encoders.CurrentEncoder;
NSString debugGroupName = StringHelper.NSString(name);
if (encoder == null)
{
return;
}
switch (Encoders.CurrentEncoderType)
{
case EncoderType.Render:
encoder.Value.PushDebugGroup(debugGroupName);
break;
case EncoderType.Blit:
encoder.Value.PushDebugGroup(debugGroupName);
break;
case EncoderType.Compute:
encoder.Value.PushDebugGroup(debugGroupName);
break;
}
}
public void PopDebugGroup()
{
MTLCommandEncoder? encoder = Encoders.CurrentEncoder;
if (encoder == null)
{
return;
}
switch (Encoders.CurrentEncoderType)
{
case EncoderType.Render:
encoder.Value.PopDebugGroup();
break;
case EncoderType.Blit:
encoder.Value.PopDebugGroup();
break;
case EncoderType.Compute:
encoder.Value.PopDebugGroup();
break;
}
}
public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
{
DispatchCompute(groupsX, groupsY, groupsZ, String.Empty);
}
public void DispatchCompute(int groupsX, int groupsY, int groupsZ, string debugGroupName)
{
MTLComputeCommandEncoder computeCommandEncoder = GetOrCreateComputeEncoder(true);
ComputeSize localSize = _encoderStateManager.ComputeLocalSize;
if (debugGroupName != String.Empty)
{
PushDebugGroup(debugGroupName);
}
computeCommandEncoder.DispatchThreadgroups(
new MTLSize { width = (ulong)groupsX, height = (ulong)groupsY, depth = (ulong)groupsZ },
new MTLSize { width = (ulong)localSize.X, height = (ulong)localSize.Y, depth = (ulong)localSize.Z });
if (debugGroupName != String.Empty)
{
PopDebugGroup();
}
}
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
{
Draw(vertexCount, instanceCount, firstVertex, firstInstance, String.Empty);
}
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance, string debugGroupName)
{
if (vertexCount == 0)
{
return;
}
MTLPrimitiveType primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
if (TopologyUnsupported(_encoderStateManager.Topology))
{
IndexBufferPattern pattern = GetIndexBufferPattern();
BufferHandle handle = pattern.GetRepeatingBuffer(vertexCount, out int indexCount);
Auto<DisposableBuffer> buffer = _renderer.BufferManager.GetBuffer(handle, false);
MTLBuffer mtlBuffer = buffer.Get(Cbs, 0, indexCount * sizeof(int)).Value;
MTLRenderCommandEncoder renderCommandEncoder = GetOrCreateRenderEncoder(true);
renderCommandEncoder.DrawIndexedPrimitives(
primitiveType,
(ulong)indexCount,
MTLIndexType.UInt32,
mtlBuffer,
0);
}
else
{
MTLRenderCommandEncoder renderCommandEncoder = GetOrCreateRenderEncoder(true);
if (debugGroupName != String.Empty)
{
PushDebugGroup(debugGroupName);
}
renderCommandEncoder.DrawPrimitives(
primitiveType,
(ulong)firstVertex,
(ulong)vertexCount,
(ulong)instanceCount,
(ulong)firstInstance);
if (debugGroupName != String.Empty)
{
PopDebugGroup();
}
}
}
private IndexBufferPattern GetIndexBufferPattern()
{
return _encoderStateManager.Topology switch
{
PrimitiveTopology.Quads => QuadsToTrisPattern,
PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => TriFanToTrisPattern,
_ => throw new NotSupportedException($"Unsupported topology: {_encoderStateManager.Topology}"),
};
}
private PrimitiveTopology TopologyRemap(PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.Quads => PrimitiveTopology.Triangles,
PrimitiveTopology.QuadStrip => PrimitiveTopology.TriangleStrip,
PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => PrimitiveTopology.Triangles,
_ => topology,
};
}
private bool TopologyUnsupported(PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.Quads or PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => true,
_ => false,
};
}
public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
{
if (indexCount == 0)
{
return;
}
MTLBuffer mtlBuffer;
int offset;
MTLIndexType type;
int finalIndexCount = indexCount;
MTLPrimitiveType primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
if (TopologyUnsupported(_encoderStateManager.Topology))
{
IndexBufferPattern pattern = GetIndexBufferPattern();
int convertedCount = pattern.GetConvertedCount(indexCount);
finalIndexCount = convertedCount;
(mtlBuffer, offset, type) = _encoderStateManager.IndexBuffer.GetConvertedIndexBuffer(_renderer, Cbs, firstIndex, indexCount, convertedCount, pattern);
}
else
{
(mtlBuffer, offset, type) = _encoderStateManager.IndexBuffer.GetIndexBuffer(_renderer, Cbs);
}
if (mtlBuffer.NativePtr != IntPtr.Zero)
{
MTLRenderCommandEncoder renderCommandEncoder = GetOrCreateRenderEncoder(true);
renderCommandEncoder.DrawIndexedPrimitives(
primitiveType,
(ulong)finalIndexCount,
type,
mtlBuffer,
(ulong)offset,
(ulong)instanceCount,
firstVertex,
(ulong)firstInstance);
}
}
public void DrawIndexedIndirect(BufferRange indirectBuffer)
{
DrawIndexedIndirectOffset(indirectBuffer);
}
public void DrawIndexedIndirectOffset(BufferRange indirectBuffer, int offset = 0)
{
// TODO: Reindex unsupported topologies
if (TopologyUnsupported(_encoderStateManager.Topology))
{
Logger.Warning?.Print(LogClass.Gpu, $"Drawing indexed with unsupported topology: {_encoderStateManager.Topology}");
}
MTLBuffer buffer = _renderer.BufferManager
.GetBuffer(indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
MTLPrimitiveType primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
(MTLBuffer indexBuffer, int indexOffset, MTLIndexType type) = _encoderStateManager.IndexBuffer.GetIndexBuffer(_renderer, Cbs);
if (indexBuffer.NativePtr != IntPtr.Zero && buffer.NativePtr != IntPtr.Zero)
{
MTLRenderCommandEncoder renderCommandEncoder = GetOrCreateRenderEncoder(true);
renderCommandEncoder.DrawIndexedPrimitives(
primitiveType,
type,
indexBuffer,
(ulong)indexOffset,
buffer,
(ulong)(indirectBuffer.Offset + offset));
}
}
public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
{
for (int i = 0; i < maxDrawCount; i++)
{
DrawIndexedIndirectOffset(indirectBuffer, stride * i);
}
}
public void DrawIndirect(BufferRange indirectBuffer)
{
DrawIndirectOffset(indirectBuffer);
}
public void DrawIndirectOffset(BufferRange indirectBuffer, int offset = 0)
{
if (TopologyUnsupported(_encoderStateManager.Topology))
{
// TODO: Reindex unsupported topologies
Logger.Warning?.Print(LogClass.Gpu, $"Drawing indirect with unsupported topology: {_encoderStateManager.Topology}");
}
MTLBuffer buffer = _renderer.BufferManager
.GetBuffer(indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
MTLPrimitiveType primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
MTLRenderCommandEncoder renderCommandEncoder = GetOrCreateRenderEncoder(true);
renderCommandEncoder.DrawPrimitives(
primitiveType,
buffer,
(ulong)(indirectBuffer.Offset + offset));
}
public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
{
for (int i = 0; i < maxDrawCount; i++)
{
DrawIndirectOffset(indirectBuffer, stride * i);
}
}
public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
{
_renderer.HelperShader.DrawTexture(texture, sampler, srcRegion, dstRegion);
}
public void SetAlphaTest(bool enable, float reference, CompareOp op)
{
// This is currently handled using shader specialization, as Metal does not support alpha test.
// In the future, we may want to use this to write the reference value into the support buffer,
// to avoid creating one version of the shader per reference value used.
}
public void SetBlendState(AdvancedBlendDescriptor blend)
{
// Metal does not support advanced blend.
}
public void SetBlendState(int index, BlendDescriptor blend)
{
_encoderStateManager.UpdateBlendDescriptors(index, blend);
}
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
{
if (enables == 0)
{
_encoderStateManager.UpdateDepthBias(0, 0, 0);
}
else
{
_encoderStateManager.UpdateDepthBias(units, factor, clamp);
}
}
public void SetDepthClamp(bool clamp)
{
_encoderStateManager.UpdateDepthClamp(clamp);
}
public void SetDepthMode(DepthMode mode)
{
// Metal does not support depth clip control.
}
public void SetDepthTest(DepthTestDescriptor depthTest)
{
_encoderStateManager.UpdateDepthState(depthTest);
}
public void SetFaceCulling(bool enable, Face face)
{
_encoderStateManager.UpdateCullMode(enable, face);
}
public void SetFrontFace(FrontFace frontFace)
{
_encoderStateManager.UpdateFrontFace(frontFace);
}
public void SetIndexBuffer(BufferRange buffer, IndexType type)
{
_encoderStateManager.UpdateIndexBuffer(buffer, type);
}
public void SetImage(ShaderStage stage, int binding, ITexture image)
{
if (image is TextureBase img)
{
_encoderStateManager.UpdateImage(stage, binding, img);
}
}
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
{
if (array is ImageArray imageArray)
{
_encoderStateManager.UpdateImageArray(stage, binding, imageArray);
}
}
public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
{
if (array is ImageArray imageArray)
{
_encoderStateManager.UpdateImageArraySeparate(stage, setIndex, imageArray);
}
}
public void SetLineParameters(float width, bool smooth)
{
// Metal does not support wide-lines.
}
public void SetLogicOpState(bool enable, LogicalOp op)
{
_encoderStateManager.UpdateLogicOpState(enable, op);
}
public void SetMultisampleState(MultisampleDescriptor multisample)
{
_encoderStateManager.UpdateMultisampleState(multisample);
}
public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode)
{
// Metal does not support polygon mode.
}
public void SetPrimitiveRestart(bool enable, int index)
{
// Always active for LineStrip and TriangleStrip
// https://github.com/gpuweb/gpuweb/issues/1220#issuecomment-732483263
// https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515520-drawindexedprimitives
// https://stackoverflow.com/questions/70813665/how-to-render-multiple-trianglestrips-using-metal
// Emulating disabling this is very difficult. It's unlikely for an index buffer to use the largest possible index,
// so it's fine nearly all of the time.
}
public void SetPrimitiveTopology(PrimitiveTopology topology)
{
_encoderStateManager.UpdatePrimitiveTopology(topology);
}
public void SetProgram(IProgram program)
{
_encoderStateManager.UpdateProgram(program);
}
public void SetRasterizerDiscard(bool discard)
{
_encoderStateManager.UpdateRasterizerDiscard(discard);
}
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
{
_encoderStateManager.UpdateRenderTargetColorMasks(componentMask);
}
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
{
_encoderStateManager.UpdateRenderTargets(colors, depthStencil);
}
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
{
_encoderStateManager.UpdateScissors(regions);
}
public void SetStencilTest(StencilTestDescriptor stencilTest)
{
_encoderStateManager.UpdateStencilState(stencilTest);
}
public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_encoderStateManager.UpdateUniformBuffers(buffers);
}
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_encoderStateManager.UpdateStorageBuffers(buffers);
}
internal void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
{
_encoderStateManager.UpdateStorageBuffers(first, buffers);
}
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
{
if (texture is TextureBase tex)
{
if (sampler == null || sampler is SamplerHolder)
{
_encoderStateManager.UpdateTextureAndSampler(stage, binding, tex, (SamplerHolder)sampler);
}
}
}
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
{
if (array is TextureArray textureArray)
{
_encoderStateManager.UpdateTextureArray(stage, binding, textureArray);
}
}
public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
{
if (array is TextureArray textureArray)
{
_encoderStateManager.UpdateTextureArraySeparate(stage, setIndex, textureArray);
}
}
public void SetUserClipDistance(int index, bool enableClip)
{
// TODO. Same as Vulkan
}
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
{
_encoderStateManager.UpdateVertexAttribs(vertexAttribs);
}
public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
{
_encoderStateManager.UpdateVertexBuffers(vertexBuffers);
}
public void SetViewports(ReadOnlySpan<Viewport> viewports)
{
_encoderStateManager.UpdateViewports(viewports);
}
public void TextureBarrier()
{
if (CurrentEncoderType == EncoderType.Render)
{
Encoders.RenderEncoder.MemoryBarrier(MTLBarrierScope.Textures, MTLRenderStages.RenderStageFragment, MTLRenderStages.RenderStageFragment);
}
}
public void TextureBarrierTiled()
{
TextureBarrier();
}
public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual)
{
// TODO: Implementable via indirect draw commands
return false;
}
public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual)
{
// TODO: Implementable via indirect draw commands
return false;
}
public void EndHostConditionalRendering()
{
// TODO: Implementable via indirect draw commands
}
public void BeginTransformFeedback(PrimitiveTopology topology)
{
// Metal does not support transform feedback.
}
public void EndTransformFeedback()
{
// Metal does not support transform feedback.
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{
// Metal does not support transform feedback.
}
public void Dispose()
{
EndCurrentPass();
_encoderStateManager.Dispose();
}
}
}

View File

@@ -0,0 +1,286 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class Program : IProgram
{
private ProgramLinkStatus _status;
private readonly ShaderSource[] _shaders;
private readonly GCHandle[] _handles;
private int _successCount;
private readonly MetalRenderer _renderer;
public MTLFunction VertexFunction;
public MTLFunction FragmentFunction;
public MTLFunction ComputeFunction;
public ComputeSize ComputeLocalSize { get; }
private HashTableSlim<PipelineUid, MTLRenderPipelineState> _graphicsPipelineCache;
private MTLComputePipelineState? _computePipelineCache;
private bool _firstBackgroundUse;
public ResourceBindingSegment[][] BindingSegments { get; }
// Argument buffer sizes for Vertex or Compute stages
public int[] ArgumentBufferSizes { get; }
// Argument buffer sizes for Fragment stage
public int[] FragArgumentBufferSizes { get; }
public Program(
MetalRenderer renderer,
MTLDevice device,
ShaderSource[] shaders,
ResourceLayout resourceLayout,
ComputeSize computeLocalSize = default)
{
_renderer = renderer;
renderer.Programs.Add(this);
ComputeLocalSize = computeLocalSize;
_shaders = shaders;
_handles = new GCHandle[_shaders.Length];
_status = ProgramLinkStatus.Incomplete;
for (int i = 0; i < _shaders.Length; i++)
{
ShaderSource shader = _shaders[i];
using MTLCompileOptions compileOptions = new()
{
PreserveInvariance = true,
LanguageVersion = MTLLanguageVersion.Version31,
};
int index = i;
_handles[i] = device.NewLibrary(StringHelper.NSString(shader.Code), compileOptions, (library, error) => CompilationResultHandler(library, error, index));
}
(BindingSegments, ArgumentBufferSizes, FragArgumentBufferSizes) = BuildBindingSegments(resourceLayout.SetUsages);
}
public void CompilationResultHandler(MTLLibrary library, NSError error, int index)
{
ShaderSource shader = _shaders[index];
if (_handles[index].IsAllocated)
{
_handles[index].Free();
}
if (error != IntPtr.Zero)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, shader.Code);
Logger.Warning?.Print(LogClass.Gpu, $"{shader.Stage} shader linking failed: \n{StringHelper.String(error.LocalizedDescription)}");
_status = ProgramLinkStatus.Failure;
return;
}
switch (shader.Stage)
{
case ShaderStage.Compute:
ComputeFunction = library.NewFunction(StringHelper.NSString("kernelMain"));
break;
case ShaderStage.Vertex:
VertexFunction = library.NewFunction(StringHelper.NSString("vertexMain"));
break;
case ShaderStage.Fragment:
FragmentFunction = library.NewFunction(StringHelper.NSString("fragmentMain"));
break;
default:
Logger.Warning?.Print(LogClass.Gpu, $"Cannot handle stage {shader.Stage}!");
break;
}
_successCount++;
if (_successCount >= _shaders.Length && _status != ProgramLinkStatus.Failure)
{
_status = ProgramLinkStatus.Success;
}
}
private static (ResourceBindingSegment[][], int[], int[]) BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages)
{
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
int[] argBufferSizes = new int[setUsages.Count];
int[] fragArgBufferSizes = new int[setUsages.Count];
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
{
List<ResourceBindingSegment> currentSegments = [];
ResourceUsage currentUsage = default;
int currentCount = 0;
for (int index = 0; index < setUsages[setIndex].Usages.Count; index++)
{
ResourceUsage usage = setUsages[setIndex].Usages[index];
if (currentUsage.Binding + currentCount != usage.Binding ||
currentUsage.Type != usage.Type ||
currentUsage.Stages != usage.Stages ||
currentUsage.ArrayLength > 1 ||
usage.ArrayLength > 1)
{
if (currentCount != 0)
{
currentSegments.Add(new ResourceBindingSegment(
currentUsage.Binding,
currentCount,
currentUsage.Type,
currentUsage.Stages,
currentUsage.ArrayLength > 1));
int size = currentCount * ResourcePointerSize(currentUsage.Type);
if (currentUsage.Stages.HasFlag(ResourceStages.Fragment))
{
fragArgBufferSizes[setIndex] += size;
}
if (currentUsage.Stages.HasFlag(ResourceStages.Vertex) ||
currentUsage.Stages.HasFlag(ResourceStages.Compute))
{
argBufferSizes[setIndex] += size;
}
}
currentUsage = usage;
currentCount = usage.ArrayLength;
}
else
{
currentCount++;
}
}
if (currentCount != 0)
{
currentSegments.Add(new ResourceBindingSegment(
currentUsage.Binding,
currentCount,
currentUsage.Type,
currentUsage.Stages,
currentUsage.ArrayLength > 1));
int size = currentCount * ResourcePointerSize(currentUsage.Type);
if (currentUsage.Stages.HasFlag(ResourceStages.Fragment))
{
fragArgBufferSizes[setIndex] += size;
}
if (currentUsage.Stages.HasFlag(ResourceStages.Vertex) ||
currentUsage.Stages.HasFlag(ResourceStages.Compute))
{
argBufferSizes[setIndex] += size;
}
}
segments[setIndex] = currentSegments.ToArray();
}
return (segments, argBufferSizes, fragArgBufferSizes);
}
private static int ResourcePointerSize(ResourceType type)
{
return (type == ResourceType.TextureAndSampler ? 2 : 1);
}
public ProgramLinkStatus CheckProgramLink(bool blocking)
{
if (blocking)
{
while (_status == ProgramLinkStatus.Incomplete)
{ }
return _status;
}
return _status;
}
public byte[] GetBinary()
{
return [];
}
public void AddGraphicsPipeline(ref PipelineUid key, MTLRenderPipelineState pipeline)
{
(_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
}
public void AddComputePipeline(MTLComputePipelineState pipeline)
{
_computePipelineCache = pipeline;
}
public bool TryGetGraphicsPipeline(ref PipelineUid key, out MTLRenderPipelineState pipeline)
{
if (_graphicsPipelineCache == null)
{
pipeline = default;
return false;
}
if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline))
{
if (_firstBackgroundUse)
{
Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?");
_firstBackgroundUse = false;
}
return false;
}
_firstBackgroundUse = false;
return true;
}
public bool TryGetComputePipeline(out MTLComputePipelineState pipeline)
{
if (_computePipelineCache.HasValue)
{
pipeline = _computePipelineCache.Value;
return true;
}
pipeline = default;
return false;
}
public void Dispose()
{
if (!_renderer.Programs.Remove(this))
{
return;
}
if (_graphicsPipelineCache != null)
{
foreach (MTLRenderPipelineState pipeline in _graphicsPipelineCache.Values)
{
pipeline.Dispose();
}
}
_computePipelineCache?.Dispose();
VertexFunction.Dispose();
FragmentFunction.Dispose();
ComputeFunction.Dispose();
}
}
}

View File

@@ -0,0 +1,22 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Metal
{
readonly struct ResourceBindingSegment
{
public readonly int Binding;
public readonly int Count;
public readonly ResourceType Type;
public readonly ResourceStages Stages;
public readonly bool IsArray;
public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, bool isArray)
{
Binding = binding;
Count = count;
Type = type;
Stages = stages;
IsArray = isArray;
}
}
}

View File

@@ -0,0 +1,59 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class ResourceLayoutBuilder
{
private const int TotalSets = MetalRenderer.TotalSets;
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
private readonly List<ResourceUsage>[] _resourceUsages;
public ResourceLayoutBuilder()
{
_resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
_resourceUsages = new List<ResourceUsage>[TotalSets];
for (int index = 0; index < TotalSets; index++)
{
_resourceDescriptors[index] = [];
_resourceUsages[index] = [];
}
}
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding, bool write = false)
{
uint setIndex = type switch
{
ResourceType.UniformBuffer => Constants.ConstantBuffersSetIndex,
ResourceType.StorageBuffer => Constants.StorageBuffersSetIndex,
ResourceType.TextureAndSampler or ResourceType.BufferTexture => Constants.TexturesSetIndex,
ResourceType.Image or ResourceType.BufferImage => Constants.ImagesSetIndex,
_ => throw new ArgumentException($"Invalid resource type \"{type}\"."),
};
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages, write));
return this;
}
public ResourceLayout Build()
{
ResourceDescriptorCollection[] descriptors = new ResourceDescriptorCollection[TotalSets];
ResourceUsageCollection[] usages = new ResourceUsageCollection[TotalSets];
for (int index = 0; index < TotalSets; index++)
{
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
}
return new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly());
}
}
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Metal.SharpMetalExtensions\Ryujinx.Graphics.Metal.SharpMetalExtensions.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Shaders\Blit.metal" />
<EmbeddedResource Include="Shaders\BlitMs.metal" />
<EmbeddedResource Include="Shaders\ChangeBufferStride.metal" />
<EmbeddedResource Include="Shaders\ConvertD32S8ToD24S8.metal" />
<EmbeddedResource Include="Shaders\ConvertIndexBuffer.metal" />
<EmbeddedResource Include="Shaders\ColorClear.metal" />
<EmbeddedResource Include="Shaders\DepthStencilClear.metal" />
<EmbeddedResource Include="Shaders\DepthBlit.metal" />
<EmbeddedResource Include="Shaders\DepthBlitMs.metal" />
<EmbeddedResource Include="Shaders\StencilBlit.metal" />
<EmbeddedResource Include="Shaders\StencilBlitMs.metal" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,90 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class SamplerHolder : ISampler
{
private readonly MetalRenderer _renderer;
private readonly Auto<DisposableSampler> _sampler;
public SamplerHolder(MetalRenderer renderer, MTLDevice device, SamplerCreateInfo info)
{
_renderer = renderer;
renderer.Samplers.Add(this);
(MTLSamplerMinMagFilter minFilter, MTLSamplerMipFilter mipFilter) = info.MinFilter.Convert();
MTLSamplerBorderColor borderColor = GetConstrainedBorderColor(info.BorderColor, out _);
using MTLSamplerDescriptor descriptor = new()
{
BorderColor = borderColor,
MinFilter = minFilter,
MagFilter = info.MagFilter.Convert(),
MipFilter = mipFilter,
CompareFunction = info.CompareOp.Convert(),
LodMinClamp = info.MinLod,
LodMaxClamp = info.MaxLod,
LodAverage = false,
MaxAnisotropy = Math.Max((uint)info.MaxAnisotropy, 1),
SAddressMode = info.AddressU.Convert(),
TAddressMode = info.AddressV.Convert(),
RAddressMode = info.AddressP.Convert(),
SupportArgumentBuffers = true
};
MTLSamplerState sampler = device.NewSamplerState(descriptor);
_sampler = new Auto<DisposableSampler>(new DisposableSampler(sampler));
}
private static MTLSamplerBorderColor GetConstrainedBorderColor(ColorF arbitraryBorderColor, out bool cantConstrain)
{
float r = arbitraryBorderColor.Red;
float g = arbitraryBorderColor.Green;
float b = arbitraryBorderColor.Blue;
float a = arbitraryBorderColor.Alpha;
if (r == 0f && g == 0f && b == 0f)
{
if (a == 1f)
{
cantConstrain = false;
return MTLSamplerBorderColor.OpaqueBlack;
}
if (a == 0f)
{
cantConstrain = false;
return MTLSamplerBorderColor.TransparentBlack;
}
}
else if (r == 1f && g == 1f && b == 1f && a == 1f)
{
cantConstrain = false;
return MTLSamplerBorderColor.OpaqueWhite;
}
cantConstrain = true;
return MTLSamplerBorderColor.OpaqueBlack;
}
public Auto<DisposableSampler> GetSampler()
{
return _sampler;
}
public void Dispose()
{
if (_renderer.Samplers.Remove(this))
{
_sampler.Dispose();
}
}
}
}

View File

@@ -0,0 +1,43 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct TexCoords {
float data[4];
};
struct ConstantBuffers {
constant TexCoords* tex_coord;
};
struct Textures
{
texture2d<FORMAT, access::sample> texture;
sampler sampler;
};
vertex CopyVertexOut vertexMain(uint vid [[vertex_id]],
constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]]) {
CopyVertexOut out;
int low = vid & 1;
int high = vid >> 1;
out.uv.x = constant_buffers.tex_coord->data[low];
out.uv.y = constant_buffers.tex_coord->data[2 + high];
out.position.x = (float(low) - 0.5f) * 2.0f;
out.position.y = (float(high) - 0.5f) * 2.0f;
out.position.z = 0.0f;
out.position.w = 1.0f;
return out;
}
fragment FORMAT4 fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]]) {
return textures.texture.sample(textures.sampler, in.uv);
}

View File

@@ -0,0 +1,45 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct TexCoords {
float data[4];
};
struct ConstantBuffers {
constant TexCoords* tex_coord;
};
struct Textures
{
texture2d_ms<FORMAT, access::read> texture;
};
vertex CopyVertexOut vertexMain(uint vid [[vertex_id]],
constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]]) {
CopyVertexOut out;
int low = vid & 1;
int high = vid >> 1;
out.uv.x = constant_buffers.tex_coord->data[low];
out.uv.y = constant_buffers.tex_coord->data[2 + high];
out.position.x = (float(low) - 0.5f) * 2.0f;
out.position.y = (float(high) - 0.5f) * 2.0f;
out.position.z = 0.0f;
out.position.w = 1.0f;
return out;
}
fragment FORMAT4 fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]],
uint sample_id [[sample_id]]) {
uint2 tex_size = uint2(textures.texture.get_width(), textures.texture.get_height());
uint2 tex_coord = uint2(in.uv * float2(tex_size));
return textures.texture.read(tex_coord, sample_id);
}

View File

@@ -0,0 +1,72 @@
#include <metal_stdlib>
using namespace metal;
struct StrideArguments {
int4 data;
};
struct InData {
uint8_t data[1];
};
struct OutData {
uint8_t data[1];
};
struct ConstantBuffers {
constant StrideArguments* stride_arguments;
};
struct StorageBuffers {
device InData* in_data;
device OutData* out_data;
};
kernel void kernelMain(constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]],
device StorageBuffers &storage_buffers [[buffer(STORAGE_BUFFERS_INDEX)]],
uint3 thread_position_in_grid [[thread_position_in_grid]],
uint3 threads_per_threadgroup [[threads_per_threadgroup]],
uint3 threadgroups_per_grid [[threadgroups_per_grid]])
{
// Determine what slice of the stride copies this invocation will perform.
int sourceStride = constant_buffers.stride_arguments->data.x;
int targetStride = constant_buffers.stride_arguments->data.y;
int bufferSize = constant_buffers.stride_arguments->data.z;
int sourceOffset = constant_buffers.stride_arguments->data.w;
int strideRemainder = targetStride - sourceStride;
int invocations = int(threads_per_threadgroup.x * threadgroups_per_grid.x);
int copiesRequired = bufferSize / sourceStride;
// Find the copies that this invocation should perform.
// - Copies that all invocations perform.
int allInvocationCopies = copiesRequired / invocations;
// - Extra remainder copy that this invocation performs.
int index = int(thread_position_in_grid.x);
int extra = (index < (copiesRequired % invocations)) ? 1 : 0;
int copyCount = allInvocationCopies + extra;
// Finally, get the starting offset. Make sure to count extra copies.
int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index);
int srcOffset = sourceOffset + startCopy * sourceStride;
int dstOffset = startCopy * targetStride;
// Perform the copies for this region
for (int i = 0; i < copyCount; i++) {
for (int j = 0; j < sourceStride; j++) {
storage_buffers.out_data->data[dstOffset++] = storage_buffers.in_data->data[srcOffset++];
}
for (int j = 0; j < strideRemainder; j++) {
storage_buffers.out_data->data[dstOffset++] = uint8_t(0);
}
}
}

View File

@@ -0,0 +1,38 @@
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
};
struct ClearColor {
FORMAT4 data;
};
struct ConstantBuffers {
constant ClearColor* clear_color;
};
vertex VertexOut vertexMain(ushort vid [[vertex_id]]) {
int low = vid & 1;
int high = vid >> 1;
VertexOut out;
out.position.x = (float(low) - 0.5f) * 2.0f;
out.position.y = (float(high) - 0.5f) * 2.0f;
out.position.z = 0.0f;
out.position.w = 1.0f;
return out;
}
struct FragmentOut {
FORMAT4 color [[color(COLOR_ATTACHMENT_INDEX)]];
};
fragment FragmentOut fragmentMain(VertexOut in [[stage_in]],
constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]]) {
return {constant_buffers.clear_color->data};
}

View File

@@ -0,0 +1,66 @@
#include <metal_stdlib>
using namespace metal;
struct StrideArguments {
int pixelCount;
int dstStartOffset;
};
struct InData {
uint data[1];
};
struct OutData {
uint data[1];
};
struct ConstantBuffers {
constant StrideArguments* stride_arguments;
};
struct StorageBuffers {
device InData* in_data;
device OutData* out_data;
};
kernel void kernelMain(constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]],
device StorageBuffers &storage_buffers [[buffer(STORAGE_BUFFERS_INDEX)]],
uint3 thread_position_in_grid [[thread_position_in_grid]],
uint3 threads_per_threadgroup [[threads_per_threadgroup]],
uint3 threadgroups_per_grid [[threadgroups_per_grid]])
{
// Determine what slice of the stride copies this invocation will perform.
int invocations = int(threads_per_threadgroup.x * threadgroups_per_grid.x);
int copiesRequired = constant_buffers.stride_arguments->pixelCount;
// Find the copies that this invocation should perform.
// - Copies that all invocations perform.
int allInvocationCopies = copiesRequired / invocations;
// - Extra remainder copy that this invocation performs.
int index = int(thread_position_in_grid.x);
int extra = (index < (copiesRequired % invocations)) ? 1 : 0;
int copyCount = allInvocationCopies + extra;
// Finally, get the starting offset. Make sure to count extra copies.
int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index);
int srcOffset = startCopy * 2;
int dstOffset = constant_buffers.stride_arguments->dstStartOffset + startCopy;
// Perform the conversion for this region.
for (int i = 0; i < copyCount; i++)
{
float depth = as_type<float>(storage_buffers.in_data->data[srcOffset++]);
uint stencil = storage_buffers.in_data->data[srcOffset++];
uint rescaledDepth = uint(clamp(depth, 0.0, 1.0) * 16777215.0);
storage_buffers.out_data->data[dstOffset++] = (rescaledDepth << 8) | (stencil & 0xff);
}
}

View File

@@ -0,0 +1,59 @@
#include <metal_stdlib>
using namespace metal;
struct IndexBufferPattern {
int pattern[8];
int primitiveVertices;
int primitiveVerticesOut;
int indexSize;
int indexSizeOut;
int baseIndex;
int indexStride;
int srcOffset;
int totalPrimitives;
};
struct InData {
uint8_t data[1];
};
struct OutData {
uint8_t data[1];
};
struct StorageBuffers {
device InData* in_data;
device OutData* out_data;
constant IndexBufferPattern* index_buffer_pattern;
};
kernel void kernelMain(device StorageBuffers &storage_buffers [[buffer(STORAGE_BUFFERS_INDEX)]],
uint3 thread_position_in_grid [[thread_position_in_grid]])
{
int primitiveIndex = int(thread_position_in_grid.x);
if (primitiveIndex >= storage_buffers.index_buffer_pattern->totalPrimitives)
{
return;
}
int inOffset = primitiveIndex * storage_buffers.index_buffer_pattern->indexStride;
int outOffset = primitiveIndex * storage_buffers.index_buffer_pattern->primitiveVerticesOut;
for (int i = 0; i < storage_buffers.index_buffer_pattern->primitiveVerticesOut; i++)
{
int j;
int io = max(0, inOffset + storage_buffers.index_buffer_pattern->baseIndex + storage_buffers.index_buffer_pattern->pattern[i]) * storage_buffers.index_buffer_pattern->indexSize;
int oo = (outOffset + i) * storage_buffers.index_buffer_pattern->indexSizeOut;
for (j = 0; j < storage_buffers.index_buffer_pattern->indexSize; j++)
{
storage_buffers.out_data->data[oo + j] = storage_buffers.in_data->data[storage_buffers.index_buffer_pattern->srcOffset + io + j];
}
for(; j < storage_buffers.index_buffer_pattern->indexSizeOut; j++)
{
storage_buffers.out_data->data[oo + j] = uint8_t(0);
}
}
}

View File

@@ -0,0 +1,27 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct Textures
{
texture2d<float, access::sample> texture;
sampler sampler;
};
struct FragmentOut {
float depth [[depth(any)]];
};
fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]]) {
FragmentOut out;
out.depth = textures.texture.sample(textures.sampler, in.uv).r;
return out;
}

View File

@@ -0,0 +1,29 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct Textures
{
texture2d_ms<float, access::read> texture;
};
struct FragmentOut {
float depth [[depth(any)]];
};
fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]],
uint sample_id [[sample_id]]) {
FragmentOut out;
uint2 tex_size = uint2(textures.texture.get_width(), textures.texture.get_height());
uint2 tex_coord = uint2(in.uv * float2(tex_size));
out.depth = textures.texture.read(tex_coord, sample_id).r;
return out;
}

Some files were not shown because too many files have changed in this diff Show More