Compare commits
6 Commits
master
...
Jacobwasbe
Author | SHA1 | Date | |
---|---|---|---|
|
0282b216fd | ||
|
f2ade5f1f8 | ||
|
965fb9dd5f | ||
|
e2a5e69f4c | ||
|
7f27b791f8 | ||
|
3ca8618f5f |
4
.github/workflows/canary.yml
vendored
4
.github/workflows/canary.yml
vendored
@ -29,7 +29,7 @@ env:
|
||||
jobs:
|
||||
tag:
|
||||
name: Create tag
|
||||
runs-on: ubuntu-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
|
||||
|
||||
|
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@ -12,14 +12,13 @@ env:
|
||||
RYUJINX_BASE_VERSION: "1.2"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Stable-Releases"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
|
||||
RELEASE: 1
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
name: Create tag
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
@ -34,7 +33,7 @@ jobs:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}",
|
||||
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}",
|
||||
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}",
|
||||
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
@ -53,7 +52,7 @@ jobs:
|
||||
| Linux ARM 64-bit | [Stable Linux ARM Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
||||
| macOS | [Stable macOS Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
||||
|
||||
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
|
||||
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
|
||||
omitBodyDuringUpdate: true
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
@ -93,7 +92,7 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
@ -174,7 +173,7 @@ jobs:
|
||||
| Linux ARM 64-bit | [Stable Linux ARM Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
||||
| macOS | [Stable macOS Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
||||
|
||||
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
|
||||
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
|
||||
omitBodyDuringUpdate: true
|
||||
allowUpdates: true
|
||||
replacesArtifacts: true
|
||||
@ -184,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
|
||||
|
||||
@ -223,7 +222,7 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
|
@ -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" />
|
||||
|
10
README.md
10
README.md
@ -1,14 +1,14 @@
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center" width="25%">
|
||||
<img src="https://raw.githubusercontent.com/Ryubing/Assets/refs/heads/main/RyujinxApp_1024.png" alt="Ryujinx" >
|
||||
<img src="https://raw.githubusercontent.com/GreemDev/ryuassets/refs/heads/main/RyujinxApp_1024.png" alt="Ryujinx" >
|
||||
</td>
|
||||
<td align="center" width="75%">
|
||||
|
||||
# Ryujinx
|
||||
|
||||
[](https://github.com/Ryubing/Ryujinx/actions/workflows/release.yml)
|
||||
[](https://github.com/Ryubing/Ryujinx/releases/latest)
|
||||
[](https://github.com/Ryubing/Ryujinx/releases/latest)
|
||||
<br>
|
||||
[](https://github.com/Ryubing/Ryujinx/actions/workflows/canary.yml)
|
||||
[](https://github.com/Ryubing/Canary-Releases/releases/latest)
|
||||
@ -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
|
||||
@ -97,7 +97,7 @@ If you are planning to contribute or just want to learn more about this project
|
||||
|
||||
- **Input**
|
||||
|
||||
We currently have support for keyboard, mouse, touch input, Joy-Con input support, and nearly all controllers.
|
||||
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers.
|
||||
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
|
||||
In all scenarios, you can set up everything inside the input configuration menu.
|
||||
|
||||
|
24
Ryujinx.sln
24
Ryujinx.sln
@ -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
|
||||
|
@ -631,7 +631,6 @@
|
||||
010030D012FF6000,"Bus Driver Simulator",,playable,2022-10-17 13:55:27
|
||||
0100A9101418C000,"BUSTAFELLOWS",nvdec,playable,2020-10-17 20:04:41
|
||||
0100177005C8A000,"BUTCHER",,playable,2021-01-11 18:50:17
|
||||
01008c2019598000,"Bluey: The Videogame",,playable,2025-02-11 04:38:00
|
||||
01000B900D8B0000,"Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda",slow;nvdec,playable,2024-04-01 22:43:40
|
||||
010065700EE06000,"Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda Demo",demo;gpu;nvdec,ingame,2021-02-14 21:48:15
|
||||
01005C00117A8000,"Café Enchanté",,playable,2020-11-13 14:54:25
|
||||
@ -970,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
|
||||
@ -1160,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
|
||||
@ -1251,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
|
||||
@ -1383,9 +1381,6 @@
|
||||
0100763015C2E000,"Gunvolt Chronicles: Luminous Avenger iX 2",crash;Needs Update,nothing,2022-04-29 15:34:34
|
||||
01002C8018554000,"Gurimugurimoa OnceMore Demo",,playable,2022-07-29 22:07:31
|
||||
0100AC601DCA8000,"GYLT",crash,ingame,2024-03-18 20:16:51
|
||||
0100c3c012718000,"Grand Theft Auto: III – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
|
||||
0100182014022000,"Grand Theft Auto: Vice City – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
|
||||
010065a014024000,"Grand Theft Auto: San Andreas – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
|
||||
0100822012D76000,"HAAK",gpu,ingame,2023-02-19 14:31:05
|
||||
01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42
|
||||
0100535012974000,"Hades",vulkan,playable,2022-10-05 10:45:21
|
||||
@ -1440,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
|
||||
@ -1804,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
|
||||
@ -2733,7 +2727,7 @@
|
||||
0100C2500FC20000,"Splatoon™ 3",ldn-works;opengl-backend-bug;LAN;amd-vendor-bug,playable,2024-08-04 23:49:11
|
||||
0100BA0018500000,"Splatoon™ 3: Splatfest World Premiere",gpu;online-broken;demo,ingame,2022-09-19 03:17:12
|
||||
010062800D39C000,"SpongeBob SquarePants: Battle for Bikini Bottom - Rehydrated",online-broken;UE4;ldn-broken;vulkan-backend-bug,playable,2023-08-01 19:29:34
|
||||
01009FB0172F4000,"SpongeBob SquarePants: The Cosmic Shake",gpu;UE4,ingame,2024-03-04 16:35:00
|
||||
01009FB0172F4000,"SpongeBob SquarePants: The Cosmic Shake",gpu;UE4,ingame,2023-08-01 19:29:53
|
||||
010097C01336A000,"Spooky Chase",,playable,2022-11-04 12:17:44
|
||||
0100C6100D75E000,"Spooky Ghosts Dot Com",,playable,2021-06-15 15:16:11
|
||||
0100DE9005170000,"Sports Party",nvdec,playable,2021-03-05 13:40:42
|
||||
@ -2844,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
|
||||
@ -2995,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
|
||||
|
|
@ -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;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
int newRegionNumber = _activeRegionIndex;
|
||||
|
||||
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
|
||||
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
|
||||
if (allocOffsetNew < 0)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||
|
@ -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());
|
||||
|
@ -9,8 +9,7 @@ namespace Ryujinx.Common.Configuration
|
||||
public enum DirtyHack : byte
|
||||
{
|
||||
Xc2MenuSoftlockFix = 1,
|
||||
// ShaderTranslationDelay = 2
|
||||
NifmServiceDisableIsAnyInternetRequestAccepted = 3
|
||||
ShaderTranslationDelay = 2
|
||||
}
|
||||
|
||||
public readonly struct EnabledDirtyHack(DirtyHack hack, int value)
|
||||
|
@ -6,7 +6,9 @@ namespace Ryujinx.Common.Configuration
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||
public enum GraphicsBackend
|
||||
{
|
||||
Auto,
|
||||
Vulkan,
|
||||
OpenGl,
|
||||
Metal
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -37,9 +37,9 @@ namespace Ryujinx.Common
|
||||
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
||||
IsCanaryBuild
|
||||
? $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelSourceRepo}/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||
: GetChangelogForVersion(newVersion);
|
||||
: $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelSourceRepo}/releases/tag/{newVersion}";
|
||||
|
||||
public static string GetChangelogForVersion(Version version) =>
|
||||
$"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelRepo}/releases/{version}";
|
||||
$"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelRepo}/releases/tag/{version}";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
using Gommon;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Helper;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
@ -7,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";
|
||||
|
||||
@ -178,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
|
||||
|
@ -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);
|
||||
|
||||
|
18
src/Ryujinx.Graphics.GAL/ComputeSize.cs
Normal file
18
src/Ryujinx.Graphics.GAL/ComputeSize.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -1,6 +1,4 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL.Multithreading;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
@ -12,20 +10,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
bool PreferThreading { get; }
|
||||
|
||||
public IRenderer TryMakeThreaded(BackendThreading backendThreading = BackendThreading.Auto)
|
||||
{
|
||||
if (backendThreading is BackendThreading.On ||
|
||||
(backendThreading is BackendThreading.Auto && PreferThreading))
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({backendThreading}): True");
|
||||
return new ThreadedRenderer(this);
|
||||
}
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({backendThreading}): False");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
IPipeline Pipeline { get; }
|
||||
|
||||
IWindow Window { get; }
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
|
||||
shaderGpuVa += (uint)qmd.ProgramOffset;
|
||||
|
||||
ShaderCache shaderCache = memoryManager.GetBackingMemory(shaderGpuVa).ShaderCache;
|
||||
|
||||
int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize;
|
||||
|
||||
int sharedMemorySize = Math.Min(qmd.SharedMemorySize, _context.Capabilities.MaximumComputeSharedMemorySize);
|
||||
@ -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);
|
||||
}
|
||||
|
@ -216,6 +216,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
@ -543,8 +545,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
RtDepthStencilState dsState = _state.State.RtDepthStencilState;
|
||||
Size3D dsSize = _state.State.RtDepthStencilSize;
|
||||
TextureCache dsTextureCache = memoryManager.GetBackingMemory(dsState.Address.Pack()).TextureCache;
|
||||
|
||||
depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
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,
|
||||
|
@ -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>
|
||||
|
@ -234,6 +234,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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
struct IndexBuffer
|
||||
{
|
||||
public BufferCache BufferCache;
|
||||
public MultiRange Range;
|
||||
public IndexType Type;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
struct VertexBuffer
|
||||
{
|
||||
public BufferCache BufferCache;
|
||||
public MultiRange Range;
|
||||
public int Stride;
|
||||
public int Divisor;
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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/>
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,14 +733,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
byte[] codeB,
|
||||
bool asCompute)
|
||||
{
|
||||
ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||
(PhysicalMemory physical, ulong cb1DataAddress) = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||
|
||||
var memoryManager = channel.MemoryManager;
|
||||
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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
146
src/Ryujinx.Graphics.Metal/Auto.cs
Normal file
146
src/Ryujinx.Graphics.Metal/Auto.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
107
src/Ryujinx.Graphics.Metal/BackgroundResources.cs
Normal file
107
src/Ryujinx.Graphics.Metal/BackgroundResources.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
157
src/Ryujinx.Graphics.Metal/BitMap.cs
Normal file
157
src/Ryujinx.Graphics.Metal/BitMap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
385
src/Ryujinx.Graphics.Metal/BufferHolder.cs
Normal file
385
src/Ryujinx.Graphics.Metal/BufferHolder.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
237
src/Ryujinx.Graphics.Metal/BufferManager.cs
Normal file
237
src/Ryujinx.Graphics.Metal/BufferManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
85
src/Ryujinx.Graphics.Metal/BufferUsageBitmap.cs
Normal file
85
src/Ryujinx.Graphics.Metal/BufferUsageBitmap.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
294
src/Ryujinx.Graphics.Metal/CacheByRange.cs
Normal file
294
src/Ryujinx.Graphics.Metal/CacheByRange.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
170
src/Ryujinx.Graphics.Metal/CommandBufferEncoder.cs
Normal file
170
src/Ryujinx.Graphics.Metal/CommandBufferEncoder.cs
Normal 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;
|
||||
}
|
||||
}
|
289
src/Ryujinx.Graphics.Metal/CommandBufferPool.cs
Normal file
289
src/Ryujinx.Graphics.Metal/CommandBufferPool.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
src/Ryujinx.Graphics.Metal/CommandBufferScoped.cs
Normal file
43
src/Ryujinx.Graphics.Metal/CommandBufferScoped.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
41
src/Ryujinx.Graphics.Metal/Constants.cs
Normal file
41
src/Ryujinx.Graphics.Metal/Constants.cs
Normal 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;
|
||||
}
|
||||
}
|
22
src/Ryujinx.Graphics.Metal/CounterEvent.cs
Normal file
22
src/Ryujinx.Graphics.Metal/CounterEvent.cs
Normal 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() { }
|
||||
}
|
||||
}
|
68
src/Ryujinx.Graphics.Metal/DepthStencilCache.cs
Normal file
68
src/Ryujinx.Graphics.Metal/DepthStencilCache.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
src/Ryujinx.Graphics.Metal/DisposableBuffer.cs
Normal file
26
src/Ryujinx.Graphics.Metal/DisposableBuffer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
src/Ryujinx.Graphics.Metal/DisposableSampler.cs
Normal file
22
src/Ryujinx.Graphics.Metal/DisposableSampler.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
10
src/Ryujinx.Graphics.Metal/Effects/IPostProcessingEffect.cs
Normal file
10
src/Ryujinx.Graphics.Metal/Effects/IPostProcessingEffect.cs
Normal 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);
|
||||
}
|
||||
}
|
18
src/Ryujinx.Graphics.Metal/Effects/IScalingFilter.cs
Normal file
18
src/Ryujinx.Graphics.Metal/Effects/IScalingFilter.cs
Normal 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);
|
||||
}
|
||||
}
|
63
src/Ryujinx.Graphics.Metal/EncoderResources.cs
Normal file
63
src/Ryujinx.Graphics.Metal/EncoderResources.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
206
src/Ryujinx.Graphics.Metal/EncoderState.cs
Normal file
206
src/Ryujinx.Graphics.Metal/EncoderState.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
1790
src/Ryujinx.Graphics.Metal/EncoderStateManager.cs
Normal file
1790
src/Ryujinx.Graphics.Metal/EncoderStateManager.cs
Normal file
File diff suppressed because it is too large
Load Diff
292
src/Ryujinx.Graphics.Metal/EnumConversion.cs
Normal file
292
src/Ryujinx.Graphics.Metal/EnumConversion.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
77
src/Ryujinx.Graphics.Metal/FenceHolder.cs
Normal file
77
src/Ryujinx.Graphics.Metal/FenceHolder.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
src/Ryujinx.Graphics.Metal/FormatConverter.cs
Normal file
49
src/Ryujinx.Graphics.Metal/FormatConverter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
196
src/Ryujinx.Graphics.Metal/FormatTable.cs
Normal file
196
src/Ryujinx.Graphics.Metal/FormatTable.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
82
src/Ryujinx.Graphics.Metal/HardwareInfo.cs
Normal file
82
src/Ryujinx.Graphics.Metal/HardwareInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
143
src/Ryujinx.Graphics.Metal/HashTableSlim.cs
Normal file
143
src/Ryujinx.Graphics.Metal/HashTableSlim.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
868
src/Ryujinx.Graphics.Metal/HelperShader.cs
Normal file
868
src/Ryujinx.Graphics.Metal/HelperShader.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
121
src/Ryujinx.Graphics.Metal/IdList.cs
Normal file
121
src/Ryujinx.Graphics.Metal/IdList.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
src/Ryujinx.Graphics.Metal/ImageArray.cs
Normal file
74
src/Ryujinx.Graphics.Metal/ImageArray.cs
Normal 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() { }
|
||||
}
|
||||
}
|
118
src/Ryujinx.Graphics.Metal/IndexBufferPattern.cs
Normal file
118
src/Ryujinx.Graphics.Metal/IndexBufferPattern.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
103
src/Ryujinx.Graphics.Metal/IndexBufferState.cs
Normal file
103
src/Ryujinx.Graphics.Metal/IndexBufferState.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
312
src/Ryujinx.Graphics.Metal/MetalRenderer.cs
Normal file
312
src/Ryujinx.Graphics.Metal/MetalRenderer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
262
src/Ryujinx.Graphics.Metal/MultiFenceHolder.cs
Normal file
262
src/Ryujinx.Graphics.Metal/MultiFenceHolder.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
100
src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs
Normal file
100
src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
877
src/Ryujinx.Graphics.Metal/Pipeline.cs
Normal file
877
src/Ryujinx.Graphics.Metal/Pipeline.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
286
src/Ryujinx.Graphics.Metal/Program.cs
Normal file
286
src/Ryujinx.Graphics.Metal/Program.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
22
src/Ryujinx.Graphics.Metal/ResourceBindingSegment.cs
Normal file
22
src/Ryujinx.Graphics.Metal/ResourceBindingSegment.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
59
src/Ryujinx.Graphics.Metal/ResourceLayoutBuilder.cs
Normal file
59
src/Ryujinx.Graphics.Metal/ResourceLayoutBuilder.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
27
src/Ryujinx.Graphics.Metal/Ryujinx.Graphics.Metal.csproj
Normal file
27
src/Ryujinx.Graphics.Metal/Ryujinx.Graphics.Metal.csproj
Normal 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>
|
90
src/Ryujinx.Graphics.Metal/SamplerHolder.cs
Normal file
90
src/Ryujinx.Graphics.Metal/SamplerHolder.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
src/Ryujinx.Graphics.Metal/Shaders/Blit.metal
Normal file
43
src/Ryujinx.Graphics.Metal/Shaders/Blit.metal
Normal 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);
|
||||
}
|
45
src/Ryujinx.Graphics.Metal/Shaders/BlitMs.metal
Normal file
45
src/Ryujinx.Graphics.Metal/Shaders/BlitMs.metal
Normal 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);
|
||||
}
|
72
src/Ryujinx.Graphics.Metal/Shaders/ChangeBufferStride.metal
Normal file
72
src/Ryujinx.Graphics.Metal/Shaders/ChangeBufferStride.metal
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
38
src/Ryujinx.Graphics.Metal/Shaders/ColorClear.metal
Normal file
38
src/Ryujinx.Graphics.Metal/Shaders/ColorClear.metal
Normal 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};
|
||||
}
|
66
src/Ryujinx.Graphics.Metal/Shaders/ConvertD32S8ToD24S8.metal
Normal file
66
src/Ryujinx.Graphics.Metal/Shaders/ConvertD32S8ToD24S8.metal
Normal 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);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user