Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
fe9e19d8cc | |||
fb55f57da7 | |||
44862dce3e | |||
e601419bd4 | |||
d6bc0de785 | |||
9f26fd3600 | |||
88df636c87 | |||
7ccff037e8 | |||
a745913329 | |||
e6700b314f | |||
e2cfe6fe44 | |||
210f475484 | |||
ddb6493896 | |||
f631933e60 | |||
5ff6ea6d82 | |||
c2d9c6955d | |||
fbe0c211c1 | |||
db0f3c0b74 | |||
34447d7359 | |||
5f771f5661 | |||
93cd327873 | |||
12cbacffca | |||
437c78e198 | |||
f09bba82b9 | |||
93d78f9ac4 | |||
cd7b52f995 | |||
7f96dbc024 | |||
3e5c211394 | |||
153b8bfc7c | |||
c6a699414a | |||
2563f88de0 | |||
b0b7843d5c | |||
6ed613a6e6 | |||
64079c034c | |||
17354d59d1 | |||
0c445184c1 |
2
.github/reviewers.yml
vendored
2
.github/reviewers.yml
vendored
@ -29,4 +29,4 @@ infra:
|
||||
- TSRBerry
|
||||
|
||||
default:
|
||||
- @developers
|
||||
- '@developers'
|
||||
|
87
.github/update_reviewers.py
vendored
87
.github/update_reviewers.py
vendored
@ -1,87 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import List, Set
|
||||
from github import Auth, Github
|
||||
from github.Repository import Repository
|
||||
from github.GithubException import GithubException
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
|
||||
def add_reviewers(
|
||||
reviewers: Set[str], team_reviewers: Set[str], new_entries: List[str]
|
||||
):
|
||||
for reviewer in new_entries:
|
||||
if reviewer.startswith("@"):
|
||||
team_reviewers.add(reviewer[1:])
|
||||
else:
|
||||
reviewers.add(reviewer)
|
||||
|
||||
|
||||
def update_reviewers(config, repo: Repository, pr_id: int) -> int:
|
||||
pull_request = repo.get_pull(pr_id)
|
||||
|
||||
if not pull_request:
|
||||
sys.stderr.writable(f"Unknown PR #{pr_id}\n")
|
||||
return 1
|
||||
|
||||
if pull_request.draft:
|
||||
print("Not assigning reviewers for draft PRs")
|
||||
return 0
|
||||
|
||||
pull_request_author = pull_request.user.login
|
||||
reviewers = set()
|
||||
team_reviewers = set()
|
||||
|
||||
for label in pull_request.labels:
|
||||
if label.name in config:
|
||||
add_reviewers(reviewers, team_reviewers, config[label.name])
|
||||
|
||||
if "default" in config:
|
||||
add_reviewers(reviewers, team_reviewers, config["default"])
|
||||
|
||||
if pull_request_author in reviewers:
|
||||
reviewers.remove(pull_request_author)
|
||||
|
||||
try:
|
||||
reviewers = list(reviewers)
|
||||
team_reviewers = list(team_reviewers)
|
||||
print(
|
||||
f"Attempting to assign reviewers ({reviewers}) and team_reviewers ({team_reviewers})"
|
||||
)
|
||||
pull_request.create_review_request(reviewers, team_reviewers)
|
||||
return 0
|
||||
except GithubException as e:
|
||||
sys.stderr.write(f"Cannot assign review request for PR #{pr_id}: {e}\n")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 7:
|
||||
sys.stderr.write("usage: <app_id> <private_key_env_name> <installation_id> <repo_path> <pr_id> <config_path>\n")
|
||||
sys.exit(1)
|
||||
|
||||
app_id = sys.argv[1]
|
||||
private_key = os.environ[sys.argv[2]]
|
||||
installation_id = sys.argv[3]
|
||||
repo_path = sys.argv[4]
|
||||
pr_id = int(sys.argv[5])
|
||||
config_path = Path(sys.argv[6])
|
||||
|
||||
auth = Auth.AppAuth(app_id, private_key).get_installation_auth(installation_id)
|
||||
g = Github(auth=auth)
|
||||
repo = g.get_repo(repo_path)
|
||||
|
||||
if not repo:
|
||||
sys.stderr.write("Repository not found!\n")
|
||||
sys.exit(1)
|
||||
|
||||
if not config_path.exists():
|
||||
sys.stderr.write(f'Config "{config_path}" not found!\n')
|
||||
sys.exit(1)
|
||||
|
||||
with open(config_path, "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
sys.exit(update_reviewers(config, repo, pr_id))
|
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
@ -108,7 +108,7 @@ jobs:
|
||||
configuration: [ Debug, Release ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
@ -135,9 +135,13 @@ jobs:
|
||||
id: git_short_hash
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish macOS
|
||||
- name: Publish macOS Ryujinx.Ava
|
||||
run: |
|
||||
./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
|
||||
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
|
||||
- name: Upload Ryujinx.Ava artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
@ -145,3 +149,10 @@ jobs:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish_ava/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish_headless/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
2
.github/workflows/checks.yml
vendored
2
.github/workflows/checks.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
4
.github/workflows/flatpak.yml
vendored
4
.github/workflows/flatpak.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
RYUJINX_VERSION: "${{ inputs.ryujinx_version }}"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: Ryujinx
|
||||
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
run: |
|
||||
echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: flathub/org.ryujinx.Ryujinx
|
||||
token: ${{ secrets.RYUJINX_BOT_PAT }}
|
||||
|
28
.github/workflows/pr_triage.yml
vendored
28
.github/workflows/pr_triage.yml
vendored
@ -12,14 +12,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Grab sources to get update_reviewers.py and reviewers.yml
|
||||
# Grab sources to get latest labeler.yml
|
||||
- name: Fetch sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Ensure we pin the source origin as pull_request_target run under forks.
|
||||
fetch-depth: 0
|
||||
repository: Ryujinx/Ryujinx
|
||||
ref: master
|
||||
# Ensure we pin the source origin as pull_request_target run under forks.
|
||||
fetch-depth: 0
|
||||
repository: Ryujinx/Ryujinx
|
||||
ref: master
|
||||
|
||||
- name: Checkout Ryujinx-Mako
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Ryujinx/Ryujinx-Mako
|
||||
ref: master
|
||||
path: '.ryujinx-mako'
|
||||
|
||||
- name: Setup Ryujinx-Mako
|
||||
uses: ./.ryujinx-mako/.github/actions/setup-mako
|
||||
|
||||
- name: Update labels based on changes
|
||||
uses: actions/labeler@v4
|
||||
@ -27,11 +37,11 @@ jobs:
|
||||
sync-labels: true
|
||||
dot: true
|
||||
|
||||
- run: pip3 install PyGithub
|
||||
|
||||
- name: Assign reviewers
|
||||
run: |
|
||||
python3 .github/update_reviewers.py ${{ secrets.MAKO_APP_ID }} "MAKO_PRIVATE_KEY" ${{ secrets.MAKO_INSTALLATION_ID }} ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml
|
||||
poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml
|
||||
shell: bash
|
||||
env:
|
||||
MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }}
|
||||
MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }}
|
||||
MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }}
|
||||
|
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@ -62,7 +62,7 @@ jobs:
|
||||
DOTNET_RUNTIME_IDENTIFIER: win10-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
@ -150,7 +150,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
@ -188,15 +188,19 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Publish macOS
|
||||
- name: Publish macOS Ryujinx.Ava
|
||||
run: |
|
||||
./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
||||
|
||||
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
||||
|
||||
- name: Pushing new release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ steps.version_info.outputs.build_version }}
|
||||
artifacts: "publish_ava/*.tar.gz"
|
||||
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
|
||||
tag: ${{ steps.version_info.outputs.build_version }}
|
||||
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
|
||||
omitBodyDuringUpdate: true
|
||||
|
147
CONTRIBUTING.md
Normal file
147
CONTRIBUTING.md
Normal file
@ -0,0 +1,147 @@
|
||||
# Contribution to Ryujinx
|
||||
|
||||
You can contribute to Ryujinx with PRs, testing of PRs and issues. Contributing code and other implementations is greatly appreciated alongside simply filing issues for problems you encounter.
|
||||
Please read the entire document before continuing as it can potentially save everyone involved a significant amount of time.
|
||||
|
||||
# Quick Links
|
||||
|
||||
* [Code Style Documentation](docs/coding-guidelines/coding-style.md)
|
||||
* [Pull Request Guidelines](docs/workflow/pr-guide.md)
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
We always welcome bug reports, feature proposals and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible.
|
||||
|
||||
### Identify Where to Report
|
||||
|
||||
The Ryujinx codebase is distributed across multiple repositories in the [Ryujinx organization](https://github.com/Ryujinx). Depending on the feedback you might want to file the issue on a different repo. Here are a few common repos:
|
||||
|
||||
* [Ryujinx/Ryujinx](https://github.com/Ryujinx/Ryujinx) Ryujinx core project files.
|
||||
* [Ryujinx/Ryujinx-Games-List](https://github.com/Ryujinx/Ryujinx-Games-List) Ryujinx game compatibility list.
|
||||
* [Ryujinx/Ryujinx-Website](https://github.com/Ryujinx/Ryujinx-Website) Ryujinx website source code.
|
||||
* [Ryujinx/Ryujinx-Ldn-Website](https://github.com/Ryujinx/Ryujinx-Ldn-Website) Ryujinx LDN website source code.
|
||||
|
||||
### Finding Existing Issues
|
||||
|
||||
Before filing a new issue, please search our [open issues](https://github.com/Ryujinx/Ryujinx/issues) to check if it already exists.
|
||||
|
||||
If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (👍 reaction) the original post, as this helps us prioritize popular issues in our backlog.
|
||||
|
||||
### Writing a Good Feature Request
|
||||
|
||||
Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please use the [Feature Request issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=%5BFeature+Request%5D).
|
||||
|
||||
### Writing a Good Bug Report
|
||||
|
||||
Good bug reports make it easier for maintainers to verify and root cause the underlying problem. The better a bug report, the faster the problem will be resolved.
|
||||
Ideally, a bug report should contain the following information:
|
||||
|
||||
* A high-level description of the problem.
|
||||
* A _minimal reproduction_, i.e. the smallest time commitment/configuration required to reproduce the wrong behavior. This can be in the form of a small homebrew application, or by providing a save file and reproduction steps for a specific game.
|
||||
* A description of the _expected behavior_, contrasted with the _actual behavior_ observed.
|
||||
* Information on the environment: OS/distro, CPU, GPU (including driver), RAM etc.
|
||||
* A Ryujinx log file of the run instance where the issue occurred. Log files can be found in `[Executable Folder]/Logs` and are named chronologically.
|
||||
* Additional information, e.g. is it a regression from previous versions? Are there any known workarounds?
|
||||
|
||||
When ready to submit a bug report, please use the [Bug Report issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=%5BBug%5D).
|
||||
|
||||
## Contributing Changes
|
||||
|
||||
Project maintainers will merge changes that both improve the project and meet our standards for code quality.
|
||||
|
||||
The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt) docs define additional guidance.
|
||||
|
||||
### DOs and DON'Ts
|
||||
|
||||
Please do:
|
||||
|
||||
* **DO** follow our [coding style](docs/coding-guidelines/coding-style.md) (C# code-specific).
|
||||
* **DO** give priority to the current style of the project or file you're changing even if it diverges from the general guidelines.
|
||||
* **DO** keep the discussions focused. When a new or related topic comes up
|
||||
it's often better to create new issue than to side track the discussion.
|
||||
* **DO** clearly state on an issue that you are going to take on implementing it.
|
||||
* **DO** blog and tweet (or whatever) about your contributions, frequently!
|
||||
|
||||
Please do not:
|
||||
|
||||
* **DON'T** make PRs for style changes.
|
||||
* **DON'T** surprise us with big pull requests. Instead, file an issue and talk with us on Discord to start
|
||||
a discussion so we can agree on a direction before you invest a large amount
|
||||
of time.
|
||||
* **DON'T** commit code that you didn't write. If you find code that you think is a good fit to add to Ryujinx, file an issue or talk to us on Discord to start a discussion before proceeding.
|
||||
* **DON'T** submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it.
|
||||
|
||||
### Suggested Workflow
|
||||
|
||||
We use and recommend the following workflow:
|
||||
|
||||
1. Create or find an issue for your work.
|
||||
- You can skip this step for trivial changes.
|
||||
- Get agreement from the team and the community that your proposed change is a good one if it is of significant size or changes core functionality.
|
||||
- Clearly state that you are going to take on implementing it, if that's the case. You can request that the issue be assigned to you. Note: The issue filer and the implementer don't have to be the same person.
|
||||
2. Create a personal fork of the repository on GitHub (if you don't already have one).
|
||||
3. In your fork, create a branch off of main (`git checkout -b mybranch`).
|
||||
- Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork.
|
||||
4. Make and commit your changes to your branch.
|
||||
- [Build Instructions](https://github.com/Ryujinx/Ryujinx#building) explains how to build and test.
|
||||
- Commit messages should be clear statements of action and intent.
|
||||
6. Build the repository with your changes.
|
||||
- Make sure that the builds are clean.
|
||||
- Make sure that `dotnet format` has been run and any corrections tested and committed.
|
||||
7. Create a pull request (PR) against the Ryujinx/Ryujinx repository's **main** branch.
|
||||
- State in the description what issue or improvement your change is addressing.
|
||||
- Check if all the Continuous Integration checks are passing. Refer to [Actions](https://github.com/Ryujinx/Ryujinx/actions) to check for outstanding errors.
|
||||
8. Wait for feedback or approval of your changes from the [core development team](https://github.com/orgs/Ryujinx/teams/developers)
|
||||
- Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md).
|
||||
9. When the team members have signed off, and all checks are green, your PR will be merged.
|
||||
- The next official build will automatically include your change.
|
||||
- You can delete the branch you used for making the change.
|
||||
|
||||
### Good First Issues
|
||||
|
||||
The team marks the most straightforward issues as [good first issues](https://github.com/Ryujinx/Ryujinx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). This set of issues is the place to start if you are interested in contributing but new to the codebase.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Please format commit messages as follows (based on [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)):
|
||||
|
||||
```
|
||||
Summarize change in 50 characters or less
|
||||
|
||||
Provide more detail after the first line. Leave one blank line below the
|
||||
summary and wrap all lines at 72 characters or less.
|
||||
|
||||
If the change fixes an issue, leave another blank line after the final
|
||||
paragraph and indicate which issue is fixed in the specific format
|
||||
below.
|
||||
|
||||
Fix #42
|
||||
```
|
||||
|
||||
Also do your best to factor commits appropriately, not too large with unrelated things in the same commit, and not too small with the same small change applied N times in N different commits.
|
||||
|
||||
### PR - CI Process
|
||||
|
||||
The [Ryujinx continuous integration](https://github.com/Ryujinx/Ryujinx/actions) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean or have bugs properly filed against flaky/unexpected failures that are unrelated to your change.
|
||||
|
||||
If the CI build fails for any reason, the PR actions tab should be consulted for further information on the failure. There are a few usual suspects for such a failure:
|
||||
* `dotnet format` has not been run on the PR and has outstanding stylistic issues.
|
||||
* There is an error within the PR that fails a test or errors the compiler.
|
||||
* Random failure of the workflow can occasionally result in a CI failure. In this scenario a maintainer will manually restart the job.
|
||||
|
||||
### PR Feedback
|
||||
|
||||
Ryujinx team and community members will provide feedback on your change. Community feedback is highly valued. You may see the absence of team feedback if the community has already provided good review feedback.
|
||||
|
||||
Two Ryujinx team members must review and approve every PR prior to merge. They will often reply with "LGTM, see nit". That means that the PR will be merged once the feedback is resolved. "LGTM" == "looks good to me".
|
||||
|
||||
There are lots of thoughts and [approaches](https://github.com/antlr/antlr4-cpp/blob/master/CONTRIBUTING.md#emoji) for how to efficiently discuss changes. It is best to be clear and explicit with your feedback. Please be patient with people who might not understand the finer details about your approach to feedback.
|
||||
|
||||
#### Copying Changes from Other Projects
|
||||
|
||||
Ryujinx uses some implementations and frameworks from other projects. The following rules must be followed for PRs that include changes from another project:
|
||||
|
||||
- The license of the file is [permissive](https://en.wikipedia.org/wiki/Permissive_free_software_licence).
|
||||
- The license of the file is left in-tact.
|
||||
- The contribution is correctly attributed in the [3rd party notices](https://github.com/Ryujinx/Ryujinx/blob/master/distribution/legal/THIRDPARTY.md) file in the repository, as needed.
|
||||
|
111
distribution/macos/create_macos_build_headless.sh
Executable file
111
distribution/macos/create_macos_build_headless.sh
Executable file
@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 7 ]; then
|
||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$1"
|
||||
mkdir -p "$2"
|
||||
mkdir -p "$3"
|
||||
|
||||
BASE_DIR=$(readlink -f "$1")
|
||||
TEMP_DIRECTORY=$(readlink -f "$2")
|
||||
OUTPUT_DIRECTORY=$(readlink -f "$3")
|
||||
ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
||||
VERSION=$5
|
||||
SOURCE_REVISION_ID=$6
|
||||
CONFIGURATION=$7
|
||||
EXTRA_ARGS=$8
|
||||
|
||||
if [ "$VERSION" == "1.1.0" ];
|
||||
then
|
||||
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
|
||||
else
|
||||
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar
|
||||
fi
|
||||
|
||||
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
|
||||
X64_OUTPUT="$TEMP_DIRECTORY/publish_x64"
|
||||
UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish"
|
||||
EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL2
|
||||
|
||||
rm -rf "$TEMP_DIRECTORY"
|
||||
mkdir -p "$TEMP_DIRECTORY"
|
||||
|
||||
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
|
||||
|
||||
dotnet restore
|
||||
dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL2
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2
|
||||
|
||||
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
|
||||
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
||||
|
||||
# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
|
||||
# TODO: remove this once done
|
||||
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
|
||||
|
||||
rm -rf "$OUTPUT_DIRECTORY"
|
||||
mkdir -p "$OUTPUT_DIRECTORY"
|
||||
|
||||
# Let's copy one of the two different outputs and remove the executable
|
||||
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
|
||||
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
|
||||
|
||||
# Make it libraries universal
|
||||
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
|
||||
|
||||
if ! [ -x "$(command -v lipo)" ];
|
||||
then
|
||||
if ! [ -x "$(command -v llvm-lipo-14)" ];
|
||||
then
|
||||
LIPO=llvm-lipo
|
||||
else
|
||||
LIPO=llvm-lipo-14
|
||||
fi
|
||||
else
|
||||
LIPO=lipo
|
||||
fi
|
||||
|
||||
# Make the executable universal
|
||||
$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create
|
||||
|
||||
# Now sign it
|
||||
if ! [ -x "$(command -v codesign)" ];
|
||||
then
|
||||
if ! [ -x "$(command -v rcodesign)" ];
|
||||
then
|
||||
echo "Cannot find rcodesign on your system, please install rcodesign."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
|
||||
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
|
||||
echo "Using rcodesign for ad-hoc signing"
|
||||
for FILE in "$UNIVERSAL_OUTPUT"/*; do
|
||||
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
|
||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "Using codesign for ad-hoc signing"
|
||||
for FILE in "$UNIVERSAL_OUTPUT"/*; do
|
||||
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Creating archive"
|
||||
pushd "$OUTPUT_DIRECTORY"
|
||||
tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null
|
||||
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2"
|
||||
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||
rm "$RELEASE_TAR_FILE_NAME"
|
||||
popd
|
||||
|
||||
echo "Done"
|
40
docs/README.md
Normal file
40
docs/README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Documents Index
|
||||
|
||||
This repo includes several documents that explain both high-level and low-level concepts about Ryujinx and its functions. These are very useful for contributors, to get context that can be very difficult to acquire from just reading code.
|
||||
|
||||
Intro to Ryujinx
|
||||
==================
|
||||
|
||||
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
|
||||
* The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions.
|
||||
* The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
|
||||
* Audio output is entirely supported via C# wrappers for SDL2, with OpenAL & libsoundio as fallbacks.
|
||||
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
- [Installing the .NET SDK](https://dotnet.microsoft.com/download)
|
||||
- [Official .NET Docs](https://docs.microsoft.com/dotnet/core/)
|
||||
|
||||
Contributing (Building, testing, benchmarking, profiling, etc.)
|
||||
===============
|
||||
|
||||
If you want to contribute a code change to this repo, start here.
|
||||
|
||||
- [Contributor Guide](../CONTRIBUTING.md)
|
||||
|
||||
Coding Guidelines
|
||||
=================
|
||||
|
||||
- [C# coding style](coding-guidelines/coding-style.md)
|
||||
- [Service Implementation Guidelines - WIP](https://gist.github.com/gdkchan/84ba88cd50efbe58d1babfaa7cd7c455)
|
||||
|
||||
Project Docs
|
||||
=================
|
||||
|
||||
To be added. Many project files will contain basic XML docs for key functions and classes in the meantime.
|
||||
|
||||
Other Information
|
||||
=================
|
||||
|
||||
- N/A
|
116
docs/coding-guidelines/coding-style.md
Normal file
116
docs/coding-guidelines/coding-style.md
Normal file
@ -0,0 +1,116 @@
|
||||
# C# Coding Style
|
||||
|
||||
The general rule we follow is "use Visual Studio defaults".
|
||||
Using an IDE that supports the `.editorconfig` standard will make this much simpler.
|
||||
|
||||
1. We use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) braces, where each brace begins on a new line. A single line statement block can go without braces but the block must be properly indented on its own line and must not be nested in other statement blocks that use braces (See rule 18 for more details). One exception is that a `using` statement is permitted to be nested within another `using` statement by starting on the following line at the same indentation level, even if the nested `using` contains a controlled block.
|
||||
2. We use four spaces of indentation (no tabs).
|
||||
3. We use `_camelCase` for internal and private fields and use `readonly` where possible. Prefix internal and private instance fields with `_`, static fields with `s_` and thread static fields with `t_`. When used on static fields, `readonly` should come after `static` (e.g. `static readonly` not `readonly static`). Public fields should be used sparingly and should use PascalCasing with no prefix when used.
|
||||
4. We avoid `this.` unless absolutely necessary.
|
||||
5. We always specify the visibility, even if it's the default (e.g.
|
||||
`private string _foo` not `string _foo`). Visibility should be the first modifier (e.g.
|
||||
`public abstract` not `abstract public`).
|
||||
6. Namespace imports should be specified at the top of the file, *outside* of `namespace` declarations.
|
||||
7. Avoid more than one empty line at any time. For example, do not have two
|
||||
blank lines between members of a type.
|
||||
8. Avoid spurious free spaces.
|
||||
For example avoid `if (someVar == 0)...`, where the dots mark the spurious free spaces.
|
||||
Consider enabling "View White Space (Ctrl+R, Ctrl+W)" or "Edit -> Advanced -> View White Space" if using Visual Studio to aid detection.
|
||||
9. If a file happens to differ in style from these guidelines (e.g. private members are named `m_member`
|
||||
rather than `_member`), the existing style in that file takes precedence.
|
||||
10. We only use `var` when the type is explicitly named on the right-hand side, typically due to either `new` or an explicit cast, e.g. `var stream = new FileStream(...)` not `var stream = OpenStandardInput()`.
|
||||
- Similarly, target-typed `new()` can only be used when the type is explicitly named on the left-hand side, in a variable definition statement or a field definition statement. e.g. `FileStream stream = new(...);`, but not `stream = new(...);` (where the type was specified on a previous line).
|
||||
11. We use language keywords instead of BCL types (e.g. `int, string, float` instead of `Int32, String, Single`, etc) for both type references as well as method calls (e.g. `int.Parse` instead of `Int32.Parse`). See issue [#13976](https://github.com/dotnet/runtime/issues/13976) for examples.
|
||||
12. We use PascalCasing to name all our constant local variables and fields. The only exception is for interop code where the constant value should exactly match the name and value of the code you are calling via interop.
|
||||
13. We use PascalCasing for all method names, including local functions.
|
||||
14. We use ```nameof(...)``` instead of ```"..."``` whenever possible and relevant.
|
||||
15. Fields should be specified at the top within type declarations.
|
||||
16. When including non-ASCII characters in the source code use Unicode escape sequences (\uXXXX) instead of literal characters. Literal non-ASCII characters occasionally get garbled by a tool or editor.
|
||||
17. When using labels (for goto), indent the label one less than the current indentation.
|
||||
18. When using a single-statement if, we follow these conventions:
|
||||
- Never use single-line form (for example: `if (source == null) throw new ArgumentNullException("source");`)
|
||||
- Using braces is always accepted, and required if any block of an `if`/`else if`/.../`else` compound statement uses braces or if a single statement body spans multiple lines.
|
||||
- Braces may be omitted only if the body of *every* block associated with an `if`/`else if`/.../`else` compound statement is placed on a single line.
|
||||
19. Make all internal and private types static or sealed unless derivation from them is required. As with any implementation detail, they can be changed if/when derivation is required in the future.
|
||||
20. XML docs should be used when writing interfaces or when a class/method is deemed sufficient in scope or complexity.
|
||||
21. So-called [Magic Numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) should be defined as named constants before use (for example `for (int i = 56; i < 68; i++)` could read `for (int i = _currentAge; i < _retireAge; i++)`).
|
||||
This may be ignored for trivial or syntactically common statements.
|
||||
|
||||
An [EditorConfig](https://editorconfig.org "EditorConfig homepage") file (`.editorconfig`) has been provided at the root of the runtime repository, enabling C# auto-formatting conforming to the above guidelines.
|
||||
|
||||
### Example File:
|
||||
|
||||
``ShaderCache.cs:``
|
||||
|
||||
```C#
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Memory cache of shader code.
|
||||
/// </summary>
|
||||
class ShaderCache : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Default flags used on the shader translation process.
|
||||
/// </summary>
|
||||
public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
|
||||
|
||||
private readonly struct TranslatedShader
|
||||
{
|
||||
public readonly CachedShaderStage Shader;
|
||||
public readonly ShaderProgram Program;
|
||||
|
||||
public TranslatedShader(CachedShaderStage shader, ShaderProgram program)
|
||||
{
|
||||
Shader = shader;
|
||||
Program = program;
|
||||
}
|
||||
}
|
||||
...
|
||||
|
||||
/// <summary>
|
||||
/// Processes the queue of shaders that must save their binaries to the disk cache.
|
||||
/// </summary>
|
||||
public void ProcessShaderCacheQueue()
|
||||
{
|
||||
// Check to see if the binaries for previously compiled shaders are ready, and save them out.
|
||||
|
||||
while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave))
|
||||
{
|
||||
ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false);
|
||||
|
||||
if (result != ProgramLinkStatus.Incomplete)
|
||||
{
|
||||
if (result == ProgramLinkStatus.Success)
|
||||
{
|
||||
_cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
|
||||
}
|
||||
|
||||
_programsToSaveQueue.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For other languages, our current best guidance is consistency. When editing files, keep new code and changes consistent with the style in the files. For new files, it should conform to the style for that component. If there is a completely new component, anything that is reasonably broadly accepted is fine.
|
56
docs/workflow/pr-guide.md
Normal file
56
docs/workflow/pr-guide.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Pull Request Guide
|
||||
|
||||
## Contributing Rules
|
||||
|
||||
All contributions to Ryujinx/Ryujinx repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team.
|
||||
|
||||
To merge pull requests, you must have write permissions in the repository.
|
||||
|
||||
## Quick Code Review Rules
|
||||
|
||||
* Do not mix unrelated changes in one pull request. For example, a code style change should never be mixed with a bug fix.
|
||||
* All changes should follow the existing code style. You can read more about our code style at [docs/coding-guidelines](../coding-guidelines/coding-style.md).
|
||||
* Adding external dependencies is to be avoided unless not doing so would introduce _significant_ complexity. Any dependency addition should be justified and discussed before merge.
|
||||
* Use Draft pull requests for changes you are still working on but want early CI loop feedback. When you think your changes are ready for review, [change the status](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request) of your pull request.
|
||||
* Rebase your changes when required or directly requested. Changes should always be commited on top of the upstream branch, not the other way around.
|
||||
* If you are asked to make changes during the review process do them as a new commit.
|
||||
* Only resolve GitHub conversations with reviewers once they have been addressed with a commit, or via a mutual agreement.
|
||||
|
||||
## Pull Request Ownership
|
||||
|
||||
Every pull request will have automatically have labels and reviewers assigned. The label not only indicates the code segment which the change touches but also the area reviewers to be assigned.
|
||||
|
||||
If during the code review process a merge conflict occurs, the PR author is responsible for its resolution. Help will be provided if necessary although GitHub makes this easier by allowing simple conflict resolution using the [conflict-editor](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github).
|
||||
|
||||
## Pull Request Builds
|
||||
|
||||
When submitting a PR to the `Ryujinx/Ryujinx` repository, various builds will run validating many areas to ensure we keep developer productivity and product quality high. These various workflows can be tracked in the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of the repository. If the job continues to completion, the build artifacts will be uploaded and posted as a comment in the PR discussion.
|
||||
|
||||
## Review Turnaround Times
|
||||
|
||||
Ryujinx is a project that is maintained by volunteers on a completely free-time basis. As such we cannot guarantee any particular timeframe for pull request review and approval. Weeks to months are common for larger (>500 line) PRs but there are some additional best practises to avoid review purgatory.
|
||||
|
||||
* Make the reviewers life easier wherever possible. Make use of descriptive commit names, code comments and XML docs where applicable.
|
||||
* If there is disagreement on feedback then always lean on the side of the development team and community over any personal opinion.
|
||||
* We're human. We miss things. We forget things. If there has been radio silence on your changes for a substantial period of time then do not hesitate to reach out directly either with something simple like "bump" on GitHub or a directly on Discord.
|
||||
|
||||
To re-iterate, make the review as easy for us as possible, respond promptly and be comfortable to interact directly with us for anything else.
|
||||
|
||||
## Merging Pull Requests
|
||||
|
||||
Anyone with write access can merge a pull request manually when the following conditions have been met:
|
||||
|
||||
* The PR has been approved by two reviewers and any other objections are addressed.
|
||||
* You can request follow up reviews from the original reviewers if they requested changes.
|
||||
* The PR successfully builds and passes all tests in the Continuous Integration (CI) system. In case of failures, refer to the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of your PR.
|
||||
|
||||
Typically, PRs are merged as one commit (squash merges). It creates a simpler history than a Merge Commit. "Special circumstances" are rare, and typically mean that there are a series of cleanly separated changes that will be too hard to understand if squashed together, or for some reason we want to preserve the ability to dissect them.
|
||||
|
||||
## Blocking Pull Request Merging
|
||||
|
||||
If for whatever reason you would like to move your pull request back to an in-progress status to avoid merging it in the current form, you can turn the PR into a draft PR by selecting the option under the reviewers section. Alternatively, you can do that by adding [WIP] prefix to the pull request title.
|
||||
|
||||
## Old Pull Request Policy
|
||||
|
||||
From time to time we will review older PRs and check them for relevance. If we find the PR is inactive or no longer applies, we will close it. As the PR owner, you can simply reopen it if you feel your closed PR needs our attention.
|
||||
|
@ -20,6 +20,11 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetBufferSize<T>(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged
|
||||
{
|
||||
if (endSampleOffset < startSampleOffset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf<T>();
|
||||
}
|
||||
|
||||
|
@ -264,8 +264,8 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
||||
{
|
||||
uint dataTypeSize = (uint)Unsafe.SizeOf<T>();
|
||||
|
||||
return StartSampleOffset * dataTypeSize <= Size &&
|
||||
EndSampleOffset * dataTypeSize <= Size;
|
||||
return (ulong)StartSampleOffset * dataTypeSize <= Size &&
|
||||
(ulong)EndSampleOffset * dataTypeSize <= Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -544,7 +544,7 @@
|
||||
"SwkbdMinCharacters": "Must be at least {0} characters long",
|
||||
"SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
|
||||
"SoftwareKeyboard": "Software Keyboard",
|
||||
"SoftwareKeyboardModeNumbersOnly": "Must be numbers only",
|
||||
"SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only",
|
||||
"SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only",
|
||||
"SoftwareKeyboardModeASCII": "Must be ASCII text only",
|
||||
"DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Core;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Markup.Xaml.MarkupExtensions;
|
||||
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.Common.Locale
|
||||
@ -18,11 +19,20 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
{
|
||||
LocaleKeys keyToUse = Key;
|
||||
|
||||
ReflectionBindingExtension binding = new($"[{keyToUse}]")
|
||||
{
|
||||
Mode = BindingMode.OneWay,
|
||||
Source = LocaleManager.Instance,
|
||||
};
|
||||
var builder = new CompiledBindingPathBuilder();
|
||||
|
||||
builder.SetRawSource(LocaleManager.Instance)
|
||||
.Property(new ClrPropertyInfo("Item",
|
||||
obj => (LocaleManager.Instance[keyToUse]),
|
||||
null,
|
||||
typeof(string)), (weakRef, iPropInfo) =>
|
||||
{
|
||||
return PropertyInfoAccessorFactory.CreateInpcPropertyAccessor(weakRef, iPropInfo);
|
||||
});
|
||||
|
||||
var path = builder.Build();
|
||||
|
||||
var binding = new CompiledBindingExtension(path);
|
||||
|
||||
return binding.ProvideValue(serviceProvider);
|
||||
}
|
||||
|
@ -6,9 +6,11 @@
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{locale:Locale ErrorWindowTitle}"
|
||||
xmlns:views="using:Ryujinx.Ava.UI.Applet"
|
||||
Width="450"
|
||||
Height="340"
|
||||
CanResize="False"
|
||||
x:DataType="views:ErrorAppletWindow"
|
||||
SizeToContent="Height"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
@ -38,7 +40,7 @@
|
||||
Grid.Column="1"
|
||||
Margin="10"
|
||||
VerticalAlignment="Stretch"
|
||||
Text="{ReflectionBinding Message}"
|
||||
Text="{Binding Message}"
|
||||
TextWrapping="Wrap" />
|
||||
<StackPanel
|
||||
Name="ButtonStack"
|
||||
@ -49,4 +51,4 @@
|
||||
Orientation="Horizontal"
|
||||
Spacing="10" />
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
@ -4,7 +4,9 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="using:Ryujinx.Ava.UI.Controls"
|
||||
Width="400"
|
||||
x:DataType="views:SwkbdAppletDialog"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
@ -34,13 +36,13 @@
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="5"
|
||||
Text="{ReflectionBinding MainText}"
|
||||
Text="{Binding MainText}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="5"
|
||||
Text="{ReflectionBinding SecondaryText}"
|
||||
Text="{Binding SecondaryText}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Name="Input"
|
||||
@ -50,7 +52,7 @@
|
||||
VerticalAlignment="Center"
|
||||
Focusable="True"
|
||||
KeyUp="Message_KeyUp"
|
||||
Text="{ReflectionBinding Message}"
|
||||
Text="{Binding Message}"
|
||||
TextInput="Message_TextInput"
|
||||
TextWrapping="Wrap"
|
||||
UseFloatingWatermark="True" />
|
||||
|
@ -136,10 +136,10 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
string localeText;
|
||||
switch (mode)
|
||||
{
|
||||
case KeyboardMode.NumbersOnly:
|
||||
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumbersOnly);
|
||||
case KeyboardMode.Numeric:
|
||||
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumeric);
|
||||
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
|
||||
_checkInput = text => text.All(char.IsDigit);
|
||||
_checkInput = text => text.All(NumericCharacterValidation.IsNumeric);
|
||||
break;
|
||||
case KeyboardMode.Alphabet:
|
||||
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet);
|
||||
|
@ -46,7 +46,7 @@
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
|
||||
<Setter Property="MinHeight" Value="{ReflectionBinding $parent[UserControl].DataContext.GridItemSelectorSize}" />
|
||||
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).GridItemSelectorSize}" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
@ -56,10 +56,10 @@
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}"
|
||||
Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
@ -78,7 +78,7 @@
|
||||
Margin="0,10,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{ReflectionBinding $parent[UserControl].DataContext.ShowNames}">
|
||||
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
@ -101,4 +101,4 @@
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -42,7 +42,7 @@
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
|
||||
<Setter Property="MinHeight" Value="{ReflectionBinding $parent[UserControl].DataContext.ListItemSelectorSize}" />
|
||||
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ListItemSelectorSize}" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
@ -67,10 +67,10 @@
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Margin="0"
|
||||
Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}"
|
||||
Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
@ -157,4 +157,4 @@
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -465,6 +465,7 @@
|
||||
Maximum="1"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Minimum="0"
|
||||
Value="{ReflectionBinding Configuration.DeadzoneLeft, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
@ -484,6 +485,7 @@
|
||||
Maximum="2"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Minimum="0"
|
||||
Value="{ReflectionBinding Configuration.RangeLeft, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
@ -607,6 +609,7 @@
|
||||
Maximum="1"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Minimum="0"
|
||||
Value="{ReflectionBinding Configuration.TriggerThreshold, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
@ -1085,6 +1088,7 @@
|
||||
Maximum="1"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Padding="0"
|
||||
VerticalAlignment="Center"
|
||||
Minimum="0"
|
||||
@ -1106,6 +1110,7 @@
|
||||
Maximum="2"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Minimum="0"
|
||||
Value="{ReflectionBinding Configuration.RangeRight, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
|
@ -29,6 +29,7 @@
|
||||
MaxWidth="150"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{Binding Sensitivity, Mode=TwoWay}" />
|
||||
@ -50,6 +51,7 @@
|
||||
MaxWidth="150"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{Binding GyroDeadzone, Mode=TwoWay}" />
|
||||
|
@ -26,6 +26,7 @@
|
||||
Width="200"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Maximum="10"
|
||||
Minimum="0"
|
||||
Value="{Binding StrongRumble, Mode=TwoWay}" />
|
||||
@ -47,6 +48,7 @@
|
||||
Maximum="10"
|
||||
TickFrequency="0.01"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="0.01"
|
||||
Minimum="0"
|
||||
Value="{Binding WeakRumble, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
|
@ -56,6 +56,7 @@
|
||||
Margin="5,-10,5,0"
|
||||
VerticalAlignment="Center"
|
||||
IsSnapToTickEnabled="True"
|
||||
SmallChange="1"
|
||||
Maximum="4"
|
||||
Minimum="1"
|
||||
TickFrequency="1"
|
||||
|
@ -11,6 +11,7 @@
|
||||
Height="500"
|
||||
MinWidth="500"
|
||||
MinHeight="500"
|
||||
x:DataType="window:CheatWindow"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
@ -40,7 +41,7 @@
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
LineHeight="18"
|
||||
Text="{ReflectionBinding Heading}"
|
||||
Text="{Binding Heading}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
@ -61,7 +62,7 @@
|
||||
MinWidth="160"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{ReflectionBinding BuildId}"
|
||||
Text="{Binding BuildId}"
|
||||
IsReadOnly="True" />
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
@ -77,7 +78,7 @@
|
||||
MinHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ItemsSource="{ReflectionBinding LoadedCheats}">
|
||||
ItemsSource="{Binding LoadedCheats}">
|
||||
<TreeView.Styles>
|
||||
<Styles>
|
||||
<Style Selector="TreeViewItem:empty /template/ ItemsPresenter">
|
||||
@ -120,18 +121,18 @@
|
||||
Name="SaveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{ReflectionBinding Save}"
|
||||
IsVisible="{ReflectionBinding !NoCheatsFound}">
|
||||
Command="{Binding Save}"
|
||||
IsVisible="{Binding !NoCheatsFound}">
|
||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{ReflectionBinding Close}">
|
||||
Command="{Binding Close}">
|
||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
||||
</window:StyleableWindow>
|
||||
|
@ -17,7 +17,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
private readonly string _enabledCheatsPath;
|
||||
public bool NoCheatsFound { get; }
|
||||
|
||||
private AvaloniaList<CheatsList> LoadedCheats { get; }
|
||||
public AvaloniaList<CheatsList> LoadedCheats { get; }
|
||||
|
||||
public string Heading { get; }
|
||||
public string BuildId { get; }
|
||||
|
@ -39,14 +39,14 @@
|
||||
Name="EnableAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{ReflectionBinding EnableAll}">
|
||||
Command="{Binding EnableAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="DisableAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{ReflectionBinding DisableAll}">
|
||||
Command="{Binding DisableAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
@ -157,14 +157,14 @@
|
||||
Name="AddButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{ReflectionBinding Add}">
|
||||
Command="{Binding Add}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{ReflectionBinding RemoveAll}">
|
||||
Command="{Binding RemoveAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
@ -189,4 +189,4 @@
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsShaderBarrierDivergence;
|
||||
public readonly bool SupportsShaderFloat64;
|
||||
public readonly bool SupportsTextureShadowLod;
|
||||
public readonly bool SupportsVertexStoreAndAtomics;
|
||||
public readonly bool SupportsViewportIndexVertexTessellation;
|
||||
public readonly bool SupportsViewportMask;
|
||||
public readonly bool SupportsViewportSwizzle;
|
||||
@ -52,7 +53,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
public readonly int MaximumComputeSharedMemorySize;
|
||||
public readonly float MaximumSupportedAnisotropy;
|
||||
public readonly int ShaderSubgroupSize;
|
||||
public readonly int StorageBufferOffsetAlignment;
|
||||
public readonly int TextureBufferOffsetAlignment;
|
||||
|
||||
public readonly int GatherBiasPrecision;
|
||||
|
||||
@ -90,6 +93,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsShaderBarrierDivergence,
|
||||
bool supportsShaderFloat64,
|
||||
bool supportsTextureShadowLod,
|
||||
bool supportsVertexStoreAndAtomics,
|
||||
bool supportsViewportIndexVertexTessellation,
|
||||
bool supportsViewportMask,
|
||||
bool supportsViewportSwizzle,
|
||||
@ -101,7 +105,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
uint maximumImagesPerStage,
|
||||
int maximumComputeSharedMemorySize,
|
||||
float maximumSupportedAnisotropy,
|
||||
int shaderSubgroupSize,
|
||||
int storageBufferOffsetAlignment,
|
||||
int textureBufferOffsetAlignment,
|
||||
int gatherBiasPrecision)
|
||||
{
|
||||
Api = api;
|
||||
@ -137,6 +143,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
|
||||
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
|
||||
SupportsViewportMask = supportsViewportMask;
|
||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||
@ -148,7 +155,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
MaximumImagesPerStage = maximumImagesPerStage;
|
||||
MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize;
|
||||
MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
|
||||
ShaderSubgroupSize = shaderSubgroupSize;
|
||||
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
|
||||
TextureBufferOffsetAlignment = textureBufferOffsetAlignment;
|
||||
GatherBiasPrecision = gatherBiasPrecision;
|
||||
}
|
||||
}
|
||||
|
@ -15,14 +15,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
BufferImage,
|
||||
}
|
||||
|
||||
public enum ResourceAccess : byte
|
||||
{
|
||||
None = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
ReadWrite = Read | Write,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ResourceStages : byte
|
||||
{
|
||||
@ -81,19 +73,17 @@ namespace Ryujinx.Graphics.GAL
|
||||
public int Binding { get; }
|
||||
public ResourceType Type { get; }
|
||||
public ResourceStages Stages { get; }
|
||||
public ResourceAccess Access { get; }
|
||||
|
||||
public ResourceUsage(int binding, ResourceType type, ResourceStages stages, ResourceAccess access)
|
||||
public ResourceUsage(int binding, ResourceType type, ResourceStages stages)
|
||||
{
|
||||
Binding = binding;
|
||||
Type = type;
|
||||
Stages = stages;
|
||||
Access = access;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Binding, Type, Stages, Access);
|
||||
return HashCode.Combine(Binding, Type, Stages);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
@ -103,7 +93,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
public bool Equals(ResourceUsage other)
|
||||
{
|
||||
return Binding == other.Binding && Type == other.Type && Stages == other.Stages && Access == other.Access;
|
||||
return Binding == other.Binding && Type == other.Type && Stages == other.Stages;
|
||||
}
|
||||
|
||||
public static bool operator ==(ResourceUsage left, ResourceUsage right)
|
||||
|
@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
/// <summary>
|
||||
/// Represents a GPU General Purpose FIFO command processor.
|
||||
/// </summary>
|
||||
class GPFifoProcessor
|
||||
class GPFifoProcessor : IDisposable
|
||||
{
|
||||
private const int MacrosCount = 0x80;
|
||||
private const int MacroIndexMask = MacrosCount - 1;
|
||||
@ -327,5 +327,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
{
|
||||
_3dClass.PerformDeferredDraws();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_3dClass.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,141 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex info buffer data updater.
|
||||
/// </summary>
|
||||
class VertexInfoBufferUpdater : BufferUpdater
|
||||
{
|
||||
private VertexInfoBuffer _data;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the vertex info buffer updater.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Renderer that the vertex info buffer will be used with</param>
|
||||
public VertexInfoBufferUpdater(IRenderer renderer) : base(renderer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets vertex data related counts.
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">Number of vertices used on the draw</param>
|
||||
/// <param name="instanceCount">Number of draw instances</param>
|
||||
/// <param name="firstVertex">Index of the first vertex on the vertex buffer</param>
|
||||
/// <param name="firstInstance">Index of the first instanced vertex on the vertex buffer</param>
|
||||
public void SetVertexCounts(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
|
||||
{
|
||||
if (_data.VertexCounts.X != vertexCount)
|
||||
{
|
||||
_data.VertexCounts.X = vertexCount;
|
||||
MarkDirty(VertexInfoBuffer.VertexCountsOffset, sizeof(int));
|
||||
}
|
||||
|
||||
if (_data.VertexCounts.Y != instanceCount)
|
||||
{
|
||||
_data.VertexCounts.Y = instanceCount;
|
||||
MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int), sizeof(int));
|
||||
}
|
||||
|
||||
if (_data.VertexCounts.Z != firstVertex)
|
||||
{
|
||||
_data.VertexCounts.Z = firstVertex;
|
||||
MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int) * 2, sizeof(int));
|
||||
}
|
||||
|
||||
if (_data.VertexCounts.W != firstInstance)
|
||||
{
|
||||
_data.VertexCounts.W = firstInstance;
|
||||
MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int) * 3, sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets vertex data related counts.
|
||||
/// </summary>
|
||||
/// <param name="primitivesCount">Number of primitives consumed by the geometry shader</param>
|
||||
public void SetGeometryCounts(int primitivesCount)
|
||||
{
|
||||
if (_data.GeometryCounts.X != primitivesCount)
|
||||
{
|
||||
_data.GeometryCounts.X = primitivesCount;
|
||||
MarkDirty(VertexInfoBuffer.GeometryCountsOffset, sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a vertex stride and related data.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the vertex stride to be updated</param>
|
||||
/// <param name="stride">Stride divided by the component or format size</param>
|
||||
/// <param name="componentCount">Number of components that the format has</param>
|
||||
public void SetVertexStride(int index, int stride, int componentCount)
|
||||
{
|
||||
if (_data.VertexStrides[index].X != stride)
|
||||
{
|
||||
_data.VertexStrides[index].X = stride;
|
||||
MarkDirty(VertexInfoBuffer.VertexStridesOffset + index * Unsafe.SizeOf<Vector4<int>>(), sizeof(int));
|
||||
}
|
||||
|
||||
for (int c = 1; c < 4; c++)
|
||||
{
|
||||
int value = c < componentCount ? 1 : 0;
|
||||
|
||||
ref int currentValue = ref GetElementRef(ref _data.VertexStrides[index], c);
|
||||
|
||||
if (currentValue != value)
|
||||
{
|
||||
currentValue = value;
|
||||
MarkDirty(VertexInfoBuffer.VertexStridesOffset + index * Unsafe.SizeOf<Vector4<int>>() + c * sizeof(int), sizeof(int));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a vertex offset and related data.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the vertex offset to be updated</param>
|
||||
/// <param name="offset">Offset divided by the component or format size</param>
|
||||
/// <param name="divisor">If the draw is instanced, should have the vertex divisor value, otherwise should be zero</param>
|
||||
public void SetVertexOffset(int index, int offset, int divisor)
|
||||
{
|
||||
if (_data.VertexOffsets[index].X != offset)
|
||||
{
|
||||
_data.VertexOffsets[index].X = offset;
|
||||
MarkDirty(VertexInfoBuffer.VertexOffsetsOffset + index * Unsafe.SizeOf<Vector4<int>>(), sizeof(int));
|
||||
}
|
||||
|
||||
if (_data.VertexOffsets[index].Y != divisor)
|
||||
{
|
||||
_data.VertexOffsets[index].Y = divisor;
|
||||
MarkDirty(VertexInfoBuffer.VertexOffsetsOffset + index * Unsafe.SizeOf<Vector4<int>>() + sizeof(int), sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the offset of the index buffer.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset divided by the component size</param>
|
||||
public void SetIndexBufferOffset(int offset)
|
||||
{
|
||||
if (_data.GeometryCounts.W != offset)
|
||||
{
|
||||
_data.GeometryCounts.W = offset;
|
||||
MarkDirty(VertexInfoBuffer.GeometryCountsOffset + sizeof(int) * 3, sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits all pending buffer updates to the GPU.
|
||||
/// </summary>
|
||||
public void Commit()
|
||||
{
|
||||
Commit(MemoryMarshal.Cast<VertexInfoBuffer, byte>(MemoryMarshal.CreateSpan(ref _data, 1)));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex, tessellation and geometry as compute shader draw manager.
|
||||
/// </summary>
|
||||
class VtgAsCompute : IDisposable
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
private readonly VtgAsComputeContext _vacContext;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the vertex, tessellation and geometry as compute shader draw manager.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">3D engine state</param>
|
||||
public VtgAsCompute(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_vacContext = new(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emulates the pre-rasterization stages of a draw operation using a compute shader.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine</param>
|
||||
/// <param name="vertexAsCompute">Vertex shader converted to compute</param>
|
||||
/// <param name="geometryAsCompute">Optional geometry shader converted to compute</param>
|
||||
/// <param name="vertexPassthroughProgram">Fragment shader with a vertex passthrough shader to feed the compute output into the fragment stage</param>
|
||||
/// <param name="topology">Primitive topology of the draw</param>
|
||||
/// <param name="count">Index or vertex count of the draw</param>
|
||||
/// <param name="instanceCount">Instance count</param>
|
||||
/// <param name="firstIndex">First index on the index buffer, for indexed draws</param>
|
||||
/// <param name="firstVertex">First vertex on the vertex buffer</param>
|
||||
/// <param name="firstInstance">First instance</param>
|
||||
/// <param name="indexed">Whether the draw is indexed</param>
|
||||
public void DrawAsCompute(
|
||||
ThreedClass engine,
|
||||
ShaderAsCompute vertexAsCompute,
|
||||
ShaderAsCompute geometryAsCompute,
|
||||
IProgram vertexPassthroughProgram,
|
||||
PrimitiveTopology topology,
|
||||
int count,
|
||||
int instanceCount,
|
||||
int firstIndex,
|
||||
int firstVertex,
|
||||
int firstInstance,
|
||||
bool indexed)
|
||||
{
|
||||
VtgAsComputeState state = new(
|
||||
_context,
|
||||
_channel,
|
||||
_state,
|
||||
_vacContext,
|
||||
engine,
|
||||
vertexAsCompute,
|
||||
geometryAsCompute,
|
||||
vertexPassthroughProgram,
|
||||
topology,
|
||||
count,
|
||||
instanceCount,
|
||||
firstIndex,
|
||||
firstVertex,
|
||||
firstInstance,
|
||||
indexed);
|
||||
|
||||
state.RunVertex();
|
||||
state.RunGeometry();
|
||||
state.RunFragment();
|
||||
|
||||
_vacContext.FreeBuffers();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_vacContext.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,651 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex, tessellation and geometry as compute shader context.
|
||||
/// </summary>
|
||||
class VtgAsComputeContext : IDisposable
|
||||
{
|
||||
private const int DummyBufferSize = 16;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
/// <summary>
|
||||
/// Cache of buffer textures used for vertex and index buffers.
|
||||
/// </summary>
|
||||
private class BufferTextureCache : IDisposable
|
||||
{
|
||||
private readonly Dictionary<Format, ITexture> _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer texture cache.
|
||||
/// </summary>
|
||||
public BufferTextureCache()
|
||||
{
|
||||
_cache = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached or creates and caches a buffer texture with the specified format.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Renderer where the texture will be used</param>
|
||||
/// <param name="format">Format of the buffer texture</param>
|
||||
/// <returns>Buffer texture</returns>
|
||||
public ITexture Get(IRenderer renderer, Format format)
|
||||
{
|
||||
if (!_cache.TryGetValue(format, out ITexture bufferTexture))
|
||||
{
|
||||
bufferTexture = renderer.CreateTexture(new TextureCreateInfo(
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
format,
|
||||
DepthStencilMode.Depth,
|
||||
Target.TextureBuffer,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha));
|
||||
|
||||
_cache.Add(format, bufferTexture);
|
||||
}
|
||||
|
||||
return bufferTexture;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (var texture in _cache.Values)
|
||||
{
|
||||
texture.Release();
|
||||
}
|
||||
|
||||
_cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buffer state.
|
||||
/// </summary>
|
||||
private struct Buffer
|
||||
{
|
||||
/// <summary>
|
||||
/// Buffer handle.
|
||||
/// </summary>
|
||||
public BufferHandle Handle;
|
||||
|
||||
/// <summary>
|
||||
/// Current free buffer offset.
|
||||
/// </summary>
|
||||
public int Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Total buffer size in bytes.
|
||||
/// </summary>
|
||||
public int Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Index buffer state.
|
||||
/// </summary>
|
||||
private readonly struct IndexBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// Buffer handle.
|
||||
/// </summary>
|
||||
public BufferHandle Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Index count.
|
||||
/// </summary>
|
||||
public int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size in bytes.
|
||||
/// </summary>
|
||||
public int Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new index buffer state.
|
||||
/// </summary>
|
||||
/// <param name="handle">Buffer handle</param>
|
||||
/// <param name="count">Index count</param>
|
||||
/// <param name="size">Size in bytes</param>
|
||||
public IndexBuffer(BufferHandle handle, int count, int size)
|
||||
{
|
||||
Handle = handle;
|
||||
Count = count;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a full range starting from the beggining of the buffer.
|
||||
/// </summary>
|
||||
/// <returns>Range</returns>
|
||||
public readonly BufferRange ToRange()
|
||||
{
|
||||
return new BufferRange(Handle, 0, Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a range starting from the beggining of the buffer, with the specified size.
|
||||
/// </summary>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>Range</returns>
|
||||
public readonly BufferRange ToRange(int size)
|
||||
{
|
||||
return new BufferRange(Handle, 0, size);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly BufferTextureCache[] _bufferTextures;
|
||||
private BufferHandle _dummyBuffer;
|
||||
private Buffer _vertexDataBuffer;
|
||||
private Buffer _geometryVertexDataBuffer;
|
||||
private Buffer _geometryIndexDataBuffer;
|
||||
private BufferHandle _sequentialIndexBuffer;
|
||||
private int _sequentialIndexBufferCount;
|
||||
|
||||
private readonly Dictionary<PrimitiveTopology, IndexBuffer> _topologyRemapBuffers;
|
||||
|
||||
/// <summary>
|
||||
/// Vertex information buffer updater.
|
||||
/// </summary>
|
||||
public VertexInfoBufferUpdater VertexInfoBufferUpdater { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the vertex, tessellation and geometry as compute shader context.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public VtgAsComputeContext(GpuContext context)
|
||||
{
|
||||
_context = context;
|
||||
_bufferTextures = new BufferTextureCache[Constants.TotalVertexBuffers + 2];
|
||||
_topologyRemapBuffers = new();
|
||||
VertexInfoBufferUpdater = new(context.Renderer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of complete primitives that can be formed with a given vertex count, for a given topology.
|
||||
/// </summary>
|
||||
/// <param name="primitiveType">Topology</param>
|
||||
/// <param name="count">Vertex count</param>
|
||||
/// <returns>Total of complete primitives</returns>
|
||||
public static int GetPrimitivesCount(PrimitiveTopology primitiveType, int count)
|
||||
{
|
||||
return primitiveType switch
|
||||
{
|
||||
PrimitiveTopology.Lines => count / 2,
|
||||
PrimitiveTopology.LinesAdjacency => count / 4,
|
||||
PrimitiveTopology.LineLoop => count > 1 ? count : 0,
|
||||
PrimitiveTopology.LineStrip => Math.Max(count - 1, 0),
|
||||
PrimitiveTopology.LineStripAdjacency => Math.Max(count - 3, 0),
|
||||
PrimitiveTopology.Triangles => count / 3,
|
||||
PrimitiveTopology.TrianglesAdjacency => count / 6,
|
||||
PrimitiveTopology.TriangleStrip or
|
||||
PrimitiveTopology.TriangleFan or
|
||||
PrimitiveTopology.Polygon => Math.Max(count - 2, 0),
|
||||
PrimitiveTopology.TriangleStripAdjacency => Math.Max(count - 2, 0) / 2,
|
||||
PrimitiveTopology.Quads => (count / 4) * 2, // In triangles.
|
||||
PrimitiveTopology.QuadStrip => Math.Max((count - 2) / 2, 0) * 2, // In triangles.
|
||||
_ => count,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total of vertices that a single primitive has, for the specified topology.
|
||||
/// </summary>
|
||||
/// <param name="primitiveType">Topology</param>
|
||||
/// <returns>Vertex count</returns>
|
||||
private static int GetVerticesPerPrimitive(PrimitiveTopology primitiveType)
|
||||
{
|
||||
return primitiveType switch
|
||||
{
|
||||
PrimitiveTopology.Lines or
|
||||
PrimitiveTopology.LineLoop or
|
||||
PrimitiveTopology.LineStrip => 2,
|
||||
PrimitiveTopology.LinesAdjacency or
|
||||
PrimitiveTopology.LineStripAdjacency => 4,
|
||||
PrimitiveTopology.Triangles or
|
||||
PrimitiveTopology.TriangleStrip or
|
||||
PrimitiveTopology.TriangleFan or
|
||||
PrimitiveTopology.Polygon => 3,
|
||||
PrimitiveTopology.TrianglesAdjacency or
|
||||
PrimitiveTopology.TriangleStripAdjacency => 6,
|
||||
PrimitiveTopology.Quads or
|
||||
PrimitiveTopology.QuadStrip => 3, // 2 triangles.
|
||||
_ => 1,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached or creates a new buffer that can be used to map linear indices to ones
|
||||
/// of a specified topology, and build complete primitives.
|
||||
/// </summary>
|
||||
/// <param name="topology">Topology</param>
|
||||
/// <param name="count">Number of input vertices that needs to be mapped using that buffer</param>
|
||||
/// <returns>Remap buffer range</returns>
|
||||
public BufferRange GetOrCreateTopologyRemapBuffer(PrimitiveTopology topology, int count)
|
||||
{
|
||||
if (!_topologyRemapBuffers.TryGetValue(topology, out IndexBuffer buffer) || buffer.Count < count)
|
||||
{
|
||||
if (buffer.Handle != BufferHandle.Null)
|
||||
{
|
||||
_context.Renderer.DeleteBuffer(buffer.Handle);
|
||||
}
|
||||
|
||||
buffer = CreateTopologyRemapBuffer(topology, count);
|
||||
_topologyRemapBuffers[topology] = buffer;
|
||||
|
||||
return buffer.ToRange();
|
||||
}
|
||||
|
||||
return buffer.ToRange(Math.Max(GetPrimitivesCount(topology, count) * GetVerticesPerPrimitive(topology), 1) * sizeof(uint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new topology remap buffer.
|
||||
/// </summary>
|
||||
/// <param name="topology">Topology</param>
|
||||
/// <param name="count">Maximum of vertices that will be accessed</param>
|
||||
/// <returns>Remap buffer range</returns>
|
||||
private IndexBuffer CreateTopologyRemapBuffer(PrimitiveTopology topology, int count)
|
||||
{
|
||||
// Size can't be zero as creating zero sized buffers is invalid.
|
||||
Span<int> data = new int[Math.Max(GetPrimitivesCount(topology, count) * GetVerticesPerPrimitive(topology), 1)];
|
||||
|
||||
switch (topology)
|
||||
{
|
||||
case PrimitiveTopology.Points:
|
||||
case PrimitiveTopology.Lines:
|
||||
case PrimitiveTopology.LinesAdjacency:
|
||||
case PrimitiveTopology.Triangles:
|
||||
case PrimitiveTopology.TrianglesAdjacency:
|
||||
case PrimitiveTopology.Patches:
|
||||
for (int index = 0; index < data.Length; index++)
|
||||
{
|
||||
data[index] = index;
|
||||
}
|
||||
break;
|
||||
case PrimitiveTopology.LineLoop:
|
||||
data[^1] = 0;
|
||||
|
||||
for (int index = 0; index < ((data.Length - 1) & ~1); index += 2)
|
||||
{
|
||||
data[index] = index >> 1;
|
||||
data[index + 1] = (index >> 1) + 1;
|
||||
}
|
||||
break;
|
||||
case PrimitiveTopology.LineStrip:
|
||||
for (int index = 0; index < ((data.Length - 1) & ~1); index += 2)
|
||||
{
|
||||
data[index] = index >> 1;
|
||||
data[index + 1] = (index >> 1) + 1;
|
||||
}
|
||||
break;
|
||||
case PrimitiveTopology.TriangleStrip:
|
||||
int tsTrianglesCount = data.Length / 3;
|
||||
int tsOutIndex = 3;
|
||||
|
||||
if (tsTrianglesCount > 0)
|
||||
{
|
||||
data[0] = 0;
|
||||
data[1] = 1;
|
||||
data[2] = 2;
|
||||
}
|
||||
|
||||
for (int tri = 1; tri < tsTrianglesCount; tri++)
|
||||
{
|
||||
int baseIndex = tri * 3;
|
||||
|
||||
if ((tri & 1) != 0)
|
||||
{
|
||||
data[baseIndex] = tsOutIndex - 1;
|
||||
data[baseIndex + 1] = tsOutIndex - 2;
|
||||
data[baseIndex + 2] = tsOutIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
data[baseIndex] = tsOutIndex - 2;
|
||||
data[baseIndex + 1] = tsOutIndex - 1;
|
||||
data[baseIndex + 2] = tsOutIndex++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PrimitiveTopology.TriangleFan:
|
||||
case PrimitiveTopology.Polygon:
|
||||
int tfTrianglesCount = data.Length / 3;
|
||||
int tfOutIndex = 1;
|
||||
|
||||
for (int index = 0; index < tfTrianglesCount * 3; index += 3)
|
||||
{
|
||||
data[index] = 0;
|
||||
data[index + 1] = tfOutIndex;
|
||||
data[index + 2] = ++tfOutIndex;
|
||||
}
|
||||
break;
|
||||
case PrimitiveTopology.Quads:
|
||||
int qQuadsCount = data.Length / 6;
|
||||
|
||||
for (int quad = 0; quad < qQuadsCount; quad++)
|
||||
{
|
||||
int index = quad * 6;
|
||||
int qIndex = quad * 4;
|
||||
|
||||
data[index] = qIndex;
|
||||
data[index + 1] = qIndex + 1;
|
||||
data[index + 2] = qIndex + 2;
|
||||
data[index + 3] = qIndex;
|
||||
data[index + 4] = qIndex + 2;
|
||||
data[index + 5] = qIndex + 3;
|
||||
}
|
||||
break;
|
||||
case PrimitiveTopology.QuadStrip:
|
||||
int qsQuadsCount = data.Length / 6;
|
||||
|
||||
if (qsQuadsCount > 0)
|
||||
{
|
||||
data[0] = 0;
|
||||
data[1] = 1;
|
||||
data[2] = 2;
|
||||
data[3] = 0;
|
||||
data[4] = 2;
|
||||
data[5] = 3;
|
||||
}
|
||||
|
||||
for (int quad = 1; quad < qsQuadsCount; quad++)
|
||||
{
|
||||
int index = quad * 6;
|
||||
int qIndex = quad * 2;
|
||||
|
||||
data[index] = qIndex + 1;
|
||||
data[index + 1] = qIndex;
|
||||
data[index + 2] = qIndex + 2;
|
||||
data[index + 3] = qIndex + 1;
|
||||
data[index + 4] = qIndex + 2;
|
||||
data[index + 5] = qIndex + 3;
|
||||
}
|
||||
break;
|
||||
case PrimitiveTopology.LineStripAdjacency:
|
||||
for (int index = 0; index < ((data.Length - 3) & ~3); index += 4)
|
||||
{
|
||||
int lIndex = index >> 2;
|
||||
|
||||
data[index] = lIndex;
|
||||
data[index + 1] = lIndex + 1;
|
||||
data[index + 2] = lIndex + 2;
|
||||
data[index + 3] = lIndex + 3;
|
||||
}
|
||||
break;
|
||||
case PrimitiveTopology.TriangleStripAdjacency:
|
||||
int tsaTrianglesCount = data.Length / 6;
|
||||
int tsaOutIndex = 6;
|
||||
|
||||
if (tsaTrianglesCount > 0)
|
||||
{
|
||||
data[0] = 0;
|
||||
data[1] = 1;
|
||||
data[2] = 2;
|
||||
data[3] = 3;
|
||||
data[4] = 4;
|
||||
data[5] = 5;
|
||||
}
|
||||
|
||||
for (int tri = 1; tri < tsaTrianglesCount; tri++)
|
||||
{
|
||||
int baseIndex = tri * 6;
|
||||
|
||||
if ((tri & 1) != 0)
|
||||
{
|
||||
data[baseIndex] = tsaOutIndex - 2;
|
||||
data[baseIndex + 1] = tsaOutIndex - 1;
|
||||
data[baseIndex + 2] = tsaOutIndex - 4;
|
||||
data[baseIndex + 3] = tsaOutIndex - 3;
|
||||
data[baseIndex + 4] = tsaOutIndex++;
|
||||
data[baseIndex + 5] = tsaOutIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
data[baseIndex] = tsaOutIndex - 4;
|
||||
data[baseIndex + 1] = tsaOutIndex - 3;
|
||||
data[baseIndex + 2] = tsaOutIndex - 2;
|
||||
data[baseIndex + 3] = tsaOutIndex - 1;
|
||||
data[baseIndex + 4] = tsaOutIndex++;
|
||||
data[baseIndex + 5] = tsaOutIndex++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> dataBytes = MemoryMarshal.Cast<int, byte>(data);
|
||||
|
||||
BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length);
|
||||
_context.Renderer.SetBufferData(buffer, 0, dataBytes);
|
||||
|
||||
return new IndexBuffer(buffer, count, dataBytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a buffer texture with a given format, for the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the buffer texture</param>
|
||||
/// <param name="format">Format of the buffer texture</param>
|
||||
/// <returns>Buffer texture</returns>
|
||||
public ITexture EnsureBufferTexture(int index, Format format)
|
||||
{
|
||||
return (_bufferTextures[index] ??= new()).Get(_context.Renderer, format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset and size of usable storage on the output vertex buffer.
|
||||
/// </summary>
|
||||
/// <param name="size">Size in bytes that will be used</param>
|
||||
/// <returns>Usable offset and size on the buffer</returns>
|
||||
public (int, int) GetVertexDataBuffer(int size)
|
||||
{
|
||||
return EnsureBuffer(ref _vertexDataBuffer, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset and size of usable storage on the output geometry shader vertex buffer.
|
||||
/// </summary>
|
||||
/// <param name="size">Size in bytes that will be used</param>
|
||||
/// <returns>Usable offset and size on the buffer</returns>
|
||||
public (int, int) GetGeometryVertexDataBuffer(int size)
|
||||
{
|
||||
return EnsureBuffer(ref _geometryVertexDataBuffer, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset and size of usable storage on the output geometry shader index buffer.
|
||||
/// </summary>
|
||||
/// <param name="size">Size in bytes that will be used</param>
|
||||
/// <returns>Usable offset and size on the buffer</returns>
|
||||
public (int, int) GetGeometryIndexDataBuffer(int size)
|
||||
{
|
||||
return EnsureBuffer(ref _geometryIndexDataBuffer, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a range of the output vertex buffer for binding.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <param name="write">Indicates if the buffer contents will be modified</param>
|
||||
/// <returns>Range</returns>
|
||||
public BufferRange GetVertexDataBufferRange(int offset, int size, bool write)
|
||||
{
|
||||
return new BufferRange(_vertexDataBuffer.Handle, offset, size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a range of the output geometry shader vertex buffer for binding.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <param name="write">Indicates if the buffer contents will be modified</param>
|
||||
/// <returns>Range</returns>
|
||||
public BufferRange GetGeometryVertexDataBufferRange(int offset, int size, bool write)
|
||||
{
|
||||
return new BufferRange(_geometryVertexDataBuffer.Handle, offset, size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a range of the output geometry shader index buffer for binding.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <param name="write">Indicates if the buffer contents will be modified</param>
|
||||
/// <returns>Range</returns>
|
||||
public BufferRange GetGeometryIndexDataBufferRange(int offset, int size, bool write)
|
||||
{
|
||||
return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the range for a dummy 16 bytes buffer, filled with zeros.
|
||||
/// </summary>
|
||||
/// <returns>Dummy buffer range</returns>
|
||||
public BufferRange GetDummyBufferRange()
|
||||
{
|
||||
if (_dummyBuffer == BufferHandle.Null)
|
||||
{
|
||||
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize);
|
||||
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
|
||||
}
|
||||
|
||||
return new BufferRange(_dummyBuffer, 0, DummyBufferSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the range for a sequential index buffer, with ever incrementing index values.
|
||||
/// </summary>
|
||||
/// <param name="count">Minimum number of indices that the buffer should have</param>
|
||||
/// <returns>Buffer handle</returns>
|
||||
public BufferHandle GetSequentialIndexBuffer(int count)
|
||||
{
|
||||
if (_sequentialIndexBufferCount < count)
|
||||
{
|
||||
if (_sequentialIndexBuffer != BufferHandle.Null)
|
||||
{
|
||||
_context.Renderer.DeleteBuffer(_sequentialIndexBuffer);
|
||||
}
|
||||
|
||||
_sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint));
|
||||
_sequentialIndexBufferCount = count;
|
||||
|
||||
Span<int> data = new int[count];
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
data[index] = index;
|
||||
}
|
||||
|
||||
_context.Renderer.SetBufferData(_sequentialIndexBuffer, 0, MemoryMarshal.Cast<int, byte>(data));
|
||||
}
|
||||
|
||||
return _sequentialIndexBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that a buffer exists, is large enough, and allocates a sub-region of the specified size inside the buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer state</param>
|
||||
/// <param name="size">Required size in bytes</param>
|
||||
/// <returns>Allocated offset and size</returns>
|
||||
private (int, int) EnsureBuffer(ref Buffer buffer, int size)
|
||||
{
|
||||
int newSize = buffer.Offset + size;
|
||||
|
||||
if (buffer.Size < newSize)
|
||||
{
|
||||
if (buffer.Handle != BufferHandle.Null)
|
||||
{
|
||||
_context.Renderer.DeleteBuffer(buffer.Handle);
|
||||
}
|
||||
|
||||
buffer.Handle = _context.Renderer.CreateBuffer(newSize);
|
||||
buffer.Size = newSize;
|
||||
}
|
||||
|
||||
int offset = buffer.Offset;
|
||||
|
||||
buffer.Offset = BitUtils.AlignUp(newSize, _context.Capabilities.StorageBufferOffsetAlignment);
|
||||
|
||||
return (offset, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all buffer sub-regions that were previously allocated.
|
||||
/// </summary>
|
||||
public void FreeBuffers()
|
||||
{
|
||||
_vertexDataBuffer.Offset = 0;
|
||||
_geometryVertexDataBuffer.Offset = 0;
|
||||
_geometryIndexDataBuffer.Offset = 0;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
for (int index = 0; index < _bufferTextures.Length; index++)
|
||||
{
|
||||
_bufferTextures[index]?.Dispose();
|
||||
_bufferTextures[index] = null;
|
||||
}
|
||||
|
||||
DestroyIfNotNull(ref _dummyBuffer);
|
||||
DestroyIfNotNull(ref _vertexDataBuffer.Handle);
|
||||
DestroyIfNotNull(ref _geometryVertexDataBuffer.Handle);
|
||||
DestroyIfNotNull(ref _geometryIndexDataBuffer.Handle);
|
||||
DestroyIfNotNull(ref _sequentialIndexBuffer);
|
||||
|
||||
foreach (var indexBuffer in _topologyRemapBuffers.Values)
|
||||
{
|
||||
_context.Renderer.DeleteBuffer(indexBuffer.Handle);
|
||||
}
|
||||
|
||||
_topologyRemapBuffers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a buffer if the handle is valid (not null), then sets the handle to null.
|
||||
/// </summary>
|
||||
/// <param name="handle">Buffer handle</param>
|
||||
private void DestroyIfNotNull(ref BufferHandle handle)
|
||||
{
|
||||
if (handle != BufferHandle.Null)
|
||||
{
|
||||
_context.Renderer.DeleteBuffer(handle);
|
||||
handle = BufferHandle.Null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,535 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex, tessellation and geometry as compute shader state.
|
||||
/// </summary>
|
||||
struct VtgAsComputeState
|
||||
{
|
||||
private const int ComputeLocalSize = 32;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
private readonly VtgAsComputeContext _vacContext;
|
||||
private readonly ThreedClass _engine;
|
||||
private readonly ShaderAsCompute _vertexAsCompute;
|
||||
private readonly ShaderAsCompute _geometryAsCompute;
|
||||
private readonly IProgram _vertexPassthroughProgram;
|
||||
private readonly PrimitiveTopology _topology;
|
||||
private readonly int _count;
|
||||
private readonly int _instanceCount;
|
||||
private readonly int _firstIndex;
|
||||
private readonly int _firstVertex;
|
||||
private readonly int _firstInstance;
|
||||
private readonly bool _indexed;
|
||||
|
||||
private readonly int _vertexDataOffset;
|
||||
private readonly int _vertexDataSize;
|
||||
private readonly int _geometryVertexDataOffset;
|
||||
private readonly int _geometryVertexDataSize;
|
||||
private readonly int _geometryIndexDataOffset;
|
||||
private readonly int _geometryIndexDataSize;
|
||||
private readonly int _geometryIndexDataCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new vertex, tessellation and geometry as compute shader state.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">3D engine state</param>
|
||||
/// <param name="vacContext">Vertex as compute context</param>
|
||||
/// <param name="engine">3D engine</param>
|
||||
/// <param name="vertexAsCompute">Vertex shader converted to compute</param>
|
||||
/// <param name="geometryAsCompute">Optional geometry shader converted to compute</param>
|
||||
/// <param name="vertexPassthroughProgram">Fragment shader with a vertex passthrough shader to feed the compute output into the fragment stage</param>
|
||||
/// <param name="topology">Primitive topology of the draw</param>
|
||||
/// <param name="count">Index or vertex count of the draw</param>
|
||||
/// <param name="instanceCount">Instance count</param>
|
||||
/// <param name="firstIndex">First index on the index buffer, for indexed draws</param>
|
||||
/// <param name="firstVertex">First vertex on the vertex buffer</param>
|
||||
/// <param name="firstInstance">First instance</param>
|
||||
/// <param name="indexed">Whether the draw is indexed</param>
|
||||
public VtgAsComputeState(
|
||||
GpuContext context,
|
||||
GpuChannel channel,
|
||||
DeviceStateWithShadow<ThreedClassState> state,
|
||||
VtgAsComputeContext vacContext,
|
||||
ThreedClass engine,
|
||||
ShaderAsCompute vertexAsCompute,
|
||||
ShaderAsCompute geometryAsCompute,
|
||||
IProgram vertexPassthroughProgram,
|
||||
PrimitiveTopology topology,
|
||||
int count,
|
||||
int instanceCount,
|
||||
int firstIndex,
|
||||
int firstVertex,
|
||||
int firstInstance,
|
||||
bool indexed)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_vacContext = vacContext;
|
||||
_engine = engine;
|
||||
_vertexAsCompute = vertexAsCompute;
|
||||
_geometryAsCompute = geometryAsCompute;
|
||||
_vertexPassthroughProgram = vertexPassthroughProgram;
|
||||
_topology = topology;
|
||||
_count = count;
|
||||
_instanceCount = instanceCount;
|
||||
_firstIndex = firstIndex;
|
||||
_firstVertex = firstVertex;
|
||||
_firstInstance = firstInstance;
|
||||
_indexed = indexed;
|
||||
|
||||
int vertexDataSize = vertexAsCompute.Reservations.OutputSizeInBytesPerInvocation * count * instanceCount;
|
||||
|
||||
(_vertexDataOffset, _vertexDataSize) = _vacContext.GetVertexDataBuffer(vertexDataSize);
|
||||
|
||||
if (geometryAsCompute != null)
|
||||
{
|
||||
int totalPrimitivesCount = VtgAsComputeContext.GetPrimitivesCount(topology, count * instanceCount);
|
||||
int maxCompleteStrips = GetMaxCompleteStrips(geometryAsCompute.Info.GeometryVerticesPerPrimitive, geometryAsCompute.Info.GeometryMaxOutputVertices);
|
||||
int totalVerticesCount = totalPrimitivesCount * geometryAsCompute.Info.GeometryMaxOutputVertices * geometryAsCompute.Info.ThreadsPerInputPrimitive;
|
||||
int geometryVbDataSize = totalVerticesCount * geometryAsCompute.Reservations.OutputSizeInBytesPerInvocation;
|
||||
int geometryIbDataCount = totalVerticesCount + totalPrimitivesCount * maxCompleteStrips;
|
||||
int geometryIbDataSize = geometryIbDataCount * sizeof(uint);
|
||||
|
||||
(_geometryVertexDataOffset, _geometryVertexDataSize) = vacContext.GetGeometryVertexDataBuffer(geometryVbDataSize);
|
||||
(_geometryIndexDataOffset, _geometryIndexDataSize) = vacContext.GetGeometryIndexDataBuffer(geometryIbDataSize);
|
||||
|
||||
_geometryIndexDataCount = geometryIbDataCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emulates the vertex stage using compute.
|
||||
/// </summary>
|
||||
public readonly void RunVertex()
|
||||
{
|
||||
_context.Renderer.Pipeline.SetProgram(_vertexAsCompute.HostProgram);
|
||||
|
||||
int primitivesCount = VtgAsComputeContext.GetPrimitivesCount(_topology, _count);
|
||||
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance);
|
||||
_vacContext.VertexInfoBufferUpdater.SetGeometryCounts(primitivesCount);
|
||||
|
||||
for (int index = 0; index < Constants.TotalVertexAttribs; index++)
|
||||
{
|
||||
var vertexAttrib = _state.State.VertexAttribState[index];
|
||||
|
||||
if (!FormatTable.TryGetSingleComponentAttribFormat(vertexAttrib.UnpackFormat(), out Format format, out int componentsCount))
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
||||
|
||||
format = vertexAttrib.UnpackType() switch
|
||||
{
|
||||
VertexAttribType.Sint => Format.R32Sint,
|
||||
VertexAttribType.Uint => Format.R32Uint,
|
||||
_ => Format.R32Float
|
||||
};
|
||||
|
||||
componentsCount = 4;
|
||||
}
|
||||
|
||||
if (vertexAttrib.UnpackIsConstant())
|
||||
{
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||
continue;
|
||||
}
|
||||
|
||||
int bufferIndex = vertexAttrib.UnpackBufferIndex();
|
||||
|
||||
GpuVa endAddress = _state.State.VertexBufferEndAddress[bufferIndex];
|
||||
var vertexBuffer = _state.State.VertexBufferState[bufferIndex];
|
||||
bool instanced = _state.State.VertexBufferInstanced[bufferIndex];
|
||||
|
||||
ulong address = vertexBuffer.Address.Pack();
|
||||
|
||||
if (!vertexBuffer.UnpackEnable() || !_channel.MemoryManager.IsMapped(address))
|
||||
{
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||
continue;
|
||||
}
|
||||
|
||||
int vbStride = vertexBuffer.UnpackStride();
|
||||
ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count);
|
||||
|
||||
ulong oldVbSize = vbSize;
|
||||
|
||||
ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset();
|
||||
int componentSize = format.GetScalarSize();
|
||||
|
||||
address += attributeOffset;
|
||||
|
||||
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
||||
|
||||
vbSize = Align(vbSize - attributeOffset + misalign, componentSize);
|
||||
|
||||
SetBufferTexture(_vertexAsCompute.Reservations, index, format, address - misalign, vbSize);
|
||||
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, vbStride / componentSize, componentsCount);
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, (int)misalign / componentSize, instanced ? vertexBuffer.Divisor : 0);
|
||||
}
|
||||
|
||||
if (_indexed)
|
||||
{
|
||||
SetIndexBufferTexture(_vertexAsCompute.Reservations, _firstIndex, _count, out int ibOffset);
|
||||
_vacContext.VertexInfoBufferUpdater.SetIndexBufferOffset(ibOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSequentialIndexBufferTexture(_vertexAsCompute.Reservations, _count);
|
||||
_vacContext.VertexInfoBufferUpdater.SetIndexBufferOffset(0);
|
||||
}
|
||||
|
||||
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
||||
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
||||
_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(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||
|
||||
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(
|
||||
BitUtils.DivRoundUp(_count, ComputeLocalSize),
|
||||
BitUtils.DivRoundUp(_instanceCount, ComputeLocalSize),
|
||||
1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emulates the geometry stage using compute, if it exists, otherwise does nothing.
|
||||
/// </summary>
|
||||
public readonly void RunGeometry()
|
||||
{
|
||||
if (_geometryAsCompute == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int primitivesCount = VtgAsComputeContext.GetPrimitivesCount(_topology, _count);
|
||||
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance);
|
||||
_vacContext.VertexInfoBufferUpdater.SetGeometryCounts(primitivesCount);
|
||||
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||
|
||||
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
||||
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
||||
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||
|
||||
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||
|
||||
// Wait until compute is done.
|
||||
// TODO: Batch compute and draw operations to avoid pipeline stalls.
|
||||
_context.Renderer.Pipeline.Barrier();
|
||||
_context.Renderer.Pipeline.SetProgram(_geometryAsCompute.HostProgram);
|
||||
|
||||
SetTopologyRemapBufferTexture(_geometryAsCompute.Reservations, _topology, _count);
|
||||
|
||||
int geometryVbBinding = _geometryAsCompute.Reservations.GeometryVertexOutputStorageBufferBinding;
|
||||
int geometryIbBinding = _geometryAsCompute.Reservations.GeometryIndexOutputStorageBufferBinding;
|
||||
|
||||
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false);
|
||||
BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: true);
|
||||
BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: true);
|
||||
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[]
|
||||
{
|
||||
new BufferAssignment(vertexDataBinding, vertexDataRange),
|
||||
new BufferAssignment(geometryVbBinding, vertexBuffer),
|
||||
new BufferAssignment(geometryIbBinding, indexBuffer),
|
||||
});
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(
|
||||
BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize),
|
||||
BitUtils.DivRoundUp(_instanceCount, ComputeLocalSize),
|
||||
_geometryAsCompute.Info.ThreadsPerInputPrimitive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a draw using the data produced on the vertex, tessellation and geometry stages,
|
||||
/// if rasterizer discard is disabled.
|
||||
/// </summary>
|
||||
public readonly void RunFragment()
|
||||
{
|
||||
bool tfEnabled = _state.State.TfEnable;
|
||||
|
||||
if (!_state.State.RasterizeEnable && (!tfEnabled || !_context.Capabilities.SupportsTransformFeedback))
|
||||
{
|
||||
// No need to run fragment if rasterizer discard is enabled,
|
||||
// and we are emulating transform feedback or transform feedback is disabled.
|
||||
|
||||
// Note: We might skip geometry shader here, but right now, this is fine,
|
||||
// because the only cases that triggers VTG to compute are geometry shader
|
||||
// being not supported, or the vertex pipeline doing store operations.
|
||||
// If the geometry shader does not do any store and rasterizer discard is enabled, the geometry shader can be skipped.
|
||||
// If the geometry shader does have stores, it would have been converted to compute too if stores are not supported.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||
|
||||
_context.Renderer.Pipeline.Barrier();
|
||||
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance);
|
||||
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||
|
||||
if (_geometryAsCompute != null)
|
||||
{
|
||||
BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: false);
|
||||
BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: false);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||
_context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexBuffer) });
|
||||
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(true, -1);
|
||||
_context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive));
|
||||
|
||||
_context.Renderer.Pipeline.DrawIndexed(_geometryIndexDataCount, 1, 0, 0, 0);
|
||||
|
||||
_engine.ForceStateDirtyByIndex(StateUpdater.IndexBufferStateIndex);
|
||||
_engine.ForceStateDirtyByIndex(StateUpdater.PrimitiveRestartStateIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||
_context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a strip primitive topology from the vertices per primitive count.
|
||||
/// </summary>
|
||||
/// <param name="verticesPerPrimitive">Vertices per primitive count</param>
|
||||
/// <returns>Primitive topology</returns>
|
||||
private static PrimitiveTopology GetGeometryOutputTopology(int verticesPerPrimitive)
|
||||
{
|
||||
return verticesPerPrimitive switch
|
||||
{
|
||||
3 => PrimitiveTopology.TriangleStrip,
|
||||
2 => PrimitiveTopology.LineStrip,
|
||||
_ => PrimitiveTopology.Points,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum number of complete primitive strips for a vertex count.
|
||||
/// </summary>
|
||||
/// <param name="verticesPerPrimitive">Vertices per primitive count</param>
|
||||
/// <param name="maxOutputVertices">Maximum geometry shader output vertices count</param>
|
||||
/// <returns>Maximum number of complete primitive strips</returns>
|
||||
private static int GetMaxCompleteStrips(int verticesPerPrimitive, int maxOutputVertices)
|
||||
{
|
||||
return maxOutputVertices / verticesPerPrimitive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a dummy buffer as vertex buffer into a buffer texture.
|
||||
/// </summary>
|
||||
/// <param name="reservations">Shader resource binding reservations</param>
|
||||
/// <param name="index">Buffer texture index</param>
|
||||
/// <param name="format">Buffer texture format</param>
|
||||
private readonly void SetDummyBufferTexture(ResourceReservations reservations, int index, Format format)
|
||||
{
|
||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||
bufferTexture.SetStorage(_vacContext.GetDummyBufferRange());
|
||||
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a vertex buffer into a buffer texture.
|
||||
/// </summary>
|
||||
/// <param name="reservations">Shader resource binding reservations</param>
|
||||
/// <param name="index">Buffer texture index</param>
|
||||
/// <param name="format">Buffer texture format</param>
|
||||
/// <param name="address">Address of the vertex buffer</param>
|
||||
/// <param name="size">Size of the buffer in bytes</param>
|
||||
private readonly void SetBufferTexture(ResourceReservations reservations, int index, Format format, ulong address, ulong size)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
address = memoryManager.Translate(address);
|
||||
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(address, size);
|
||||
|
||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||
bufferTexture.SetStorage(range);
|
||||
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds the index buffer into a buffer texture.
|
||||
/// </summary>
|
||||
/// <param name="reservations">Shader resource binding reservations</param>
|
||||
/// <param name="firstIndex">First index of the index buffer</param>
|
||||
/// <param name="count">Index count</param>
|
||||
/// <param name="misalignedOffset">Offset that should be added when accessing the buffer texture on the shader</param>
|
||||
private readonly void SetIndexBufferTexture(ResourceReservations reservations, int firstIndex, int count, out int misalignedOffset)
|
||||
{
|
||||
ulong address = _state.State.IndexBufferState.Address.Pack();
|
||||
ulong indexOffset = (ulong)firstIndex;
|
||||
ulong size = (ulong)count;
|
||||
|
||||
int shift = 0;
|
||||
Format format = Format.R8Uint;
|
||||
|
||||
switch (_state.State.IndexBufferState.Type)
|
||||
{
|
||||
case IndexType.UShort:
|
||||
shift = 1;
|
||||
format = Format.R16Uint;
|
||||
break;
|
||||
case IndexType.UInt:
|
||||
shift = 2;
|
||||
format = Format.R32Uint;
|
||||
break;
|
||||
}
|
||||
|
||||
indexOffset <<= shift;
|
||||
size <<= shift;
|
||||
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
address = memoryManager.Translate(address + indexOffset);
|
||||
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
||||
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(address - misalign, size + misalign);
|
||||
misalignedOffset = (int)misalign >> shift;
|
||||
|
||||
SetIndexBufferTexture(reservations, range, format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the host buffer texture for the index buffer.
|
||||
/// </summary>
|
||||
/// <param name="reservations">Shader resource binding reservations</param>
|
||||
/// <param name="range">Index buffer range</param>
|
||||
/// <param name="format">Index buffer format</param>
|
||||
private readonly void SetIndexBufferTexture(ResourceReservations reservations, BufferRange range, Format format)
|
||||
{
|
||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(0, format);
|
||||
bufferTexture.SetStorage(range);
|
||||
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.IndexBufferTextureBinding, bufferTexture, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the host buffer texture for the topology remap buffer.
|
||||
/// </summary>
|
||||
/// <param name="reservations">Shader resource binding reservations</param>
|
||||
/// <param name="topology">Input topology</param>
|
||||
/// <param name="count">Input vertex count</param>
|
||||
private readonly void SetTopologyRemapBufferTexture(ResourceReservations reservations, PrimitiveTopology topology, int count)
|
||||
{
|
||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(1, Format.R32Uint);
|
||||
bufferTexture.SetStorage(_vacContext.GetOrCreateTopologyRemapBuffer(topology, count));
|
||||
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.TopologyRemapBufferTextureBinding, bufferTexture, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the host buffer texture to a generated sequential index buffer.
|
||||
/// </summary>
|
||||
/// <param name="reservations">Shader resource binding reservations</param>
|
||||
/// <param name="count">Vertex count</param>
|
||||
private readonly void SetSequentialIndexBufferTexture(ResourceReservations reservations, int count)
|
||||
{
|
||||
BufferHandle sequentialIndexBuffer = _vacContext.GetSequentialIndexBuffer(count);
|
||||
|
||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(0, Format.R32Uint);
|
||||
bufferTexture.SetStorage(new BufferRange(sequentialIndexBuffer, 0, count * sizeof(uint)));
|
||||
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.IndexBufferTextureBinding, bufferTexture, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of a vertex buffer based on the current 3D engine state.
|
||||
/// </summary>
|
||||
/// <param name="vbAddress">Vertex buffer address</param>
|
||||
/// <param name="vbEndAddress">Vertex buffer end address (exclusive)</param>
|
||||
/// <param name="vbStride">Vertex buffer stride</param>
|
||||
/// <param name="indexed">Whether the draw is indexed</param>
|
||||
/// <param name="instanced">Whether the draw is instanced</param>
|
||||
/// <param name="firstVertex">First vertex index</param>
|
||||
/// <param name="vertexCount">Vertex count</param>
|
||||
/// <returns>Size of the vertex buffer, in bytes</returns>
|
||||
private readonly ulong GetVertexBufferSize(ulong vbAddress, ulong vbEndAddress, int vbStride, bool indexed, bool instanced, int firstVertex, int vertexCount)
|
||||
{
|
||||
IndexType indexType = _state.State.IndexBufferState.Type;
|
||||
bool indexTypeSmall = indexType == IndexType.UByte || indexType == IndexType.UShort;
|
||||
ulong vbSize = vbEndAddress - vbAddress + 1;
|
||||
ulong size;
|
||||
|
||||
if (indexed || vbStride == 0 || instanced)
|
||||
{
|
||||
// This size may be (much) larger than the real vertex buffer size.
|
||||
// Avoid calculating it this way, unless we don't have any other option.
|
||||
|
||||
size = vbSize;
|
||||
|
||||
if (vbStride > 0 && indexTypeSmall && indexed && !instanced)
|
||||
{
|
||||
// If the index type is a small integer type, then we might be still able
|
||||
// to reduce the vertex buffer size based on the maximum possible index value.
|
||||
|
||||
ulong maxVertexBufferSize = indexType == IndexType.UByte ? 0x100UL : 0x10000UL;
|
||||
|
||||
maxVertexBufferSize += _state.State.FirstVertex;
|
||||
maxVertexBufferSize *= (uint)vbStride;
|
||||
|
||||
size = Math.Min(size, maxVertexBufferSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-indexed draws, we can guess the size from the vertex count
|
||||
// and stride.
|
||||
|
||||
int firstInstance = (int)_state.State.FirstInstance;
|
||||
|
||||
size = Math.Min(vbSize, (ulong)((firstInstance + firstVertex + vertexCount) * vbStride));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aligns a size to a given alignment value.
|
||||
/// </summary>
|
||||
/// <param name="size">Size</param>
|
||||
/// <param name="alignment">Alignment</param>
|
||||
/// <returns>Aligned size</returns>
|
||||
private static ulong Align(ulong size, int alignment)
|
||||
{
|
||||
ulong align = (ulong)alignment;
|
||||
|
||||
size += align - 1;
|
||||
|
||||
size /= align;
|
||||
size *= align;
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
@ -8,7 +9,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <summary>
|
||||
/// Draw manager.
|
||||
/// </summary>
|
||||
class DrawManager
|
||||
class DrawManager : IDisposable
|
||||
{
|
||||
// Since we don't know the index buffer size for indirect draws,
|
||||
// we must assume a minimum and maximum size and use that for buffer data update purposes.
|
||||
@ -20,6 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
private readonly DrawState _drawState;
|
||||
private readonly SpecializationStateUpdater _currentSpecState;
|
||||
private readonly VtgAsCompute _vtgAsCompute;
|
||||
private bool _topologySet;
|
||||
|
||||
private bool _instancedDrawPending;
|
||||
@ -53,6 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_state = state;
|
||||
_drawState = drawState;
|
||||
_currentSpecState = spec;
|
||||
_vtgAsCompute = new(context, channel, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -127,7 +130,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
if (renderEnable == ConditionalRenderEnabled.False)
|
||||
{
|
||||
PerformDeferredDraws();
|
||||
PerformDeferredDraws(engine);
|
||||
}
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
@ -190,13 +193,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||
|
||||
_context.Renderer.Pipeline.DrawIndexed(inlineIndexCount, 1, firstIndex, firstVertex, firstInstance);
|
||||
DrawImpl(engine, inlineIndexCount, 1, firstIndex, firstVertex, firstInstance, indexed: true);
|
||||
}
|
||||
else if (_drawState.DrawIndexed)
|
||||
{
|
||||
int firstVertex = (int)_state.State.FirstVertex;
|
||||
|
||||
_context.Renderer.Pipeline.DrawIndexed(indexCount, 1, firstIndex, firstVertex, firstInstance);
|
||||
DrawImpl(engine, indexCount, 1, firstIndex, firstVertex, firstInstance, indexed: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -204,7 +207,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
var drawState = _state.State.VertexBufferDrawState;
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
_context.Renderer.Pipeline.Draw(drawVertexCount, 1, drawFirstVertex, firstInstance);
|
||||
DrawImpl(engine, drawVertexCount, 1, 0, drawFirstVertex, firstInstance, indexed: false);
|
||||
}
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
@ -219,24 +222,26 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// Starts draw.
|
||||
/// This sets primitive type and instanced draw parameters.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawBegin(int argument)
|
||||
public void DrawBegin(ThreedClass engine, int argument)
|
||||
{
|
||||
bool incrementInstance = (argument & (1 << 26)) != 0;
|
||||
bool resetInstance = (argument & (1 << 27)) == 0;
|
||||
|
||||
PrimitiveType type = (PrimitiveType)(argument & 0xffff);
|
||||
DrawBegin(incrementInstance, resetInstance, type);
|
||||
DrawBegin(engine, incrementInstance, resetInstance, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts draw.
|
||||
/// This sets primitive type and instanced draw parameters.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="incrementInstance">Indicates if the current instance should be incremented</param>
|
||||
/// <param name="resetInstance">Indicates if the current instance should be set to zero</param>
|
||||
/// <param name="primitiveType">Primitive type</param>
|
||||
private void DrawBegin(bool incrementInstance, bool resetInstance, PrimitiveType primitiveType)
|
||||
private void DrawBegin(ThreedClass engine, bool incrementInstance, bool resetInstance, PrimitiveType primitiveType)
|
||||
{
|
||||
if (incrementInstance)
|
||||
{
|
||||
@ -244,7 +249,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
else if (resetInstance)
|
||||
{
|
||||
PerformDeferredDraws();
|
||||
PerformDeferredDraws(engine);
|
||||
|
||||
_instanceIndex = 0;
|
||||
}
|
||||
@ -364,7 +369,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <param name="instanced">True to increment the current instance value, false otherwise</param>
|
||||
private void DrawIndexBufferBeginEndInstance(ThreedClass engine, int argument, bool instanced)
|
||||
{
|
||||
DrawBegin(instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
||||
DrawBegin(engine, instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
||||
|
||||
int firstIndex = argument & 0xffff;
|
||||
int indexCount = (argument >> 16) & 0xfff;
|
||||
@ -409,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <param name="instanced">True to increment the current instance value, false otherwise</param>
|
||||
private void DrawVertexArrayBeginEndInstance(ThreedClass engine, int argument, bool instanced)
|
||||
{
|
||||
DrawBegin(instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
||||
DrawBegin(engine, instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
||||
|
||||
int firstVertex = argument & 0xffff;
|
||||
int vertexCount = (argument >> 16) & 0xfff;
|
||||
@ -541,23 +546,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
engine.UpdateState();
|
||||
|
||||
if (instanceCount > 1)
|
||||
{
|
||||
// Must be called after UpdateState as it assumes the shader state
|
||||
// has already been set, and that bindings have been updated already.
|
||||
|
||||
_channel.BufferManager.SetInstancedDrawVertexCount(count);
|
||||
}
|
||||
DrawImpl(engine, count, instanceCount, firstIndex, firstVertex, firstInstance, indexed);
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
_context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance);
|
||||
_state.State.FirstVertex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.Draw(count, instanceCount, firstVertex, firstInstance);
|
||||
}
|
||||
|
||||
_state.State.FirstInstance = 0;
|
||||
|
||||
@ -569,6 +563,67 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed or non-indexed draw.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="count">Index count for indexed draws, vertex count for non-indexed draws</param>
|
||||
/// <param name="instanceCount">Instance count</param>
|
||||
/// <param name="firstIndex">First index on the index buffer for indexed draws, ignored for non-indexed draws</param>
|
||||
/// <param name="firstVertex">First vertex on the vertex buffer</param>
|
||||
/// <param name="firstInstance">First instance</param>
|
||||
/// <param name="indexed">True if the draw is indexed, false otherwise</param>
|
||||
private void DrawImpl(
|
||||
ThreedClass engine,
|
||||
int count,
|
||||
int instanceCount,
|
||||
int firstIndex,
|
||||
int firstVertex,
|
||||
int firstInstance,
|
||||
bool indexed)
|
||||
{
|
||||
if (instanceCount > 1)
|
||||
{
|
||||
_channel.BufferManager.SetInstancedDrawVertexCount(count);
|
||||
}
|
||||
|
||||
if (_drawState.VertexAsCompute != null)
|
||||
{
|
||||
_vtgAsCompute.DrawAsCompute(
|
||||
engine,
|
||||
_drawState.VertexAsCompute,
|
||||
_drawState.GeometryAsCompute,
|
||||
_drawState.VertexPassthrough,
|
||||
_drawState.Topology,
|
||||
count,
|
||||
instanceCount,
|
||||
firstIndex,
|
||||
firstVertex,
|
||||
firstInstance,
|
||||
indexed);
|
||||
|
||||
if (_drawState.GeometryAsCompute != null)
|
||||
{
|
||||
// Geometry draws need to change the topology, so we need to set it here again
|
||||
// if we are going to do a regular draw.
|
||||
// Would have been better to do that on the callee, but doing it here
|
||||
// avoids having to pass the draw manager instance.
|
||||
ForceStateDirty();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (indexed)
|
||||
{
|
||||
_context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.Draw(count, instanceCount, firstVertex, firstInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indirect draw, with parameters from a GPU buffer.
|
||||
/// </summary>
|
||||
@ -667,43 +722,42 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// Once we detect the last instanced draw, then we perform the host instanced draw,
|
||||
/// with the accumulated instance count.
|
||||
/// </summary>
|
||||
public void PerformDeferredDraws()
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
public void PerformDeferredDraws(ThreedClass engine)
|
||||
{
|
||||
// Perform any pending instanced draw.
|
||||
if (_instancedDrawPending)
|
||||
{
|
||||
_instancedDrawPending = false;
|
||||
|
||||
int instanceCount = _instanceIndex + 1;
|
||||
int firstInstance = _instancedFirstInstance;
|
||||
bool indexedInline = _instancedIndexedInline;
|
||||
|
||||
if (_instancedIndexed || indexedInline)
|
||||
{
|
||||
int indexCount = _instancedIndexCount;
|
||||
|
||||
if (indexedInline)
|
||||
{
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||
BufferRange br = new(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
||||
|
||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||
indexCount = inlineIndexCount;
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetInstancedDrawVertexCount(_instancedIndexCount);
|
||||
int firstIndex = _instancedFirstIndex;
|
||||
int firstVertex = _instancedFirstVertex;
|
||||
|
||||
_context.Renderer.Pipeline.DrawIndexed(
|
||||
_instancedIndexCount,
|
||||
_instanceIndex + 1,
|
||||
_instancedFirstIndex,
|
||||
_instancedFirstVertex,
|
||||
_instancedFirstInstance);
|
||||
DrawImpl(engine, indexCount, instanceCount, firstIndex, firstVertex, firstInstance, indexed: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_channel.BufferManager.SetInstancedDrawVertexCount(_instancedDrawStateCount);
|
||||
int vertexCount = _instancedDrawStateCount;
|
||||
int firstVertex = _instancedDrawStateFirst;
|
||||
|
||||
_context.Renderer.Pipeline.Draw(
|
||||
_instancedDrawStateCount,
|
||||
_instanceIndex + 1,
|
||||
_instancedDrawStateFirst,
|
||||
_instancedFirstInstance);
|
||||
DrawImpl(engine, vertexCount, instanceCount, 0, firstVertex, firstInstance, indexed: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -866,5 +920,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_vtgAsCompute.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
@ -61,5 +62,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// Index buffer data streamer for inline index buffer updates, such as those used in legacy OpenGL.
|
||||
/// </summary>
|
||||
public IbStreamer IbStreamer = new();
|
||||
|
||||
/// <summary>
|
||||
/// If the vertex shader is emulated on compute, this should be set to the compute program, otherwise it should be null.
|
||||
/// </summary>
|
||||
public ShaderAsCompute VertexAsCompute;
|
||||
|
||||
/// <summary>
|
||||
/// If a geometry shader exists and is emulated on compute, this should be set to the compute program, otherwise it should be null.
|
||||
/// </summary>
|
||||
public ShaderAsCompute GeometryAsCompute;
|
||||
|
||||
/// <summary>
|
||||
/// If the vertex shader is emulated on compute, this should be set to the passthrough vertex program, otherwise it should be null.
|
||||
/// </summary>
|
||||
public IProgram VertexPassthrough;
|
||||
}
|
||||
}
|
||||
|
@ -218,11 +218,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
bool changed = false;
|
||||
ref Array32<AttributeType> attributeTypes = ref _graphics.AttributeTypes;
|
||||
bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats;
|
||||
bool mayConvertVtgToCompute = ShaderCache.MayConvertVtgToCompute(ref _context.Capabilities);
|
||||
bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats && !mayConvertVtgToCompute;
|
||||
|
||||
for (int location = 0; location < state.Length; location++)
|
||||
{
|
||||
VertexAttribType type = state[location].UnpackType();
|
||||
VertexAttribSize size = state[location].UnpackSize();
|
||||
|
||||
AttributeType value;
|
||||
|
||||
@ -247,6 +249,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
};
|
||||
}
|
||||
|
||||
if (mayConvertVtgToCompute && (size == VertexAttribSize.Rgb10A2 || size == VertexAttribSize.Rg11B10))
|
||||
{
|
||||
value |= AttributeType.Packed;
|
||||
|
||||
if (type == VertexAttribType.Snorm ||
|
||||
type == VertexAttribType.Sint ||
|
||||
type == VertexAttribType.Sscaled)
|
||||
{
|
||||
value |= AttributeType.PackedRgb10A2Signed;
|
||||
}
|
||||
}
|
||||
|
||||
if (attributeTypes[location] != value)
|
||||
{
|
||||
attributeTypes[location] = value;
|
||||
|
@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public const int RasterizerStateIndex = 15;
|
||||
public const int ScissorStateIndex = 16;
|
||||
public const int VertexBufferStateIndex = 0;
|
||||
public const int IndexBufferStateIndex = 23;
|
||||
public const int PrimitiveRestartStateIndex = 12;
|
||||
public const int RenderTargetStateIndex = 27;
|
||||
|
||||
@ -290,7 +291,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
// of the shader for the new state.
|
||||
if (_shaderSpecState != null && _currentSpecState.HasChanged())
|
||||
{
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), _vsUsesDrawParameters, false))
|
||||
if (!_shaderSpecState.MatchesGraphics(
|
||||
_channel,
|
||||
ref _currentSpecState.GetPoolState(),
|
||||
ref _currentSpecState.GetGraphicsState(),
|
||||
_drawState.VertexAsCompute != null,
|
||||
_vsUsesDrawParameters,
|
||||
checkTextures: false))
|
||||
{
|
||||
// Shader must be reloaded. _vtgWritesRtLayer should not change.
|
||||
UpdateShaderState();
|
||||
@ -1453,6 +1460,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_fsReadsFragCoord = false;
|
||||
}
|
||||
|
||||
if (gs.VertexAsCompute != null)
|
||||
{
|
||||
_drawState.VertexAsCompute = gs.VertexAsCompute;
|
||||
_drawState.GeometryAsCompute = gs.GeometryAsCompute;
|
||||
_drawState.VertexPassthrough = gs.HostProgram;
|
||||
}
|
||||
else
|
||||
{
|
||||
_drawState.VertexAsCompute = null;
|
||||
_drawState.GeometryAsCompute = null;
|
||||
_drawState.VertexPassthrough = null;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(gs.HostProgram);
|
||||
}
|
||||
|
||||
@ -1540,5 +1560,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
_updateTracker.ForceDirty(ShaderStateIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces a register group as dirty, by index.
|
||||
/// </summary>
|
||||
/// <param name="groupIndex">Index of the group to be dirtied</param>
|
||||
public void ForceDirty(int groupIndex)
|
||||
{
|
||||
_updateTracker.ForceDirty(groupIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <summary>
|
||||
/// Represents a 3D engine class.
|
||||
/// </summary>
|
||||
class ThreedClass : IDeviceState
|
||||
class ThreedClass : IDeviceState, IDisposable
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GPFifoClass _fifoClass;
|
||||
@ -178,6 +178,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_stateUpdater.SetDirty(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the specified register range for a group index as dirty, forcing the associated state to update on the next draw.
|
||||
/// </summary>
|
||||
/// <param name="groupIndex">Index of the group to dirty</param>
|
||||
public void ForceStateDirtyByIndex(int groupIndex)
|
||||
{
|
||||
_stateUpdater.ForceDirty(groupIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the shaders to be rebound on the next draw.
|
||||
/// </summary>
|
||||
@ -207,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
public void PerformDeferredDraws()
|
||||
{
|
||||
_drawManager.PerformDeferredDraws();
|
||||
_drawManager.PerformDeferredDraws(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -402,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawBegin(int argument)
|
||||
{
|
||||
_drawManager.DrawBegin(argument);
|
||||
_drawManager.DrawBegin(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -617,5 +626,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
_drawManager.Clear(this, argument, layerCount);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_drawManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +135,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
private void Destroy()
|
||||
{
|
||||
_processor.Dispose();
|
||||
TextureManager.Dispose();
|
||||
|
||||
var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
|
||||
|
@ -557,6 +557,91 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
};
|
||||
#pragma warning restore IDE0055
|
||||
|
||||
// Note: Some of those formats have been changed and requires conversion on the shader,
|
||||
// as GPUs don't support them when used as buffer texture format.
|
||||
private static readonly Dictionary<VertexAttributeFormat, (Format, int)> _singleComponentAttribFormats = new()
|
||||
{
|
||||
{ VertexAttributeFormat.R8Unorm, (Format.R8Unorm, 1) },
|
||||
{ VertexAttributeFormat.R8Snorm, (Format.R8Snorm, 1) },
|
||||
{ VertexAttributeFormat.R8Uint, (Format.R8Uint, 1) },
|
||||
{ VertexAttributeFormat.R8Sint, (Format.R8Sint, 1) },
|
||||
{ VertexAttributeFormat.R16Float, (Format.R16Float, 1) },
|
||||
{ VertexAttributeFormat.R16Unorm, (Format.R16Unorm, 1) },
|
||||
{ VertexAttributeFormat.R16Snorm, (Format.R16Snorm, 1) },
|
||||
{ VertexAttributeFormat.R16Uint, (Format.R16Uint, 1) },
|
||||
{ VertexAttributeFormat.R16Sint, (Format.R16Sint, 1) },
|
||||
{ VertexAttributeFormat.R32Float, (Format.R32Float, 1) },
|
||||
{ VertexAttributeFormat.R32Uint, (Format.R32Uint, 1) },
|
||||
{ VertexAttributeFormat.R32Sint, (Format.R32Sint, 1) },
|
||||
{ VertexAttributeFormat.R8G8Unorm, (Format.R8Unorm, 2) },
|
||||
{ VertexAttributeFormat.R8G8Snorm, (Format.R8Snorm, 2) },
|
||||
{ VertexAttributeFormat.R8G8Uint, (Format.R8Uint, 2) },
|
||||
{ VertexAttributeFormat.R8G8Sint, (Format.R8Sint, 2) },
|
||||
{ VertexAttributeFormat.R16G16Float, (Format.R16Float, 2) },
|
||||
{ VertexAttributeFormat.R16G16Unorm, (Format.R16Unorm, 2) },
|
||||
{ VertexAttributeFormat.R16G16Snorm, (Format.R16Snorm, 2) },
|
||||
{ VertexAttributeFormat.R16G16Uint, (Format.R16Uint, 2) },
|
||||
{ VertexAttributeFormat.R16G16Sint, (Format.R16Sint, 2) },
|
||||
{ VertexAttributeFormat.R32G32Float, (Format.R32Float, 2) },
|
||||
{ VertexAttributeFormat.R32G32Uint, (Format.R32Uint, 2) },
|
||||
{ VertexAttributeFormat.R32G32Sint, (Format.R32Sint, 2) },
|
||||
{ VertexAttributeFormat.R8G8B8Unorm, (Format.R8Unorm, 3) },
|
||||
{ VertexAttributeFormat.R8G8B8Snorm, (Format.R8Snorm, 3) },
|
||||
{ VertexAttributeFormat.R8G8B8Uint, (Format.R8Uint, 3) },
|
||||
{ VertexAttributeFormat.R8G8B8Sint, (Format.R8Sint, 3) },
|
||||
{ VertexAttributeFormat.R16G16B16Float, (Format.R16Float, 3) },
|
||||
{ VertexAttributeFormat.R16G16B16Unorm, (Format.R16Unorm, 3) },
|
||||
{ VertexAttributeFormat.R16G16B16Snorm, (Format.R16Snorm, 3) },
|
||||
{ VertexAttributeFormat.R16G16B16Uint, (Format.R16Uint, 3) },
|
||||
{ VertexAttributeFormat.R16G16B16Sint, (Format.R16Sint, 3) },
|
||||
{ VertexAttributeFormat.R32G32B32Float, (Format.R32Float, 3) },
|
||||
{ VertexAttributeFormat.R32G32B32Uint, (Format.R32Uint, 3) },
|
||||
{ VertexAttributeFormat.R32G32B32Sint, (Format.R32Sint, 3) },
|
||||
{ VertexAttributeFormat.R8G8B8A8Unorm, (Format.R8Unorm, 4) },
|
||||
{ VertexAttributeFormat.R8G8B8A8Snorm, (Format.R8Snorm, 4) },
|
||||
{ VertexAttributeFormat.R8G8B8A8Uint, (Format.R8Uint, 4) },
|
||||
{ VertexAttributeFormat.R8G8B8A8Sint, (Format.R8Sint, 4) },
|
||||
{ VertexAttributeFormat.R16G16B16A16Float, (Format.R16Float, 4) },
|
||||
{ VertexAttributeFormat.R16G16B16A16Unorm, (Format.R16Unorm, 4) },
|
||||
{ VertexAttributeFormat.R16G16B16A16Snorm, (Format.R16Snorm, 4) },
|
||||
{ VertexAttributeFormat.R16G16B16A16Uint, (Format.R16Uint, 4) },
|
||||
{ VertexAttributeFormat.R16G16B16A16Sint, (Format.R16Sint, 4) },
|
||||
{ VertexAttributeFormat.R32G32B32A32Float, (Format.R32Float, 4) },
|
||||
{ VertexAttributeFormat.R32G32B32A32Uint, (Format.R32Uint, 4) },
|
||||
{ VertexAttributeFormat.R32G32B32A32Sint, (Format.R32Sint, 4) },
|
||||
{ VertexAttributeFormat.A2B10G10R10Unorm, (Format.R10G10B10A2Unorm, 4) },
|
||||
{ VertexAttributeFormat.A2B10G10R10Uint, (Format.R10G10B10A2Uint, 4) },
|
||||
{ VertexAttributeFormat.B10G11R11Float, (Format.R11G11B10Float, 3) },
|
||||
{ VertexAttributeFormat.R8Uscaled, (Format.R8Uint, 1) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R8Sscaled, (Format.R8Sint, 1) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R16Uscaled, (Format.R16Uint, 1) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R16Sscaled, (Format.R16Sint, 1) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R32Uscaled, (Format.R32Uint, 1) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R32Sscaled, (Format.R32Sint, 1) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R8G8Uscaled, (Format.R8Uint, 2) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R8G8Sscaled, (Format.R8Sint, 2) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R16G16Uscaled, (Format.R16Uint, 2) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R16G16Sscaled, (Format.R16Sint, 2) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R32G32Uscaled, (Format.R32Uint, 2) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R32G32Sscaled, (Format.R32Sint, 2) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R8G8B8Uscaled, (Format.R8Uint, 3) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R8G8B8Sscaled, (Format.R8Sint, 3) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R16G16B16Uscaled, (Format.R16Uint, 3) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R16G16B16Sscaled, (Format.R16Sint, 3) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R32G32B32Uscaled, (Format.R32Uint, 3) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R32G32B32Sscaled, (Format.R32Sint , 3) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R8G8B8A8Uscaled, (Format.R8Uint, 4) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R8G8B8A8Sscaled, (Format.R8Sint, 4) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R16G16B16A16Uscaled, (Format.R16Uint, 4) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R16G16B16A16Sscaled, (Format.R16Sint, 4) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.R32G32B32A32Uscaled, (Format.R32Uint, 4) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.R32G32B32A32Sscaled, (Format.R32Sint, 4) }, // Sscaled -> Sint
|
||||
{ VertexAttributeFormat.A2B10G10R10Snorm, (Format.R10G10B10A2Uint, 4) }, // Snorm -> Uint
|
||||
{ VertexAttributeFormat.A2B10G10R10Sint, (Format.R10G10B10A2Uint, 4) }, // Sint -> Uint
|
||||
{ VertexAttributeFormat.A2B10G10R10Uscaled, (Format.R10G10B10A2Uint, 4) }, // Uscaled -> Uint
|
||||
{ VertexAttributeFormat.A2B10G10R10Sscaled, (Format.R10G10B10A2Sint, 4) } // Sscaled -> Sint
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor.
|
||||
/// </summary>
|
||||
@ -581,5 +666,22 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
return _attribFormats.TryGetValue((VertexAttributeFormat)encoded, out format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try getting a single component vertex attribute format from an encoded format integer from Maxwell attribute registers.
|
||||
/// </summary>
|
||||
/// <param name="encoded">The encoded format integer from the attribute registers</param>
|
||||
/// <param name="format">The output single component vertex attribute format</param>
|
||||
/// <param name="componentsCount">Number of components that the format has</param>
|
||||
/// <returns>True if the format is valid, false otherwise</returns>
|
||||
public static bool TryGetSingleComponentAttribFormat(uint encoded, out Format format, out int componentsCount)
|
||||
{
|
||||
bool result = _singleComponentAttribFormats.TryGetValue((VertexAttributeFormat)encoded, out var tuple);
|
||||
|
||||
format = tuple.Item1;
|
||||
componentsCount = tuple.Item2;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
@ -15,9 +14,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
class BufferManager
|
||||
{
|
||||
private const int TfInfoVertexCountOffset = Constants.TotalTransformFeedbackBuffers * sizeof(int);
|
||||
private const int TfInfoBufferSize = TfInfoVertexCountOffset + sizeof(int);
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
|
||||
@ -104,9 +100,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private readonly BuffersPerStage[] _gpStorageBuffers;
|
||||
private readonly BuffersPerStage[] _gpUniformBuffers;
|
||||
|
||||
private BufferHandle _tfInfoBuffer;
|
||||
private readonly int[] _tfInfoData;
|
||||
|
||||
private bool _gpStorageBuffersDirty;
|
||||
private bool _gpUniformBuffersDirty;
|
||||
|
||||
@ -146,11 +139,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_bufferTextures = new List<BufferTextureBinding>();
|
||||
|
||||
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
|
||||
|
||||
if (!context.Capabilities.SupportsTransformFeedback)
|
||||
{
|
||||
_tfInfoData = new int[Constants.TotalTransformFeedbackBuffers];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -339,13 +327,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="vertexCount">Vertex count per instance</param>
|
||||
public void SetInstancedDrawVertexCount(int vertexCount)
|
||||
{
|
||||
if (!_context.Capabilities.SupportsTransformFeedback &&
|
||||
HasTransformFeedbackOutputs &&
|
||||
_tfInfoBuffer != BufferHandle.Null)
|
||||
if (!_context.Capabilities.SupportsTransformFeedback && HasTransformFeedbackOutputs)
|
||||
{
|
||||
Span<byte> data = stackalloc byte[sizeof(int)];
|
||||
MemoryMarshal.Cast<byte, int>(data)[0] = vertexCount;
|
||||
_context.Renderer.SetBufferData(_tfInfoBuffer, TfInfoVertexCountOffset, data);
|
||||
_context.SupportBufferUpdater.SetTfeVertexCount(vertexCount);
|
||||
_context.SupportBufferUpdater.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -607,17 +592,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else if (HasTransformFeedbackOutputs)
|
||||
{
|
||||
Span<int> info = _tfInfoData.AsSpan();
|
||||
Span<BufferAssignment> buffers = stackalloc BufferAssignment[Constants.TotalTransformFeedbackBuffers + 1];
|
||||
|
||||
bool needsDataUpdate = false;
|
||||
|
||||
if (_tfInfoBuffer == BufferHandle.Null)
|
||||
{
|
||||
_tfInfoBuffer = _context.Renderer.CreateBuffer(TfInfoBufferSize, BufferAccess.Stream);
|
||||
}
|
||||
|
||||
buffers[0] = new BufferAssignment(0, new BufferRange(_tfInfoBuffer, 0, TfInfoBufferSize));
|
||||
Span<BufferAssignment> buffers = stackalloc BufferAssignment[Constants.TotalTransformFeedbackBuffers];
|
||||
|
||||
int alignment = _context.Capabilities.StorageBufferOffsetAlignment;
|
||||
|
||||
@ -627,7 +602,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (tfb.Address == 0)
|
||||
{
|
||||
buffers[1 + index] = new BufferAssignment(1 + index, BufferRange.Empty);
|
||||
buffers[index] = new BufferAssignment(index, BufferRange.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -637,22 +612,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
int tfeOffset = ((int)tfb.Address & (alignment - 1)) / 4;
|
||||
|
||||
if (info[index] != tfeOffset)
|
||||
{
|
||||
info[index] = tfeOffset;
|
||||
needsDataUpdate = true;
|
||||
}
|
||||
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
|
||||
|
||||
buffers[1 + index] = new BufferAssignment(1 + index, bufferCache.GetBufferRange(address, size, write: true));
|
||||
buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(address, size, write: true));
|
||||
}
|
||||
}
|
||||
|
||||
if (needsDataUpdate)
|
||||
{
|
||||
Span<byte> infoData = MemoryMarshal.Cast<int, byte>(info);
|
||||
_context.Renderer.SetBufferData(_tfInfoBuffer, 0, infoData);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(buffers);
|
||||
}
|
||||
}
|
||||
|
123
src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs
Normal file
123
src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs
Normal file
@ -0,0 +1,123 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Buffer data updater.
|
||||
/// </summary>
|
||||
class BufferUpdater : IDisposable
|
||||
{
|
||||
private BufferHandle _handle;
|
||||
|
||||
/// <summary>
|
||||
/// Handle of the buffer.
|
||||
/// </summary>
|
||||
public BufferHandle Handle => _handle;
|
||||
|
||||
private readonly IRenderer _renderer;
|
||||
private int _startOffset = -1;
|
||||
private int _endOffset = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer updater.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Renderer that the buffer will be used with</param>
|
||||
public BufferUpdater(IRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark a region of the buffer as modified and needing to be sent to the GPU.
|
||||
/// </summary>
|
||||
/// <param name="startOffset">Start offset of the region in bytes</param>
|
||||
/// <param name="byteSize">Size of the region in bytes</param>
|
||||
protected void MarkDirty(int startOffset, int byteSize)
|
||||
{
|
||||
int endOffset = startOffset + byteSize;
|
||||
|
||||
if (_startOffset == -1)
|
||||
{
|
||||
_startOffset = startOffset;
|
||||
_endOffset = endOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (startOffset < _startOffset)
|
||||
{
|
||||
_startOffset = startOffset;
|
||||
}
|
||||
|
||||
if (endOffset > _endOffset)
|
||||
{
|
||||
_endOffset = endOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits all pending buffer updates to the GPU.
|
||||
/// </summary>
|
||||
/// <param name="data">All data that should be sent to the GPU. Only the modified regions will be updated</param>
|
||||
/// <param name="binding">Optional binding to bind the buffer if a new buffer was created</param>
|
||||
protected void Commit(ReadOnlySpan<byte> data, int binding = -1)
|
||||
{
|
||||
if (_startOffset != -1)
|
||||
{
|
||||
if (_handle == BufferHandle.Null)
|
||||
{
|
||||
_handle = _renderer.CreateBuffer(data.Length, BufferAccess.Stream);
|
||||
_renderer.Pipeline.ClearBuffer(_handle, 0, data.Length, 0);
|
||||
|
||||
if (binding >= 0)
|
||||
{
|
||||
var range = new BufferRange(_handle, 0, data.Length);
|
||||
_renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, range) });
|
||||
}
|
||||
};
|
||||
|
||||
_renderer.SetBufferData(_handle, _startOffset, data[_startOffset.._endOffset]);
|
||||
|
||||
_startOffset = -1;
|
||||
_endOffset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to a given element of a vector.
|
||||
/// </summary>
|
||||
/// <param name="vector">Vector to get the element reference from</param>
|
||||
/// <param name="elementIndex">Element index</param>
|
||||
/// <returns>Reference to the specified element</returns>
|
||||
protected static ref T GetElementRef<T>(ref Vector4<T> vector, int elementIndex)
|
||||
{
|
||||
switch (elementIndex)
|
||||
{
|
||||
case 0:
|
||||
return ref vector.X;
|
||||
case 1:
|
||||
return ref vector.Y;
|
||||
case 2:
|
||||
return ref vector.Z;
|
||||
case 3:
|
||||
return ref vector.W;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(elementIndex));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the buffer.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_handle != BufferHandle.Null)
|
||||
{
|
||||
_renderer.DeleteBuffer(_handle);
|
||||
_handle = BufferHandle.Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,56 +9,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Support buffer data updater.
|
||||
/// </summary>
|
||||
class SupportBufferUpdater : IDisposable
|
||||
class SupportBufferUpdater : BufferUpdater
|
||||
{
|
||||
private SupportBuffer _data;
|
||||
private BufferHandle _handle;
|
||||
|
||||
private readonly IRenderer _renderer;
|
||||
private int _startOffset = -1;
|
||||
private int _endOffset = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the support buffer updater.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Renderer that the support buffer will be used with</param>
|
||||
public SupportBufferUpdater(IRenderer renderer)
|
||||
public SupportBufferUpdater(IRenderer renderer) : base(renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
|
||||
var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f };
|
||||
_data.RenderScale.AsSpan().Fill(defaultScale);
|
||||
DirtyRenderScale(0, SupportBuffer.RenderScaleMaxCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark a region of the support buffer as modified and needing to be sent to the GPU.
|
||||
/// </summary>
|
||||
/// <param name="startOffset">Start offset of the region in bytes</param>
|
||||
/// <param name="byteSize">Size of the region in bytes</param>
|
||||
private void MarkDirty(int startOffset, int byteSize)
|
||||
{
|
||||
int endOffset = startOffset + byteSize;
|
||||
|
||||
if (_startOffset == -1)
|
||||
{
|
||||
_startOffset = startOffset;
|
||||
_endOffset = endOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (startOffset < _startOffset)
|
||||
{
|
||||
_startOffset = startOffset;
|
||||
}
|
||||
|
||||
if (endOffset > _endOffset)
|
||||
{
|
||||
_endOffset = endOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the fragment render scale count as being modified.
|
||||
/// </summary>
|
||||
@ -220,40 +185,40 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits all pending buffer updates to the GPU.
|
||||
/// Sets offset for the misaligned portion of a transform feedback buffer, and the buffer size, for transform feedback emulation.
|
||||
/// </summary>
|
||||
public void Commit()
|
||||
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
|
||||
/// <param name="offset">Misaligned offset of the buffer</param>
|
||||
public void SetTfeOffset(int bufferIndex, int offset)
|
||||
{
|
||||
if (_startOffset != -1)
|
||||
ref int currentOffset = ref GetElementRef(ref _data.TfeOffset, bufferIndex);
|
||||
|
||||
if (currentOffset != offset)
|
||||
{
|
||||
if (_handle == BufferHandle.Null)
|
||||
{
|
||||
_handle = _renderer.CreateBuffer(SupportBuffer.RequiredSize, BufferAccess.Stream);
|
||||
_renderer.Pipeline.ClearBuffer(_handle, 0, SupportBuffer.RequiredSize, 0);
|
||||
|
||||
var range = new BufferRange(_handle, 0, SupportBuffer.RequiredSize);
|
||||
_renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, range) });
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<SupportBuffer, byte>(MemoryMarshal.CreateSpan(ref _data, 1));
|
||||
|
||||
_renderer.SetBufferData(_handle, _startOffset, data[_startOffset.._endOffset]);
|
||||
|
||||
_startOffset = -1;
|
||||
_endOffset = -1;
|
||||
currentOffset = offset;
|
||||
MarkDirty(SupportBuffer.TfeOffsetOffset + bufferIndex * sizeof(int), sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the support buffer.
|
||||
/// Sets the vertex count used for transform feedback emulation with instanced draws.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
/// <param name="vertexCount">Vertex count of the instanced draw</param>
|
||||
public void SetTfeVertexCount(int vertexCount)
|
||||
{
|
||||
if (_handle != BufferHandle.Null)
|
||||
if (_data.TfeVertexCount.X != vertexCount)
|
||||
{
|
||||
_renderer.DeleteBuffer(_handle);
|
||||
_handle = BufferHandle.Null;
|
||||
_data.TfeVertexCount.X = vertexCount;
|
||||
MarkDirty(SupportBuffer.TfeVertexCountOffset, sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits all pending buffer updates to the GPU.
|
||||
/// </summary>
|
||||
public void Commit()
|
||||
{
|
||||
Commit(MemoryMarshal.Cast<SupportBuffer, byte>(MemoryMarshal.CreateSpan(ref _data, 1)), SupportBuffer.Binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
public IProgram HostProgram { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional vertex shader converted to compute.
|
||||
/// </summary>
|
||||
public ShaderAsCompute VertexAsCompute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional geometry shader converted to compute.
|
||||
/// </summary>
|
||||
public ShaderAsCompute GeometryAsCompute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU state used to create this version of the shader.
|
||||
/// </summary>
|
||||
@ -45,12 +55,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
Bindings = new CachedShaderBindings(shaders.Length == 1, shaders);
|
||||
}
|
||||
|
||||
public CachedShaderProgram(
|
||||
IProgram hostProgram,
|
||||
ShaderAsCompute vertexAsCompute,
|
||||
ShaderAsCompute geometryAsCompute,
|
||||
ShaderSpecializationState specializationState,
|
||||
CachedShaderStage[] shaders) : this(hostProgram, specializationState, shaders)
|
||||
{
|
||||
VertexAsCompute = vertexAsCompute;
|
||||
GeometryAsCompute = geometryAsCompute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of the host shader resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
HostProgram.Dispose();
|
||||
VertexAsCompute?.HostProgram.Dispose();
|
||||
GeometryAsCompute?.HostProgram.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
ShaderSpecializationState oldSpecState,
|
||||
ShaderSpecializationState newSpecState,
|
||||
ResourceCounts counts,
|
||||
int stageIndex) : base(context, counts, stageIndex, oldSpecState.TransformFeedbackDescriptors != null)
|
||||
int stageIndex) : base(context, counts, stageIndex)
|
||||
{
|
||||
_data = data;
|
||||
_cb1Data = cb1Data;
|
||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 5529;
|
||||
private const uint CodeGenVersion = 5682;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
@ -140,6 +140,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// </summary>
|
||||
public ShaderStage Stage;
|
||||
|
||||
/// <summary>
|
||||
/// Number of vertices that each output primitive has on a geometry shader.
|
||||
/// </summary>
|
||||
public byte GeometryVerticesPerPrimitive;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of vertices that a geometry shader may generate.
|
||||
/// </summary>
|
||||
public ushort GeometryMaxOutputVertices;
|
||||
|
||||
/// <summary>
|
||||
/// Number of invocations per primitive on tessellation or geometry shaders.
|
||||
/// </summary>
|
||||
public ushort ThreadsPerInputPrimitive;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the fragment shader accesses the fragment coordinate built-in variable.
|
||||
/// </summary>
|
||||
@ -783,9 +798,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
sBuffers,
|
||||
textures,
|
||||
images,
|
||||
ShaderIdentification.None,
|
||||
0,
|
||||
dataInfo.Stage,
|
||||
dataInfo.GeometryVerticesPerPrimitive,
|
||||
dataInfo.GeometryMaxOutputVertices,
|
||||
dataInfo.ThreadsPerInputPrimitive,
|
||||
dataInfo.UsesFragCoord,
|
||||
dataInfo.UsesInstanceId,
|
||||
dataInfo.UsesDrawParameters,
|
||||
@ -813,6 +829,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
TexturesCount = (ushort)info.Textures.Count,
|
||||
ImagesCount = (ushort)info.Images.Count,
|
||||
Stage = info.Stage,
|
||||
GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive,
|
||||
GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices,
|
||||
ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive,
|
||||
UsesFragCoord = info.UsesFragCoord,
|
||||
UsesInstanceId = info.UsesInstanceId,
|
||||
UsesDrawParameters = info.UsesDrawParameters,
|
||||
|
@ -595,6 +595,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
ResourceCounts counts = new();
|
||||
|
||||
DiskCacheGpuAccessor[] gpuAccessors = new DiskCacheGpuAccessor[Constants.ShaderStages];
|
||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||
TranslatorContext nextStage = null;
|
||||
|
||||
@ -626,14 +627,22 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
|
||||
}
|
||||
|
||||
gpuAccessors[stageIndex] = gpuAccessor;
|
||||
translatorContexts[stageIndex + 1] = currentStage;
|
||||
nextStage = currentStage;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_context.Capabilities.SupportsGeometryShader)
|
||||
bool hasGeometryShader = translatorContexts[4] != null;
|
||||
bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore;
|
||||
bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore;
|
||||
bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader);
|
||||
|
||||
// We don't support caching shader stages that have been converted to compute currently,
|
||||
// so just eliminate them if they exist in the cache.
|
||||
if (vertexToCompute)
|
||||
{
|
||||
ShaderCache.TryRemoveGeometryStage(translatorContexts);
|
||||
return;
|
||||
}
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
||||
@ -647,6 +656,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
if (currentStage != null)
|
||||
{
|
||||
gpuAccessors[stageIndex].InitializeReservedCounts(specState.TransformFeedbackDescriptors != null, vertexToCompute);
|
||||
|
||||
ShaderProgram program;
|
||||
|
||||
byte[] guestCode = guestShaders[stageIndex + 1].Value.Code;
|
||||
@ -701,6 +712,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
ResourceCounts counts = new();
|
||||
ShaderSpecializationState newSpecState = new(ref specState.ComputeState);
|
||||
DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
|
||||
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
|
||||
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
|
||||
|
||||
|
@ -25,11 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
|
||||
public GpuAccessor(
|
||||
GpuContext context,
|
||||
GpuChannel channel,
|
||||
GpuAccessorState state,
|
||||
int stageIndex) : base(context, state.ResourceCounts, stageIndex, state.TransformFeedbackDescriptors != null)
|
||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context, state.ResourceCounts, stageIndex)
|
||||
{
|
||||
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
||||
_channel = channel;
|
||||
@ -49,7 +45,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0, false)
|
||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0)
|
||||
{
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
|
@ -15,8 +15,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private readonly ResourceCounts _resourceCounts;
|
||||
private readonly int _stageIndex;
|
||||
|
||||
private readonly int _reservedConstantBuffers;
|
||||
private readonly int _reservedStorageBuffers;
|
||||
private int _reservedConstantBuffers;
|
||||
private int _reservedStorageBuffers;
|
||||
private int _reservedTextures;
|
||||
private int _reservedImages;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU accessor.
|
||||
@ -24,15 +26,26 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="resourceCounts">Counter of GPU resources used by the shader</param>
|
||||
/// <param name="stageIndex">Index of the shader stage, 0 for compute</param>
|
||||
/// <param name="tfEnabled">Indicates if the current graphics shader is used with transform feedback enabled</param>
|
||||
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex, bool tfEnabled)
|
||||
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
|
||||
{
|
||||
_context = context;
|
||||
_resourceCounts = resourceCounts;
|
||||
_stageIndex = stageIndex;
|
||||
}
|
||||
|
||||
_reservedConstantBuffers = 1; // For the support buffer.
|
||||
_reservedStorageBuffers = !context.Capabilities.SupportsTransformFeedback && tfEnabled ? 5 : 0;
|
||||
/// <summary>
|
||||
/// Initializes counts for bindings that will be reserved for emulator use.
|
||||
/// </summary>
|
||||
/// <param name="tfEnabled">Indicates if the current 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 void InitializeReservedCounts(bool tfEnabled, bool vertexAsCompute)
|
||||
{
|
||||
ResourceReservationCounts rrc = new(!_context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
|
||||
|
||||
_reservedConstantBuffers = rrc.ReservedConstantBuffers;
|
||||
_reservedStorageBuffers = rrc.ReservedStorageBuffers;
|
||||
_reservedTextures = rrc.ReservedTextures;
|
||||
_reservedImages = rrc.ReservedImages;
|
||||
}
|
||||
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
@ -69,6 +82,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
public int QueryBindingTexture(int index, bool isBuffer)
|
||||
{
|
||||
int binding;
|
||||
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
if (isBuffer)
|
||||
@ -76,16 +91,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
index += (int)_context.Capabilities.MaximumTexturesPerStage;
|
||||
}
|
||||
|
||||
return GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
|
||||
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
|
||||
}
|
||||
else
|
||||
{
|
||||
return _resourceCounts.TexturesCount++;
|
||||
binding = _resourceCounts.TexturesCount++;
|
||||
}
|
||||
|
||||
return binding + _reservedTextures;
|
||||
}
|
||||
|
||||
public int QueryBindingImage(int index, bool isBuffer)
|
||||
{
|
||||
int binding;
|
||||
|
||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||
{
|
||||
if (isBuffer)
|
||||
@ -93,12 +112,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
index += (int)_context.Capabilities.MaximumImagesPerStage;
|
||||
}
|
||||
|
||||
return GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
|
||||
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
|
||||
}
|
||||
else
|
||||
{
|
||||
return _resourceCounts.ImagesCount++;
|
||||
binding = _resourceCounts.ImagesCount++;
|
||||
}
|
||||
|
||||
return binding + _reservedImages;
|
||||
}
|
||||
|
||||
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
|
||||
@ -137,6 +158,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment;
|
||||
|
||||
public int QueryHostSubgroupSize() => _context.Capabilities.ShaderSubgroupSize;
|
||||
|
||||
public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat;
|
||||
|
||||
public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock;
|
||||
|
20
src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs
Normal file
20
src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
class ShaderAsCompute
|
||||
{
|
||||
public IProgram HostProgram { get; }
|
||||
public ShaderProgramInfo Info { get; }
|
||||
public ResourceReservations Reservations { get; }
|
||||
|
||||
public ShaderAsCompute(IProgram hostProgram, ShaderProgramInfo info, ResourceReservations reservations)
|
||||
{
|
||||
HostProgram = hostProgram;
|
||||
Info = info;
|
||||
Reservations = reservations;
|
||||
}
|
||||
}
|
||||
}
|
@ -215,9 +215,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
ShaderSpecializationState specState = new(ref computeState);
|
||||
GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState);
|
||||
GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState);
|
||||
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
|
||||
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
|
||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
|
||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false);
|
||||
|
||||
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
|
||||
@ -321,6 +322,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
|
||||
|
||||
GpuAccessor[] gpuAccessors = new GpuAccessor[Constants.ShaderStages];
|
||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||
TranslatorContext nextStage = null;
|
||||
|
||||
@ -345,22 +347,31 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
|
||||
}
|
||||
|
||||
gpuAccessors[stageIndex] = gpuAccessor;
|
||||
translatorContexts[stageIndex + 1] = currentStage;
|
||||
nextStage = currentStage;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_context.Capabilities.SupportsGeometryShader)
|
||||
{
|
||||
TryRemoveGeometryStage(translatorContexts);
|
||||
}
|
||||
bool hasGeometryShader = translatorContexts[4] != null;
|
||||
bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore;
|
||||
bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore;
|
||||
bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader);
|
||||
bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore);
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
|
||||
List<ShaderSource> shaderSources = new();
|
||||
|
||||
TranslatorContext previousStage = null;
|
||||
ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute);
|
||||
|
||||
ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null);
|
||||
if (geometryToCompute && translatorContexts[4] != null)
|
||||
{
|
||||
translatorContexts[4].SetVertexOutputMapForGeometryAsCompute(translatorContexts[1]);
|
||||
}
|
||||
|
||||
ShaderAsCompute vertexAsCompute = null;
|
||||
ShaderAsCompute geometryAsCompute = null;
|
||||
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
{
|
||||
@ -368,8 +379,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
if (currentStage != null)
|
||||
{
|
||||
gpuAccessors[stageIndex].InitializeReservedCounts(transformFeedbackDescriptors != null, vertexToCompute);
|
||||
|
||||
ShaderProgram program;
|
||||
|
||||
bool asCompute = (stageIndex == 0 && vertexToCompute) || (stageIndex == 3 && geometryToCompute);
|
||||
|
||||
if (stageIndex == 0 && translatorContexts[0] != null)
|
||||
{
|
||||
TranslatedShaderVertexPair translatedShader = TranslateShader(
|
||||
@ -378,7 +393,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
currentStage,
|
||||
translatorContexts[0],
|
||||
cachedGuestCode.VertexACode,
|
||||
cachedGuestCode.VertexBCode);
|
||||
cachedGuestCode.VertexBCode,
|
||||
asCompute);
|
||||
|
||||
shaders[0] = translatedShader.VertexA;
|
||||
shaders[1] = translatedShader.VertexB;
|
||||
@ -388,12 +404,31 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
byte[] code = cachedGuestCode.GetByIndex(stageIndex);
|
||||
|
||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code);
|
||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code, asCompute);
|
||||
|
||||
shaders[stageIndex + 1] = translatedShader.Shader;
|
||||
program = translatedShader.Program;
|
||||
}
|
||||
|
||||
if (asCompute)
|
||||
{
|
||||
bool tfEnabled = transformFeedbackDescriptors != null;
|
||||
|
||||
if (stageIndex == 0)
|
||||
{
|
||||
vertexAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled);
|
||||
|
||||
TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
|
||||
|
||||
program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
|
||||
}
|
||||
else
|
||||
{
|
||||
geometryAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled);
|
||||
program = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (program != null)
|
||||
{
|
||||
shaderSources.Add(CreateShaderSource(program));
|
||||
@ -418,46 +453,81 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
|
||||
|
||||
gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
|
||||
gpShaders = new(hostProgram, vertexAsCompute, geometryAsCompute, specState, shaders);
|
||||
|
||||
_graphicsShaderCache.Add(gpShaders);
|
||||
EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
|
||||
|
||||
// We don't currently support caching shaders that have been converted to compute.
|
||||
if (vertexAsCompute == null)
|
||||
{
|
||||
EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
|
||||
}
|
||||
|
||||
_gpPrograms[addresses] = gpShaders;
|
||||
|
||||
return gpShaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to eliminate the geometry stage from the array of translator contexts.
|
||||
/// Checks if a vertex shader should be converted to a compute shader due to it making use of
|
||||
/// features that are not supported on the host.
|
||||
/// </summary>
|
||||
/// <param name="translatorContexts">Array of translator contexts</param>
|
||||
public static void TryRemoveGeometryStage(TranslatorContext[] translatorContexts)
|
||||
/// <param name="context">GPU context of the shader</param>
|
||||
/// <param name="vertexHasStore">Whether the vertex shader has image or storage buffer store operations</param>
|
||||
/// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param>
|
||||
/// <param name="hasGeometryShader">Whether a geometry shader exists</param>
|
||||
/// <returns>True if the vertex shader should be converted to compute, false otherwise</returns>
|
||||
public static bool ShouldConvertVertexToCompute(GpuContext context, bool vertexHasStore, bool geometryHasStore, bool hasGeometryShader)
|
||||
{
|
||||
if (translatorContexts[4] != null)
|
||||
// If the host does not support store operations on vertex,
|
||||
// we need to emulate it on a compute shader.
|
||||
if (!context.Capabilities.SupportsVertexStoreAndAtomics && vertexHasStore)
|
||||
{
|
||||
// We have a geometry shader, but geometry shaders are not supported.
|
||||
// Try to eliminate the geometry shader.
|
||||
|
||||
ShaderProgramInfo info = translatorContexts[4].Translate().Info;
|
||||
|
||||
if (info.Identification == ShaderIdentification.GeometryLayerPassthrough)
|
||||
{
|
||||
// We managed to identify that this geometry shader is only used to set the output Layer value,
|
||||
// we can set the Layer on the previous stage instead (usually the vertex stage) and eliminate it.
|
||||
|
||||
for (int i = 3; i >= 1; i--)
|
||||
{
|
||||
if (translatorContexts[i] != null)
|
||||
{
|
||||
translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute);
|
||||
translatorContexts[i].SetLastInVertexPipeline();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
translatorContexts[4] = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If any stage after the vertex stage is converted to compute,
|
||||
// we need to convert vertex to compute too.
|
||||
return hasGeometryShader && ShouldConvertGeometryToCompute(context, geometryHasStore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a geometry shader should be converted to a compute shader due to it making use of
|
||||
/// features that are not supported on the host.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context of the shader</param>
|
||||
/// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param>
|
||||
/// <returns>True if the geometry shader should be converted to compute, false otherwise</returns>
|
||||
public static bool ShouldConvertGeometryToCompute(GpuContext context, bool geometryHasStore)
|
||||
{
|
||||
return (!context.Capabilities.SupportsVertexStoreAndAtomics && geometryHasStore) ||
|
||||
!context.Capabilities.SupportsGeometryShader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if it might be necessary for any vertex, tessellation or geometry shader to be converted to compute,
|
||||
/// based on the supported host features.
|
||||
/// </summary>
|
||||
/// <param name="capabilities">Host capabilities</param>
|
||||
/// <returns>True if the possibility of a shader being converted to compute exists, false otherwise</returns>
|
||||
public static bool MayConvertVtgToCompute(ref Capabilities capabilities)
|
||||
{
|
||||
return !capabilities.SupportsVertexStoreAndAtomics || !capabilities.SupportsGeometryShader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a compute shader from a vertex, tessellation or geometry shader that has been converted to compute.
|
||||
/// </summary>
|
||||
/// <param name="program">Shader program</param>
|
||||
/// <param name="context">Translation context of the shader</param>
|
||||
/// <param name="tfEnabled">Whether transform feedback is enabled</param>
|
||||
/// <returns>Compute shader</returns>
|
||||
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);
|
||||
|
||||
return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -573,9 +643,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
bool vertexAsCompute = gpShaders.VertexAsCompute != null;
|
||||
bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false;
|
||||
|
||||
return gpShaders.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true);
|
||||
return gpShaders.SpecializationState.MatchesGraphics(
|
||||
channel,
|
||||
ref poolState,
|
||||
ref graphicsState,
|
||||
vertexAsCompute,
|
||||
usesDrawParameters,
|
||||
checkTextures: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -636,6 +713,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="vertexA">Optional translator context of the shader that should be combined</param>
|
||||
/// <param name="codeA">Optional Maxwell binary code of the Vertex A shader, if present</param>
|
||||
/// <param name="codeB">Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache</param>
|
||||
/// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param>
|
||||
/// <returns>Compiled graphics shader code</returns>
|
||||
private static TranslatedShaderVertexPair TranslateShader(
|
||||
ShaderDumper dumper,
|
||||
@ -643,7 +721,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
TranslatorContext currentStage,
|
||||
TranslatorContext vertexA,
|
||||
byte[] codeA,
|
||||
byte[] codeB)
|
||||
byte[] codeB,
|
||||
bool asCompute)
|
||||
{
|
||||
ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||
|
||||
@ -663,7 +742,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
pathsB = dumper.Dump(codeB, compute: false);
|
||||
}
|
||||
|
||||
ShaderProgram program = currentStage.Translate(vertexA);
|
||||
ShaderProgram program = currentStage.Translate(vertexA, asCompute);
|
||||
|
||||
pathsB.Prepend(program);
|
||||
pathsA.Prepend(program);
|
||||
@ -681,8 +760,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="channel">GPU channel using the shader</param>
|
||||
/// <param name="context">Translator context of the stage to be translated</param>
|
||||
/// <param name="code">Optional Maxwell binary code of the current stage shader, if present on cache</param>
|
||||
/// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param>
|
||||
/// <returns>Compiled graphics shader code</returns>
|
||||
private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code)
|
||||
private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code, bool asCompute)
|
||||
{
|
||||
var memoryManager = channel.MemoryManager;
|
||||
|
||||
@ -694,7 +774,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
|
||||
|
||||
ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
|
||||
ShaderProgram program = context.Translate();
|
||||
ShaderProgram program = context.Translate(asCompute);
|
||||
|
||||
paths.Prepend(program);
|
||||
|
||||
|
@ -33,6 +33,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
private readonly int _reservedConstantBuffers;
|
||||
private readonly int _reservedStorageBuffers;
|
||||
private readonly int _reservedTextures;
|
||||
private readonly int _reservedImages;
|
||||
|
||||
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
|
||||
private readonly List<ResourceUsage>[] _resourceUsages;
|
||||
@ -42,7 +44,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public ShaderInfoBuilder(GpuContext context, bool tfEnabled)
|
||||
/// <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)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
@ -58,29 +61,36 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
|
||||
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
||||
AddUsage(SupportBufferStages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 0, 1);
|
||||
AddUsage(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
||||
|
||||
_reservedConstantBuffers = 1; // For the support buffer.
|
||||
ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
|
||||
|
||||
if (!context.Capabilities.SupportsTransformFeedback && tfEnabled)
|
||||
{
|
||||
_reservedStorageBuffers = 5;
|
||||
_reservedConstantBuffers = rrc.ReservedConstantBuffers;
|
||||
_reservedStorageBuffers = rrc.ReservedStorageBuffers;
|
||||
_reservedTextures = rrc.ReservedTextures;
|
||||
_reservedImages = rrc.ReservedImages;
|
||||
|
||||
AddDescriptor(VtgStages, ResourceType.StorageBuffer, StorageSetIndex, 0, 5);
|
||||
AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Read, StorageSetIndex, 0, 1);
|
||||
AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Write, StorageSetIndex, 1, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
_reservedStorageBuffers = 0;
|
||||
}
|
||||
// TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader.
|
||||
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
|
||||
|
||||
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
|
||||
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, StorageSetIndex, 0, rrc.ReservedStorageBuffers);
|
||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, TextureSetIndex, 0, rrc.ReservedTextures);
|
||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ImageSetIndex, 0, rrc.ReservedImages);
|
||||
}
|
||||
|
||||
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
|
||||
{
|
||||
AddDescriptor(stages, type, setIndex, start, count);
|
||||
AddUsage(stages, type, setIndex, start, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds information from a given shader stage.
|
||||
/// </summary>
|
||||
/// <param name="info">Shader stage information</param>
|
||||
public void AddStageInfo(ShaderProgramInfo info)
|
||||
/// <param name="vertexAsCompute">True if the shader stage has been converted into a compute shader</param>
|
||||
public void AddStageInfo(ShaderProgramInfo info, bool vertexAsCompute = false)
|
||||
{
|
||||
if (info.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
@ -96,7 +106,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
_ => 0,
|
||||
});
|
||||
|
||||
ResourceStages stages = info.Stage switch
|
||||
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute : info.Stage switch
|
||||
{
|
||||
ShaderStage.Compute => ResourceStages.Compute,
|
||||
ShaderStage.Vertex => ResourceStages.Vertex,
|
||||
@ -114,8 +124,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
int uniformBinding = _reservedConstantBuffers + stageIndex * uniformsPerStage;
|
||||
int storageBinding = _reservedStorageBuffers + stageIndex * storagesPerStage;
|
||||
int textureBinding = stageIndex * texturesPerStage * 2;
|
||||
int imageBinding = stageIndex * imagesPerStage * 2;
|
||||
int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2;
|
||||
int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2;
|
||||
|
||||
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
|
||||
AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
|
||||
@ -164,15 +174,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <param name="stages">Shader stages where the resource is used</param>
|
||||
/// <param name="type">Type of the resource</param>
|
||||
/// <param name="access">How the resource is accessed by the shader stages where it is used</param>
|
||||
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
|
||||
/// <param name="binding">Binding number where the resource will be bound</param>
|
||||
/// <param name="count">Number of resources bound at the binding location</param>
|
||||
private void AddUsage(ResourceStages stages, ResourceType type, ResourceAccess access, int setIndex, int binding, int count)
|
||||
private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
|
||||
{
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages, access));
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages));
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,8 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(
|
||||
buffer.Binding,
|
||||
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
||||
stages,
|
||||
buffer.Flags.HasFlag(BufferUsageFlags.Write) ? ResourceAccess.ReadWrite : ResourceAccess.Read));
|
||||
stages));
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,8 +223,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(
|
||||
texture.Binding,
|
||||
type,
|
||||
stages,
|
||||
texture.Flags.HasFlag(TextureUsageFlags.ImageStore) ? ResourceAccess.ReadWrite : ResourceAccess.Read));
|
||||
stages));
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,11 +292,28 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <returns>Shader information</returns>
|
||||
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
|
||||
{
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled: false);
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false);
|
||||
|
||||
builder.AddStageInfo(info);
|
||||
|
||||
return builder.Build(null, fromCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds shader information for a vertex or geometry shader thas was converted to compute shader.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that owns the shader</param>
|
||||
/// <param name="info">Compute shader information</param>
|
||||
/// <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)
|
||||
{
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true);
|
||||
|
||||
builder.AddStageInfo(info, vertexAsCompute: true);
|
||||
|
||||
return builder.Build(null, fromCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
bool vertexAsCompute = entry.VertexAsCompute != null;
|
||||
bool usesDrawParameters = entry.Shaders[1]?.Info.UsesDrawParameters ?? false;
|
||||
|
||||
if (entry.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true))
|
||||
if (entry.SpecializationState.MatchesGraphics(
|
||||
channel,
|
||||
ref poolState,
|
||||
ref graphicsState,
|
||||
vertexAsCompute,
|
||||
usesDrawParameters,
|
||||
checkTextures: true))
|
||||
{
|
||||
program = entry;
|
||||
return true;
|
||||
|
@ -457,6 +457,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="graphicsState">Graphics state</param>
|
||||
/// <param name="vertexAsCompute">Indicates that the vertex shader has been converted into a compute shader</param>
|
||||
/// <param name="usesDrawParameters">Indicates whether the vertex shader accesses draw parameters</param>
|
||||
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
@ -464,6 +465,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
GpuChannel channel,
|
||||
ref GpuChannelPoolState poolState,
|
||||
ref GpuChannelGraphicsState graphicsState,
|
||||
bool vertexAsCompute,
|
||||
bool usesDrawParameters,
|
||||
bool checkTextures)
|
||||
{
|
||||
@ -497,9 +499,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan()))
|
||||
if (ShaderCache.MayConvertVtgToCompute(ref channel.Capabilities) && !vertexAsCompute)
|
||||
{
|
||||
return false;
|
||||
for (int index = 0; index < graphicsState.AttributeTypes.Length; index++)
|
||||
{
|
||||
AttributeType lType = FilterAttributeType(channel, graphicsState.AttributeTypes[index]);
|
||||
AttributeType rType = FilterAttributeType(channel, GraphicsState.AttributeTypes[index]);
|
||||
|
||||
if (lType != rType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (usesDrawParameters && graphicsState.HasConstantBufferDrawParameters != GraphicsState.HasConstantBufferDrawParameters)
|
||||
@ -530,6 +548,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
||||
}
|
||||
|
||||
private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type)
|
||||
{
|
||||
type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed);
|
||||
|
||||
if (channel.Capabilities.SupportsScaledVertexFormats &&
|
||||
(type == AttributeType.Sscaled || type == AttributeType.Uscaled))
|
||||
{
|
||||
type = AttributeType.Float;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the recorded state matches the current GPU compute engine state.
|
||||
/// </summary>
|
||||
|
@ -7,5 +7,6 @@
|
||||
public const int MaxVertexAttribs = 16;
|
||||
public const int MaxVertexBuffers = 16;
|
||||
public const int MaxTransformFeedbackBuffers = 4;
|
||||
public const int MaxSubgroupSize = 64;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
private static readonly Lazy<int> _maximumComputeSharedMemorySize = new(() => GetLimit(All.MaxComputeSharedMemorySize));
|
||||
private static readonly Lazy<int> _storageBufferOffsetAlignment = new(() => GetLimit(All.ShaderStorageBufferOffsetAlignment));
|
||||
private static readonly Lazy<int> _textureBufferOffsetAlignment = new(() => GetLimit(All.TextureBufferOffsetAlignment));
|
||||
|
||||
public enum GpuVendor
|
||||
{
|
||||
@ -78,6 +79,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value;
|
||||
public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value;
|
||||
public static int TextureBufferOffsetAlignment => _textureBufferOffsetAlignment.Value;
|
||||
|
||||
public static float MaximumSupportedAnisotropy => _maxSupportedAnisotropy.Value;
|
||||
|
||||
|
@ -164,6 +164,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
||||
supportsShaderFloat64: true,
|
||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||
supportsVertexStoreAndAtomics: true,
|
||||
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
||||
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
|
||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||
@ -175,7 +176,9 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
maximumImagesPerStage: 8,
|
||||
maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
|
||||
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
|
||||
shaderSubgroupSize: Constants.MaxSubgroupSize,
|
||||
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
|
||||
textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment,
|
||||
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan.
|
||||
}
|
||||
|
||||
|
@ -11,13 +11,17 @@ namespace Ryujinx.Graphics.Shader
|
||||
Uint,
|
||||
Sscaled,
|
||||
Uscaled,
|
||||
|
||||
Packed = 1 << 6,
|
||||
PackedRgb10A2Signed = 1 << 7,
|
||||
AnyPacked = Packed | PackedRgb10A2Signed,
|
||||
}
|
||||
|
||||
static class AttributeTypeExtensions
|
||||
{
|
||||
public static AggregateType ToAggregateType(this AttributeType type)
|
||||
{
|
||||
return type switch
|
||||
return (type & ~AttributeType.AnyPacked) switch
|
||||
{
|
||||
AttributeType.Float => AggregateType.FP32,
|
||||
AttributeType.Sint => AggregateType.S32,
|
||||
@ -28,7 +32,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
|
||||
public static AggregateType ToAggregateType(this AttributeType type, bool supportsScaledFormats)
|
||||
{
|
||||
return type switch
|
||||
return (type & ~AttributeType.AnyPacked) switch
|
||||
{
|
||||
AttributeType.Float => AggregateType.FP32,
|
||||
AttributeType.Sint => AggregateType.S32,
|
||||
|
@ -25,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
context.AppendLine("#extension GL_KHR_shader_subgroup_basic : enable");
|
||||
context.AppendLine("#extension GL_KHR_shader_subgroup_ballot : enable");
|
||||
context.AppendLine("#extension GL_KHR_shader_subgroup_shuffle : enable");
|
||||
}
|
||||
|
||||
context.AppendLine("#extension GL_ARB_shader_group_vote : enable");
|
||||
@ -99,10 +100,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
else
|
||||
{
|
||||
string outPrimitive = context.Definitions.OutputTopology.ToGlslString();
|
||||
|
||||
int maxOutputVertices = context.Definitions.GpPassthrough
|
||||
? context.Definitions.InputTopology.ToInputVertices()
|
||||
: context.Definitions.MaxOutputVertices;
|
||||
int maxOutputVertices = context.Definitions.MaxOutputVertices;
|
||||
|
||||
context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;");
|
||||
}
|
||||
@ -201,26 +199,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl");
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.Shuffle) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl");
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleDown) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl");
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleUp) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl");
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleXor) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl");
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl");
|
||||
@ -339,15 +317,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
string typeName = GetVarTypeName(context, memory.Type & ~AggregateType.Array);
|
||||
|
||||
if (memory.ArrayLength > 0)
|
||||
if (memory.Type.HasFlag(AggregateType.Array))
|
||||
{
|
||||
string arraySize = memory.ArrayLength.ToString(CultureInfo.InvariantCulture);
|
||||
if (memory.ArrayLength > 0)
|
||||
{
|
||||
string arraySize = memory.ArrayLength.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
context.AppendLine($"{prefix}{typeName} {memory.Name}[{arraySize}];");
|
||||
context.AppendLine($"{prefix}{typeName} {memory.Name}[{arraySize}];");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.AppendLine($"{prefix}{typeName} {memory.Name}[];");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.AppendLine($"{prefix}{typeName} {memory.Name}[];");
|
||||
context.AppendLine($"{prefix}{typeName} {memory.Name};");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
public static string MultiplyHighS32 = "Helper_MultiplyHighS32";
|
||||
public static string MultiplyHighU32 = "Helper_MultiplyHighU32";
|
||||
|
||||
public static string Shuffle = "Helper_Shuffle";
|
||||
public static string ShuffleDown = "Helper_ShuffleDown";
|
||||
public static string ShuffleUp = "Helper_ShuffleUp";
|
||||
public static string ShuffleXor = "Helper_ShuffleXor";
|
||||
public static string SwizzleAdd = "Helper_SwizzleAdd";
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
float Helper_Shuffle(float x, uint index, uint mask, out bool valid)
|
||||
{
|
||||
uint clamp = mask & 0x1fu;
|
||||
uint segMask = (mask >> 8) & 0x1fu;
|
||||
uint minThreadId = $SUBGROUP_INVOCATION$ & segMask;
|
||||
uint maxThreadId = minThreadId | (clamp & ~segMask);
|
||||
uint srcThreadId = (index & ~segMask) | minThreadId;
|
||||
valid = srcThreadId <= maxThreadId;
|
||||
float v = $SUBGROUP_BROADCAST$(x, srcThreadId);
|
||||
return valid ? v : x;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
float Helper_ShuffleDown(float x, uint index, uint mask, out bool valid)
|
||||
{
|
||||
uint clamp = mask & 0x1fu;
|
||||
uint segMask = (mask >> 8) & 0x1fu;
|
||||
uint minThreadId = $SUBGROUP_INVOCATION$ & segMask;
|
||||
uint maxThreadId = minThreadId | (clamp & ~segMask);
|
||||
uint srcThreadId = $SUBGROUP_INVOCATION$ + index;
|
||||
valid = srcThreadId <= maxThreadId;
|
||||
float v = $SUBGROUP_BROADCAST$(x, srcThreadId);
|
||||
return valid ? v : x;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
float Helper_ShuffleUp(float x, uint index, uint mask, out bool valid)
|
||||
{
|
||||
uint segMask = (mask >> 8) & 0x1fu;
|
||||
uint minThreadId = $SUBGROUP_INVOCATION$ & segMask;
|
||||
uint srcThreadId = $SUBGROUP_INVOCATION$ - index;
|
||||
valid = int(srcThreadId) >= int(minThreadId);
|
||||
float v = $SUBGROUP_BROADCAST$(x, srcThreadId);
|
||||
return valid ? v : x;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
float Helper_ShuffleXor(float x, uint index, uint mask, out bool valid)
|
||||
{
|
||||
uint clamp = mask & 0x1fu;
|
||||
uint segMask = (mask >> 8) & 0x1fu;
|
||||
uint minThreadId = $SUBGROUP_INVOCATION$ & segMask;
|
||||
uint maxThreadId = minThreadId | (clamp & ~segMask);
|
||||
uint srcThreadId = $SUBGROUP_INVOCATION$ ^ index;
|
||||
valid = srcThreadId <= maxThreadId;
|
||||
float v = $SUBGROUP_BROADCAST$(x, srcThreadId);
|
||||
return valid ? v : x;
|
||||
}
|
@ -9,6 +9,7 @@ using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenFSI;
|
||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
|
||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenMemory;
|
||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenPacking;
|
||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenShuffle;
|
||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenVector;
|
||||
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
|
||||
|
||||
@ -174,6 +175,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
case Instruction.PackHalf2x16:
|
||||
return PackHalf2x16(context, operation);
|
||||
|
||||
case Instruction.Shuffle:
|
||||
return Shuffle(context, operation);
|
||||
|
||||
case Instruction.Store:
|
||||
return Store(context, operation);
|
||||
|
||||
|
@ -13,14 +13,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
AggregateType dstType = GetSrcVarType(operation.Inst, 0);
|
||||
|
||||
string arg = GetSoureExpr(context, operation.GetSource(0), dstType);
|
||||
char component = "xyzw"[operation.Index];
|
||||
|
||||
if (context.HostCapabilities.SupportsShaderBallot)
|
||||
{
|
||||
return $"unpackUint2x32(ballotARB({arg})).x";
|
||||
return $"unpackUint2x32(ballotARB({arg})).{component}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"subgroupBallot({arg}).x";
|
||||
return $"subgroupBallot({arg}).{component}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,10 +108,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3);
|
||||
Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3);
|
||||
Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3);
|
||||
Add(Instruction.Shuffle, InstType.CallQuaternary, HelperFunctionNames.Shuffle);
|
||||
Add(Instruction.ShuffleDown, InstType.CallQuaternary, HelperFunctionNames.ShuffleDown);
|
||||
Add(Instruction.ShuffleUp, InstType.CallQuaternary, HelperFunctionNames.ShuffleUp);
|
||||
Add(Instruction.ShuffleXor, InstType.CallQuaternary, HelperFunctionNames.ShuffleXor);
|
||||
Add(Instruction.Shuffle, InstType.Special);
|
||||
Add(Instruction.ShuffleDown, InstType.CallBinary, "subgroupShuffleDown");
|
||||
Add(Instruction.ShuffleUp, InstType.CallBinary, "subgroupShuffleUp");
|
||||
Add(Instruction.ShuffleXor, InstType.CallBinary, "subgroupShuffleXor");
|
||||
Add(Instruction.Sine, InstType.CallUnary, "sin");
|
||||
Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt");
|
||||
Add(Instruction.Store, InstType.Special);
|
||||
|
@ -0,0 +1,25 @@
|
||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
{
|
||||
static class InstGenShuffle
|
||||
{
|
||||
public static string Shuffle(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
string value = GetSoureExpr(context, operation.GetSource(0), AggregateType.FP32);
|
||||
string index = GetSoureExpr(context, operation.GetSource(1), AggregateType.U32);
|
||||
|
||||
if (context.HostCapabilities.SupportsShaderBallot)
|
||||
{
|
||||
return $"readInvocationARB({value}, {index})";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"subgroupShuffle({value}, {index})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
|
||||
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
|
||||
IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool),
|
||||
IoVariable.GlobalId => ("gl_GlobalInvocationID", AggregateType.Vector3 | AggregateType.U32),
|
||||
IoVariable.InstanceId => ("gl_InstanceID", AggregateType.S32),
|
||||
IoVariable.InstanceIndex => ("gl_InstanceIndex", AggregateType.S32),
|
||||
IoVariable.InvocationId => ("gl_InvocationID", AggregateType.S32),
|
||||
|
@ -27,8 +27,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
public ILogger Logger { get; }
|
||||
public TargetApi TargetApi { get; }
|
||||
|
||||
public int InputVertices { get; }
|
||||
|
||||
public Dictionary<int, Instruction> ConstantBuffers { get; } = new();
|
||||
public Dictionary<int, Instruction> StorageBuffers { get; } = new();
|
||||
|
||||
@ -101,19 +99,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
Logger = parameters.Logger;
|
||||
TargetApi = parameters.TargetApi;
|
||||
|
||||
if (parameters.Definitions.Stage == ShaderStage.Geometry)
|
||||
{
|
||||
InputVertices = parameters.Definitions.InputTopology switch
|
||||
{
|
||||
InputTopology.Points => 1,
|
||||
InputTopology.Lines => 2,
|
||||
InputTopology.LinesAdjacency => 2,
|
||||
InputTopology.Triangles => 3,
|
||||
InputTopology.TrianglesAdjacency => 3,
|
||||
_ => throw new InvalidOperationException($"Invalid input topology \"{parameters.Definitions.InputTopology}\"."),
|
||||
};
|
||||
}
|
||||
|
||||
AddCapability(Capability.Shader);
|
||||
AddCapability(Capability.Float64);
|
||||
|
||||
|
@ -348,12 +348,103 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (IoMap.IsPerVertexBuiltIn(ioDefinition.IoVariable))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isOutput = ioDefinition.StorageKind.IsOutput();
|
||||
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
|
||||
|
||||
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq, firstLocation);
|
||||
}
|
||||
|
||||
DeclarePerVertexBlock(context);
|
||||
}
|
||||
|
||||
private static void DeclarePerVertexBlock(CodeGenContext context)
|
||||
{
|
||||
if (context.Definitions.Stage.IsVtg())
|
||||
{
|
||||
if (context.Definitions.Stage != ShaderStage.Vertex)
|
||||
{
|
||||
var perVertexInputStructType = CreatePerVertexStructType(context);
|
||||
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32;
|
||||
var perVertexInputArrayType = context.TypeArray(perVertexInputStructType, context.Constant(context.TypeU32(), arraySize));
|
||||
var perVertexInputPointerType = context.TypePointer(StorageClass.Input, perVertexInputArrayType);
|
||||
var perVertexInputVariable = context.Variable(perVertexInputPointerType, StorageClass.Input);
|
||||
|
||||
context.Name(perVertexInputVariable, "gl_in");
|
||||
|
||||
context.AddGlobalVariable(perVertexInputVariable);
|
||||
context.Inputs.Add(new IoDefinition(StorageKind.Input, IoVariable.Position), perVertexInputVariable);
|
||||
}
|
||||
|
||||
var perVertexOutputStructType = CreatePerVertexStructType(context);
|
||||
|
||||
void DecorateTfo(IoVariable ioVariable, int fieldIndex)
|
||||
{
|
||||
if (context.Definitions.TryGetTransformFeedbackOutput(ioVariable, 0, 0, out var transformFeedbackOutput))
|
||||
{
|
||||
context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.XfbBuffer, (LiteralInteger)transformFeedbackOutput.Buffer);
|
||||
context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.XfbStride, (LiteralInteger)transformFeedbackOutput.Stride);
|
||||
context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.Offset, (LiteralInteger)transformFeedbackOutput.Offset);
|
||||
}
|
||||
}
|
||||
|
||||
DecorateTfo(IoVariable.Position, 0);
|
||||
DecorateTfo(IoVariable.PointSize, 1);
|
||||
DecorateTfo(IoVariable.ClipDistance, 2);
|
||||
|
||||
SpvInstruction perVertexOutputArrayType;
|
||||
|
||||
if (context.Definitions.Stage == ShaderStage.TessellationControl)
|
||||
{
|
||||
int arraySize = context.Definitions.ThreadsPerInputPrimitive;
|
||||
perVertexOutputArrayType = context.TypeArray(perVertexOutputStructType, context.Constant(context.TypeU32(), arraySize));
|
||||
}
|
||||
else
|
||||
{
|
||||
perVertexOutputArrayType = perVertexOutputStructType;
|
||||
}
|
||||
|
||||
var perVertexOutputPointerType = context.TypePointer(StorageClass.Output, perVertexOutputArrayType);
|
||||
var perVertexOutputVariable = context.Variable(perVertexOutputPointerType, StorageClass.Output);
|
||||
|
||||
context.AddGlobalVariable(perVertexOutputVariable);
|
||||
context.Outputs.Add(new IoDefinition(StorageKind.Output, IoVariable.Position), perVertexOutputVariable);
|
||||
}
|
||||
}
|
||||
|
||||
private static SpvInstruction CreatePerVertexStructType(CodeGenContext context)
|
||||
{
|
||||
var vec4FloatType = context.TypeVector(context.TypeFP32(), 4);
|
||||
var floatType = context.TypeFP32();
|
||||
var array8FloatType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), 8));
|
||||
var array1FloatType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), 1));
|
||||
|
||||
var perVertexStructType = context.TypeStruct(true, vec4FloatType, floatType, array8FloatType, array1FloatType);
|
||||
|
||||
context.Name(perVertexStructType, "gl_PerVertex");
|
||||
|
||||
context.MemberName(perVertexStructType, 0, "gl_Position");
|
||||
context.MemberName(perVertexStructType, 1, "gl_PointSize");
|
||||
context.MemberName(perVertexStructType, 2, "gl_ClipDistance");
|
||||
context.MemberName(perVertexStructType, 3, "gl_CullDistance");
|
||||
|
||||
context.Decorate(perVertexStructType, Decoration.Block);
|
||||
|
||||
if (context.HostCapabilities.ReducedPrecision)
|
||||
{
|
||||
context.MemberDecorate(perVertexStructType, 0, Decoration.Invariant);
|
||||
}
|
||||
|
||||
context.MemberDecorate(perVertexStructType, 0, Decoration.BuiltIn, (LiteralInteger)BuiltIn.Position);
|
||||
context.MemberDecorate(perVertexStructType, 1, Decoration.BuiltIn, (LiteralInteger)BuiltIn.PointSize);
|
||||
context.MemberDecorate(perVertexStructType, 2, Decoration.BuiltIn, (LiteralInteger)BuiltIn.ClipDistance);
|
||||
context.MemberDecorate(perVertexStructType, 3, Decoration.BuiltIn, (LiteralInteger)BuiltIn.CullDistance);
|
||||
|
||||
return perVertexStructType;
|
||||
}
|
||||
|
||||
private static void DeclareInputOrOutput(
|
||||
@ -415,7 +506,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Definitions.Stage, isOutput))
|
||||
{
|
||||
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
|
||||
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32;
|
||||
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), arraySize));
|
||||
|
||||
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
|
||||
|
@ -231,7 +231,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
|
||||
|
||||
var maskVector = context.GroupNonUniformBallot(uvec4Type, execution, context.Get(AggregateType.Bool, source));
|
||||
var mask = context.CompositeExtract(context.TypeU32(), maskVector, (SpvLiteralInteger)0);
|
||||
var mask = context.CompositeExtract(context.TypeU32(), maskVector, (SpvLiteralInteger)operation.Index);
|
||||
|
||||
return new OperationResult(AggregateType.U32, mask);
|
||||
}
|
||||
@ -1100,117 +1100,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
private static OperationResult GenerateShuffle(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var x = context.GetFP32(operation.GetSource(0));
|
||||
var value = context.GetFP32(operation.GetSource(0));
|
||||
var index = context.GetU32(operation.GetSource(1));
|
||||
var mask = context.GetU32(operation.GetSource(2));
|
||||
|
||||
var const31 = context.Constant(context.TypeU32(), 31);
|
||||
var const8 = context.Constant(context.TypeU32(), 8);
|
||||
|
||||
var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31);
|
||||
var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
|
||||
var notSegMask = context.Not(context.TypeU32(), segMask);
|
||||
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
|
||||
var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask);
|
||||
|
||||
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
|
||||
|
||||
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
|
||||
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
|
||||
var srcThreadId = context.BitwiseOr(context.TypeU32(), indexNotSegMask, minThreadId);
|
||||
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
|
||||
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
|
||||
var result = context.Select(context.TypeFP32(), valid, value, x);
|
||||
|
||||
var validLocal = (AstOperand)operation.GetSource(3);
|
||||
|
||||
context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType, AggregateType.Bool, valid));
|
||||
var result = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index);
|
||||
|
||||
return new OperationResult(AggregateType.FP32, result);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateShuffleDown(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var x = context.GetFP32(operation.GetSource(0));
|
||||
var value = context.GetFP32(operation.GetSource(0));
|
||||
var index = context.GetU32(operation.GetSource(1));
|
||||
var mask = context.GetU32(operation.GetSource(2));
|
||||
|
||||
var const31 = context.Constant(context.TypeU32(), 31);
|
||||
var const8 = context.Constant(context.TypeU32(), 8);
|
||||
|
||||
var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31);
|
||||
var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
|
||||
var notSegMask = context.Not(context.TypeU32(), segMask);
|
||||
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
|
||||
|
||||
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
|
||||
|
||||
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
|
||||
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
|
||||
var srcThreadId = context.IAdd(context.TypeU32(), threadId, index);
|
||||
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
|
||||
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
|
||||
var result = context.Select(context.TypeFP32(), valid, value, x);
|
||||
|
||||
var validLocal = (AstOperand)operation.GetSource(3);
|
||||
|
||||
context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType, AggregateType.Bool, valid));
|
||||
var result = context.GroupNonUniformShuffleDown(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index);
|
||||
|
||||
return new OperationResult(AggregateType.FP32, result);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateShuffleUp(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var x = context.GetFP32(operation.GetSource(0));
|
||||
var value = context.GetFP32(operation.GetSource(0));
|
||||
var index = context.GetU32(operation.GetSource(1));
|
||||
var mask = context.GetU32(operation.GetSource(2));
|
||||
|
||||
var const31 = context.Constant(context.TypeU32(), 31);
|
||||
var const8 = context.Constant(context.TypeU32(), 8);
|
||||
|
||||
var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
|
||||
|
||||
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
|
||||
|
||||
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
|
||||
var srcThreadId = context.ISub(context.TypeU32(), threadId, index);
|
||||
var valid = context.SGreaterThanEqual(context.TypeBool(), srcThreadId, minThreadId);
|
||||
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
|
||||
var result = context.Select(context.TypeFP32(), valid, value, x);
|
||||
|
||||
var validLocal = (AstOperand)operation.GetSource(3);
|
||||
|
||||
context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType, AggregateType.Bool, valid));
|
||||
var result = context.GroupNonUniformShuffleUp(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index);
|
||||
|
||||
return new OperationResult(AggregateType.FP32, result);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateShuffleXor(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var x = context.GetFP32(operation.GetSource(0));
|
||||
var value = context.GetFP32(operation.GetSource(0));
|
||||
var index = context.GetU32(operation.GetSource(1));
|
||||
var mask = context.GetU32(operation.GetSource(2));
|
||||
|
||||
var const31 = context.Constant(context.TypeU32(), 31);
|
||||
var const8 = context.Constant(context.TypeU32(), 8);
|
||||
|
||||
var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31);
|
||||
var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
|
||||
var notSegMask = context.Not(context.TypeU32(), segMask);
|
||||
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
|
||||
|
||||
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
|
||||
|
||||
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
|
||||
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
|
||||
var srcThreadId = context.BitwiseXor(context.TypeU32(), threadId, index);
|
||||
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
|
||||
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
|
||||
var result = context.Select(context.TypeFP32(), valid, value, x);
|
||||
|
||||
var validLocal = (AstOperand)operation.GetSource(3);
|
||||
|
||||
context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType, AggregateType.Bool, valid));
|
||||
var result = context.GroupNonUniformShuffleXor(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index);
|
||||
|
||||
return new OperationResult(AggregateType.FP32, result);
|
||||
}
|
||||
@ -1788,6 +1711,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
StorageClass storageClass;
|
||||
SpvInstruction baseObj;
|
||||
int srcIndex = 0;
|
||||
IoVariable? perVertexBuiltIn = null;
|
||||
|
||||
switch (storageKind)
|
||||
{
|
||||
@ -1881,6 +1805,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
else
|
||||
{
|
||||
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
|
||||
|
||||
if (IoMap.IsPerVertexBuiltIn(ioVariable))
|
||||
{
|
||||
perVertexBuiltIn = ioVariable;
|
||||
ioVariable = IoVariable.Position;
|
||||
}
|
||||
}
|
||||
|
||||
varType &= AggregateType.ElementTypeMask;
|
||||
@ -1902,6 +1832,31 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
|
||||
int inputsCount = (isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
|
||||
|
||||
if (perVertexBuiltIn.HasValue)
|
||||
{
|
||||
int fieldIndex = IoMap.GetPerVertexStructFieldIndex(perVertexBuiltIn.Value);
|
||||
|
||||
var indexes = new SpvInstruction[inputsCount + 1];
|
||||
int index = 0;
|
||||
|
||||
if (IoMap.IsPerVertexArrayBuiltIn(storageKind, context.Definitions.Stage))
|
||||
{
|
||||
indexes[index++] = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
indexes[index++] = context.Constant(context.TypeS32(), fieldIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
indexes[index++] = context.Constant(context.TypeS32(), fieldIndex);
|
||||
}
|
||||
|
||||
for (; index < inputsCount + 1; srcIndex++, index++)
|
||||
{
|
||||
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
|
||||
}
|
||||
|
||||
return context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
|
||||
}
|
||||
|
||||
if (operation.Inst == Instruction.AtomicCompareAndSwap)
|
||||
{
|
||||
inputsCount--;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using static Spv.Specification;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
@ -21,6 +22,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
IoVariable.FragmentCoord => (BuiltIn.FragCoord, AggregateType.Vector4 | AggregateType.FP32),
|
||||
IoVariable.FragmentOutputDepth => (BuiltIn.FragDepth, AggregateType.FP32),
|
||||
IoVariable.FrontFacing => (BuiltIn.FrontFacing, AggregateType.Bool),
|
||||
IoVariable.GlobalId => (BuiltIn.GlobalInvocationId, AggregateType.Vector3 | AggregateType.U32),
|
||||
IoVariable.InstanceId => (BuiltIn.InstanceId, AggregateType.S32),
|
||||
IoVariable.InstanceIndex => (BuiltIn.InstanceIndex, AggregateType.S32),
|
||||
IoVariable.InvocationId => (BuiltIn.InvocationId, AggregateType.S32),
|
||||
@ -80,5 +82,43 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsPerVertexBuiltIn(IoVariable ioVariable)
|
||||
{
|
||||
switch (ioVariable)
|
||||
{
|
||||
case IoVariable.Position:
|
||||
case IoVariable.PointSize:
|
||||
case IoVariable.ClipDistance:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsPerVertexArrayBuiltIn(StorageKind storageKind, ShaderStage stage)
|
||||
{
|
||||
if (storageKind == StorageKind.Output)
|
||||
{
|
||||
return stage == ShaderStage.TessellationControl;
|
||||
}
|
||||
else
|
||||
{
|
||||
return stage == ShaderStage.TessellationControl ||
|
||||
stage == ShaderStage.TessellationEvaluation ||
|
||||
stage == ShaderStage.Geometry;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetPerVertexStructFieldIndex(IoVariable ioVariable)
|
||||
{
|
||||
return ioVariable switch
|
||||
{
|
||||
IoVariable.Position => 0,
|
||||
IoVariable.PointSize => 1,
|
||||
IoVariable.ClipDistance => 2,
|
||||
_ => throw new ArgumentException($"Invalid built-in variable {ioVariable}.")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +28,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
_poolLock = new object();
|
||||
}
|
||||
|
||||
private const HelperFunctionsMask NeedsInvocationIdMask =
|
||||
HelperFunctionsMask.Shuffle |
|
||||
HelperFunctionsMask.ShuffleDown |
|
||||
HelperFunctionsMask.ShuffleUp |
|
||||
HelperFunctionsMask.ShuffleXor |
|
||||
HelperFunctionsMask.SwizzleAdd;
|
||||
private const HelperFunctionsMask NeedsInvocationIdMask = HelperFunctionsMask.SwizzleAdd;
|
||||
|
||||
public static byte[] Generate(StructuredProgramInfo info, CodeGenParameters parameters)
|
||||
{
|
||||
@ -94,6 +89,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
context.AddCapability(Capability.DrawParameters);
|
||||
}
|
||||
|
||||
if (context.Definitions.Stage != ShaderStage.Fragment &&
|
||||
context.Definitions.Stage != ShaderStage.Geometry &&
|
||||
context.Definitions.Stage != ShaderStage.Compute &&
|
||||
(context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.Layer)) ||
|
||||
context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.ViewportIndex))))
|
||||
{
|
||||
context.AddExtension("SPV_EXT_shader_viewport_index_layer");
|
||||
context.AddCapability(Capability.ShaderViewportIndexLayerEXT);
|
||||
}
|
||||
|
||||
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.ViewportMask)))
|
||||
{
|
||||
context.AddExtension("SPV_NV_viewport_array2");
|
||||
@ -244,9 +249,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
_ => throw new InvalidOperationException($"Invalid output topology \"{context.Definitions.OutputTopology}\"."),
|
||||
});
|
||||
|
||||
int maxOutputVertices = context.Definitions.GpPassthrough ? context.InputVertices : context.Definitions.MaxOutputVertices;
|
||||
|
||||
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)maxOutputVertices);
|
||||
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Definitions.MaxOutputVertices);
|
||||
}
|
||||
else if (context.Definitions.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
|
@ -10,11 +10,5 @@ namespace Ryujinx.Graphics.Shader
|
||||
public const int NvnBaseVertexByteOffset = 0x640;
|
||||
public const int NvnBaseInstanceByteOffset = 0x644;
|
||||
public const int NvnDrawIndexByteOffset = 0x648;
|
||||
|
||||
// Transform Feedback emulation.
|
||||
|
||||
public const int TfeInfoBinding = 0;
|
||||
public const int TfeBufferBaseBinding = 1;
|
||||
public const int TfeBuffersCount = 4;
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
_functionsWithId.Add(function);
|
||||
}
|
||||
|
||||
public IoUsage GetIoUsage()
|
||||
{
|
||||
return new IoUsage(UsedFeatures, ClipDistancesWritten, AttributeUsage.UsedOutputAttributes);
|
||||
}
|
||||
|
||||
public IEnumerator<DecodedFunction> GetEnumerator()
|
||||
{
|
||||
return _functions.Values.GetEnumerator();
|
||||
|
@ -297,6 +297,9 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
case InstName.Ssy:
|
||||
block.AddPushOp(op);
|
||||
break;
|
||||
case InstName.Shfl:
|
||||
context.SetUsedFeature(FeatureFlags.Shuffle);
|
||||
break;
|
||||
case InstName.Ldl:
|
||||
case InstName.Stl:
|
||||
context.SetUsedFeature(FeatureFlags.LocalMemory);
|
||||
@ -307,6 +310,23 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
case InstName.Sts:
|
||||
context.SetUsedFeature(FeatureFlags.SharedMemory);
|
||||
break;
|
||||
case InstName.Atom:
|
||||
case InstName.AtomCas:
|
||||
case InstName.Red:
|
||||
case InstName.Stg:
|
||||
case InstName.Suatom:
|
||||
case InstName.SuatomB:
|
||||
case InstName.SuatomB2:
|
||||
case InstName.SuatomCas:
|
||||
case InstName.SuatomCasB:
|
||||
case InstName.Sured:
|
||||
case InstName.SuredB:
|
||||
case InstName.Sust:
|
||||
case InstName.SustB:
|
||||
case InstName.SustD:
|
||||
case InstName.SustDB:
|
||||
context.SetUsedFeature(FeatureFlags.Store);
|
||||
break;
|
||||
}
|
||||
|
||||
block.OpCodes.Add(op);
|
||||
@ -421,6 +441,12 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
context.SetUsedFeature(FeatureFlags.RtLayer);
|
||||
}
|
||||
break;
|
||||
case AttributeConsts.ViewportIndex:
|
||||
if (definitions.Stage != ShaderStage.Fragment)
|
||||
{
|
||||
context.SetUsedFeature(FeatureFlags.ViewportIndex);
|
||||
}
|
||||
break;
|
||||
case AttributeConsts.ClipDistance0:
|
||||
case AttributeConsts.ClipDistance1:
|
||||
case AttributeConsts.ClipDistance2:
|
||||
@ -429,11 +455,17 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
case AttributeConsts.ClipDistance5:
|
||||
case AttributeConsts.ClipDistance6:
|
||||
case AttributeConsts.ClipDistance7:
|
||||
if (definitions.Stage == ShaderStage.Vertex)
|
||||
if (definitions.Stage.IsVtg())
|
||||
{
|
||||
context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4);
|
||||
}
|
||||
break;
|
||||
case AttributeConsts.ViewportMask:
|
||||
if (definitions.Stage != ShaderStage.Fragment)
|
||||
{
|
||||
context.SetUsedFeature(FeatureFlags.ViewportMask);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -128,7 +128,26 @@ namespace Ryujinx.Graphics.Shader
|
||||
/// <returns>GPU graphics state</returns>
|
||||
GpuGraphicsState QueryGraphicsState()
|
||||
{
|
||||
return default;
|
||||
return new GpuGraphicsState(
|
||||
false,
|
||||
InputTopology.Points,
|
||||
false,
|
||||
TessPatchType.Triangles,
|
||||
TessSpacing.EqualSpacing,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
1f,
|
||||
AlphaTestOp.Always,
|
||||
0f,
|
||||
default,
|
||||
true,
|
||||
default,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -194,6 +213,15 @@ namespace Ryujinx.Graphics.Shader
|
||||
return 16;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host shader subgroup size.
|
||||
/// </summary>
|
||||
/// <returns>Host shader subgroup size in invocations</returns>
|
||||
int QueryHostSubgroupSize()
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for texture formats with BGRA component order (such as BGRA8).
|
||||
/// </summary>
|
||||
|
@ -25,6 +25,19 @@ namespace Ryujinx.Graphics.Shader
|
||||
}
|
||||
|
||||
public static int ToInputVertices(this InputTopology topology)
|
||||
{
|
||||
return topology switch
|
||||
{
|
||||
InputTopology.Points => 1,
|
||||
InputTopology.Lines => 2,
|
||||
InputTopology.LinesAdjacency => 4,
|
||||
InputTopology.Triangles => 3,
|
||||
InputTopology.TrianglesAdjacency => 6,
|
||||
_ => 1,
|
||||
};
|
||||
}
|
||||
|
||||
public static int ToInputVerticesNoAdjacency(this InputTopology topology)
|
||||
{
|
||||
return topology switch
|
||||
{
|
||||
|
@ -63,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
{
|
||||
value = AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P);
|
||||
|
||||
if (!context.TranslatorContext.Definitions.SupportsScaledVertexFormats &&
|
||||
if ((!context.TranslatorContext.Definitions.SupportsScaledVertexFormats || context.VertexAsCompute) &&
|
||||
context.TranslatorContext.Stage == ShaderStage.Vertex &&
|
||||
!op.O &&
|
||||
offset >= 0x80 &&
|
||||
|
@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
switch (op.SReg)
|
||||
{
|
||||
case SReg.LaneId:
|
||||
src = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId);
|
||||
src = EmitLoadSubgroupLaneId(context);
|
||||
break;
|
||||
|
||||
case SReg.InvocationId:
|
||||
@ -146,19 +146,19 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
break;
|
||||
|
||||
case SReg.EqMask:
|
||||
src = context.Load(StorageKind.Input, IoVariable.SubgroupEqMask, null, Const(0));
|
||||
src = EmitLoadSubgroupMask(context, IoVariable.SubgroupEqMask);
|
||||
break;
|
||||
case SReg.LtMask:
|
||||
src = context.Load(StorageKind.Input, IoVariable.SubgroupLtMask, null, Const(0));
|
||||
src = EmitLoadSubgroupMask(context, IoVariable.SubgroupLtMask);
|
||||
break;
|
||||
case SReg.LeMask:
|
||||
src = context.Load(StorageKind.Input, IoVariable.SubgroupLeMask, null, Const(0));
|
||||
src = EmitLoadSubgroupMask(context, IoVariable.SubgroupLeMask);
|
||||
break;
|
||||
case SReg.GtMask:
|
||||
src = context.Load(StorageKind.Input, IoVariable.SubgroupGtMask, null, Const(0));
|
||||
src = EmitLoadSubgroupMask(context, IoVariable.SubgroupGtMask);
|
||||
break;
|
||||
case SReg.GeMask:
|
||||
src = context.Load(StorageKind.Input, IoVariable.SubgroupGeMask, null, Const(0));
|
||||
src = EmitLoadSubgroupMask(context, IoVariable.SubgroupGeMask);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -169,6 +169,52 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.Copy(GetDest(op.Dest), src);
|
||||
}
|
||||
|
||||
private static Operand EmitLoadSubgroupLaneId(EmitterContext context)
|
||||
{
|
||||
if (context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize() <= 32)
|
||||
{
|
||||
return context.Load(StorageKind.Input, IoVariable.SubgroupLaneId);
|
||||
}
|
||||
|
||||
return context.BitwiseAnd(context.Load(StorageKind.Input, IoVariable.SubgroupLaneId), Const(0x1f));
|
||||
}
|
||||
|
||||
private static Operand EmitLoadSubgroupMask(EmitterContext context, IoVariable ioVariable)
|
||||
{
|
||||
int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize();
|
||||
|
||||
if (subgroupSize <= 32)
|
||||
{
|
||||
return context.Load(StorageKind.Input, ioVariable, null, Const(0));
|
||||
}
|
||||
else if (subgroupSize == 64)
|
||||
{
|
||||
Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId);
|
||||
Operand low = context.Load(StorageKind.Input, ioVariable, null, Const(0));
|
||||
Operand high = context.Load(StorageKind.Input, ioVariable, null, Const(1));
|
||||
|
||||
return context.ConditionalSelect(context.BitwiseAnd(laneId, Const(32)), high, low);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId);
|
||||
Operand element = context.ShiftRightU32(laneId, Const(5));
|
||||
|
||||
Operand res = context.Load(StorageKind.Input, ioVariable, null, Const(0));
|
||||
res = context.ConditionalSelect(
|
||||
context.ICompareEqual(element, Const(1)),
|
||||
context.Load(StorageKind.Input, ioVariable, null, Const(1)), res);
|
||||
res = context.ConditionalSelect(
|
||||
context.ICompareEqual(element, Const(2)),
|
||||
context.Load(StorageKind.Input, ioVariable, null, Const(2)), res);
|
||||
res = context.ConditionalSelect(
|
||||
context.ICompareEqual(element, Const(3)),
|
||||
context.Load(StorageKind.Input, ioVariable, null, Const(3)), res);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SelR(EmitterContext context)
|
||||
{
|
||||
InstSelR op = context.GetOp<InstSelR>();
|
||||
|
@ -10,10 +10,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
{
|
||||
static partial class InstEmit
|
||||
{
|
||||
private static readonly int[,] _maskLut = new int[,]
|
||||
private static readonly int[][] _maskLut = new int[][]
|
||||
{
|
||||
{ 0b0001, 0b0010, 0b0100, 0b1000, 0b0011, 0b1001, 0b1010, 0b1100 },
|
||||
{ 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b0000, 0b0000, 0b0000 },
|
||||
new int[] { 0b0001, 0b0010, 0b0100, 0b1000, 0b0011, 0b1001, 0b1010, 0b1100 },
|
||||
new int[] { 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b0000, 0b0000, 0b0000 },
|
||||
};
|
||||
|
||||
public const bool Sample1DAs2D = true;
|
||||
@ -605,7 +605,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
Operand[] rd1 = new Operand[2] { ConstF(0), ConstF(0) };
|
||||
|
||||
int handle = imm;
|
||||
int componentMask = _maskLut[dest2 == RegisterConsts.RegisterZeroIndex ? 0 : 1, writeMask];
|
||||
int componentMask = _maskLut[dest2 == RegisterConsts.RegisterZeroIndex ? 0 : 1][writeMask];
|
||||
|
||||
int componentsCount = BitOperations.PopCount((uint)componentMask);
|
||||
|
||||
|
@ -50,20 +50,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
InstVote op = context.GetOp<InstVote>();
|
||||
|
||||
Operand pred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
Operand res = null;
|
||||
|
||||
switch (op.VoteMode)
|
||||
{
|
||||
case VoteMode.All:
|
||||
res = context.VoteAll(pred);
|
||||
break;
|
||||
case VoteMode.Any:
|
||||
res = context.VoteAny(pred);
|
||||
break;
|
||||
case VoteMode.Eq:
|
||||
res = context.VoteAllEqual(pred);
|
||||
break;
|
||||
}
|
||||
Operand res = EmitVote(context, op.VoteMode, pred);
|
||||
|
||||
if (res != null)
|
||||
{
|
||||
@ -76,7 +63,81 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
|
||||
if (op.Dest != RegisterConsts.RegisterZeroIndex)
|
||||
{
|
||||
context.Copy(GetDest(op.Dest), context.Ballot(pred));
|
||||
context.Copy(GetDest(op.Dest), EmitBallot(context, pred));
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand EmitVote(EmitterContext context, VoteMode voteMode, Operand pred)
|
||||
{
|
||||
int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize();
|
||||
|
||||
if (subgroupSize <= 32)
|
||||
{
|
||||
return voteMode switch
|
||||
{
|
||||
VoteMode.All => context.VoteAll(pred),
|
||||
VoteMode.Any => context.VoteAny(pred),
|
||||
VoteMode.Eq => context.VoteAllEqual(pred),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
// Emulate vote with ballot masks.
|
||||
// We do that when the GPU thread count is not 32,
|
||||
// since the shader code assumes it is 32.
|
||||
// allInvocations => ballot(pred) == ballot(true),
|
||||
// anyInvocation => ballot(pred) != 0,
|
||||
// allInvocationsEqual => ballot(pred) == balot(true) || ballot(pred) == 0
|
||||
Operand ballotMask = EmitBallot(context, pred);
|
||||
|
||||
Operand AllTrue() => context.ICompareEqual(ballotMask, EmitBallot(context, Const(IrConsts.True)));
|
||||
|
||||
return voteMode switch
|
||||
{
|
||||
VoteMode.All => AllTrue(),
|
||||
VoteMode.Any => context.ICompareNotEqual(ballotMask, Const(0)),
|
||||
VoteMode.Eq => context.BitwiseOr(AllTrue(), context.ICompareEqual(ballotMask, Const(0))),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static Operand EmitBallot(EmitterContext context, Operand pred)
|
||||
{
|
||||
int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize();
|
||||
|
||||
if (subgroupSize <= 32)
|
||||
{
|
||||
return context.Ballot(pred, 0);
|
||||
}
|
||||
else if (subgroupSize == 64)
|
||||
{
|
||||
// TODO: Add support for vector destination and do that with a single operation.
|
||||
|
||||
Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId);
|
||||
Operand low = context.Ballot(pred, 0);
|
||||
Operand high = context.Ballot(pred, 1);
|
||||
|
||||
return context.ConditionalSelect(context.BitwiseAnd(laneId, Const(32)), high, low);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Add support for vector destination and do that with a single operation.
|
||||
|
||||
Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId);
|
||||
Operand element = context.ShiftRightU32(laneId, Const(5));
|
||||
|
||||
Operand res = context.Ballot(pred, 0);
|
||||
res = context.ConditionalSelect(
|
||||
context.ICompareEqual(element, Const(1)),
|
||||
context.Ballot(pred, 1), res);
|
||||
res = context.ConditionalSelect(
|
||||
context.ICompareEqual(element, Const(2)),
|
||||
context.Ballot(pred, 2), res);
|
||||
res = context.ConditionalSelect(
|
||||
context.ICompareEqual(element, Const(3)),
|
||||
context.Ballot(pred, 3), res);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||
FrontColorDiffuse,
|
||||
FrontColorSpecular,
|
||||
FrontFacing,
|
||||
GlobalId,
|
||||
InstanceId,
|
||||
InstanceIndex,
|
||||
InvocationId,
|
||||
|
22
src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs
Normal file
22
src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
public readonly struct ResourceReservationCounts
|
||||
{
|
||||
public readonly int ReservedConstantBuffers { get; }
|
||||
public readonly int ReservedStorageBuffers { get; }
|
||||
public readonly int ReservedTextures { get; }
|
||||
public readonly int ReservedImages { get; }
|
||||
|
||||
public ResourceReservationCounts(bool isTransformFeedbackEmulated, bool vertexAsCompute)
|
||||
{
|
||||
ResourceReservations reservations = new(isTransformFeedbackEmulated, vertexAsCompute);
|
||||
|
||||
ReservedConstantBuffers = reservations.ReservedConstantBuffers;
|
||||
ReservedStorageBuffers = reservations.ReservedStorageBuffers;
|
||||
ReservedTextures = reservations.ReservedTextures;
|
||||
ReservedImages = reservations.ReservedImages;
|
||||
}
|
||||
}
|
||||
}
|
@ -12,10 +12,6 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighS32.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\Shuffle.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleDown.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleUp.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleXor.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
public enum ShaderIdentification
|
||||
{
|
||||
None,
|
||||
GeometryLayerPassthrough,
|
||||
}
|
||||
}
|
@ -10,9 +10,10 @@ namespace Ryujinx.Graphics.Shader
|
||||
public ReadOnlyCollection<TextureDescriptor> Textures { get; }
|
||||
public ReadOnlyCollection<TextureDescriptor> Images { get; }
|
||||
|
||||
public ShaderIdentification Identification { get; }
|
||||
public int GpLayerInputAttribute { get; }
|
||||
public ShaderStage Stage { get; }
|
||||
public int GeometryVerticesPerPrimitive { get; }
|
||||
public int GeometryMaxOutputVertices { get; }
|
||||
public int ThreadsPerInputPrimitive { get; }
|
||||
public bool UsesFragCoord { get; }
|
||||
public bool UsesInstanceId { get; }
|
||||
public bool UsesDrawParameters { get; }
|
||||
@ -25,9 +26,10 @@ namespace Ryujinx.Graphics.Shader
|
||||
BufferDescriptor[] sBuffers,
|
||||
TextureDescriptor[] textures,
|
||||
TextureDescriptor[] images,
|
||||
ShaderIdentification identification,
|
||||
int gpLayerInputAttribute,
|
||||
ShaderStage stage,
|
||||
int geometryVerticesPerPrimitive,
|
||||
int geometryMaxOutputVertices,
|
||||
int threadsPerInputPrimitive,
|
||||
bool usesFragCoord,
|
||||
bool usesInstanceId,
|
||||
bool usesDrawParameters,
|
||||
@ -40,9 +42,10 @@ namespace Ryujinx.Graphics.Shader
|
||||
Textures = Array.AsReadOnly(textures);
|
||||
Images = Array.AsReadOnly(images);
|
||||
|
||||
Identification = identification;
|
||||
GpLayerInputAttribute = gpLayerInputAttribute;
|
||||
Stage = stage;
|
||||
GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive;
|
||||
GeometryMaxOutputVertices = geometryMaxOutputVertices;
|
||||
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
|
||||
UsesFragCoord = usesFragCoord;
|
||||
UsesInstanceId = usesInstanceId;
|
||||
UsesDrawParameters = usesDrawParameters;
|
||||
|
@ -23,5 +23,18 @@ namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
return stage == ShaderStage.Vertex || stage == ShaderStage.Fragment || stage == ShaderStage.Compute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the shader stage is vertex, tessellation or geometry.
|
||||
/// </summary>
|
||||
/// <param name="stage">Shader stage</param>
|
||||
/// <returns>True if the shader stage is vertex, tessellation or geometry, false otherwise</returns>
|
||||
public static bool IsVtg(this ShaderStage stage)
|
||||
{
|
||||
return stage == ShaderStage.Vertex ||
|
||||
stage == ShaderStage.TessellationControl ||
|
||||
stage == ShaderStage.TessellationEvaluation ||
|
||||
stage == ShaderStage.Geometry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
{
|
||||
MultiplyHighS32 = 1 << 2,
|
||||
MultiplyHighU32 = 1 << 3,
|
||||
Shuffle = 1 << 4,
|
||||
ShuffleDown = 1 << 5,
|
||||
ShuffleUp = 1 << 6,
|
||||
ShuffleXor = 1 << 7,
|
||||
SwizzleAdd = 1 << 10,
|
||||
FSI = 1 << 11,
|
||||
}
|
||||
|
@ -109,14 +109,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
Add(Instruction.PackDouble2x32, AggregateType.FP64, AggregateType.U32, AggregateType.U32);
|
||||
Add(Instruction.PackHalf2x16, AggregateType.U32, AggregateType.FP32, AggregateType.FP32);
|
||||
Add(Instruction.ReciprocalSquareRoot, AggregateType.Scalar, AggregateType.Scalar);
|
||||
Add(Instruction.Return, AggregateType.Void, AggregateType.U32);
|
||||
Add(Instruction.Round, AggregateType.Scalar, AggregateType.Scalar);
|
||||
Add(Instruction.ShiftLeft, AggregateType.S32, AggregateType.S32, AggregateType.S32);
|
||||
Add(Instruction.ShiftRightS32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
|
||||
Add(Instruction.ShiftRightU32, AggregateType.U32, AggregateType.U32, AggregateType.S32);
|
||||
Add(Instruction.Shuffle, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
|
||||
Add(Instruction.ShuffleDown, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
|
||||
Add(Instruction.ShuffleUp, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
|
||||
Add(Instruction.ShuffleXor, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
|
||||
Add(Instruction.Shuffle, AggregateType.FP32, AggregateType.FP32, AggregateType.U32);
|
||||
Add(Instruction.ShuffleDown, AggregateType.FP32, AggregateType.FP32, AggregateType.U32);
|
||||
Add(Instruction.ShuffleUp, AggregateType.FP32, AggregateType.FP32, AggregateType.U32);
|
||||
Add(Instruction.ShuffleXor, AggregateType.FP32, AggregateType.FP32, AggregateType.U32);
|
||||
Add(Instruction.Sine, AggregateType.Scalar, AggregateType.Scalar);
|
||||
Add(Instruction.SquareRoot, AggregateType.Scalar, AggregateType.Scalar);
|
||||
Add(Instruction.Store, AggregateType.Void);
|
||||
@ -131,7 +132,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
Add(Instruction.VoteAll, AggregateType.Bool, AggregateType.Bool);
|
||||
Add(Instruction.VoteAllEqual, AggregateType.Bool, AggregateType.Bool);
|
||||
Add(Instruction.VoteAny, AggregateType.Bool, AggregateType.Bool);
|
||||
#pragma warning restore IDE0055v
|
||||
#pragma warning restore IDE0055
|
||||
}
|
||||
|
||||
private static void Add(Instruction inst, AggregateType destType, params AggregateType[] srcTypes)
|
||||
|
@ -282,18 +282,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
case Instruction.MultiplyHighU32:
|
||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighU32;
|
||||
break;
|
||||
case Instruction.Shuffle:
|
||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.Shuffle;
|
||||
break;
|
||||
case Instruction.ShuffleDown:
|
||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleDown;
|
||||
break;
|
||||
case Instruction.ShuffleUp:
|
||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleUp;
|
||||
break;
|
||||
case Instruction.ShuffleXor:
|
||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleXor;
|
||||
break;
|
||||
case Instruction.SwizzleAdd:
|
||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.SwizzleAdd;
|
||||
break;
|
||||
|
@ -22,11 +22,13 @@ namespace Ryujinx.Graphics.Shader
|
||||
ViewportSize,
|
||||
FragmentRenderScaleCount,
|
||||
RenderScale,
|
||||
TfeOffset,
|
||||
TfeVertexCount,
|
||||
}
|
||||
|
||||
public struct SupportBuffer
|
||||
{
|
||||
internal const int Binding = 0;
|
||||
public const int Binding = 0;
|
||||
|
||||
public static readonly int FieldSize;
|
||||
public static readonly int RequiredSize;
|
||||
@ -38,6 +40,8 @@ namespace Ryujinx.Graphics.Shader
|
||||
public static readonly int FragmentRenderScaleCountOffset;
|
||||
public static readonly int GraphicsRenderScaleOffset;
|
||||
public static readonly int ComputeRenderScaleOffset;
|
||||
public static readonly int TfeOffsetOffset;
|
||||
public static readonly int TfeVertexCountOffset;
|
||||
|
||||
public const int FragmentIsBgraCount = 8;
|
||||
// One for the render target, 64 for the textures, and 8 for the images.
|
||||
@ -62,18 +66,22 @@ namespace Ryujinx.Graphics.Shader
|
||||
FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
|
||||
GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
|
||||
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
|
||||
TfeOffsetOffset = OffsetOf(ref instance, ref instance.TfeOffset);
|
||||
TfeVertexCountOffset = OffsetOf(ref instance, ref instance.TfeVertexCount);
|
||||
}
|
||||
|
||||
internal static StructureType GetStructureType()
|
||||
{
|
||||
return new StructureType(new[]
|
||||
{
|
||||
new StructureField(AggregateType.U32, "s_alpha_test"),
|
||||
new StructureField(AggregateType.Array | AggregateType.U32, "s_is_bgra", FragmentIsBgraCount),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_inverse"),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_size"),
|
||||
new StructureField(AggregateType.S32, "s_frag_scale_count"),
|
||||
new StructureField(AggregateType.Array | AggregateType.FP32, "s_render_scale", RenderScaleMaxCount),
|
||||
new StructureField(AggregateType.U32, "alpha_test"),
|
||||
new StructureField(AggregateType.Array | AggregateType.U32, "is_bgra", FragmentIsBgraCount),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "viewport_inverse"),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "viewport_size"),
|
||||
new StructureField(AggregateType.S32, "frag_scale_count"),
|
||||
new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"),
|
||||
new StructureField(AggregateType.S32, "tfe_vertex_count"),
|
||||
});
|
||||
}
|
||||
|
||||
@ -85,5 +93,8 @@ namespace Ryujinx.Graphics.Shader
|
||||
|
||||
// Render scale max count: 1 + 64 + 8. First scale is fragment output scale, others are textures/image inputs.
|
||||
public Array73<Vector4<float>> RenderScale;
|
||||
|
||||
public Vector4<int> TfeOffset;
|
||||
public Vector4<int> TfeVertexCount;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
public const int PrimitiveId = 0x060;
|
||||
public const int Layer = 0x064;
|
||||
public const int ViewportIndex = 0x068;
|
||||
public const int PositionX = 0x070;
|
||||
public const int PositionY = 0x074;
|
||||
public const int FrontColorDiffuseR = 0x280;
|
||||
@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
public const int TexCoordCount = 10;
|
||||
public const int TexCoordBase = 0x300;
|
||||
public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16;
|
||||
public const int ViewportMask = 0x3a0;
|
||||
public const int FrontFacing = 0x3fc;
|
||||
|
||||
public const int UserAttributesCount = 32;
|
||||
|
@ -14,6 +14,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
public TranslatorContext TranslatorContext { get; }
|
||||
public ResourceManager ResourceManager { get; }
|
||||
|
||||
public bool VertexAsCompute { get; }
|
||||
|
||||
public bool IsNonMain { get; }
|
||||
|
||||
public Block CurrBlock { get; set; }
|
||||
@ -59,11 +61,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
TranslatorContext translatorContext,
|
||||
ResourceManager resourceManager,
|
||||
DecodedProgram program,
|
||||
bool vertexAsCompute,
|
||||
bool isNonMain) : this()
|
||||
{
|
||||
TranslatorContext = translatorContext;
|
||||
ResourceManager = resourceManager;
|
||||
Program = program;
|
||||
VertexAsCompute = vertexAsCompute;
|
||||
IsNonMain = isNonMain;
|
||||
|
||||
EmitStart();
|
||||
@ -71,13 +75,87 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
private void EmitStart()
|
||||
{
|
||||
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex &&
|
||||
TranslatorContext.Options.TargetApi == TargetApi.Vulkan &&
|
||||
(TranslatorContext.Options.Flags & TranslationFlags.VertexA) == 0)
|
||||
if (TranslatorContext.Options.Flags.HasFlag(TranslationFlags.VertexA))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex && TranslatorContext.Options.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
|
||||
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize));
|
||||
}
|
||||
|
||||
if (VertexAsCompute)
|
||||
{
|
||||
int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||
int countFieldIndex = TranslatorContext.Stage == ShaderStage.Vertex
|
||||
? (int)VertexInfoBufferField.VertexCounts
|
||||
: (int)VertexInfoBufferField.GeometryCounts;
|
||||
|
||||
Operand outputVertexOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0));
|
||||
Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const(countFieldIndex), Const(0));
|
||||
Operand isVertexOob = this.ICompareGreaterOrEqualUnsigned(outputVertexOffset, vertexCount);
|
||||
|
||||
Operand lblVertexInBounds = Label();
|
||||
|
||||
this.BranchIfFalse(lblVertexInBounds, isVertexOob);
|
||||
this.Return();
|
||||
this.MarkLabel(lblVertexInBounds);
|
||||
|
||||
Operand outputInstanceOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1));
|
||||
Operand instanceCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(1));
|
||||
Operand firstVertex = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(2));
|
||||
Operand firstInstance = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(3));
|
||||
Operand ibBaseOffset = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.GeometryCounts), Const(3));
|
||||
Operand isInstanceOob = this.ICompareGreaterOrEqualUnsigned(outputInstanceOffset, instanceCount);
|
||||
|
||||
Operand lblInstanceInBounds = Label();
|
||||
|
||||
this.BranchIfFalse(lblInstanceInBounds, isInstanceOob);
|
||||
this.Return();
|
||||
this.MarkLabel(lblInstanceInBounds);
|
||||
|
||||
if (TranslatorContext.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
Operand vertexIndexVr = Local();
|
||||
|
||||
this.TextureSample(
|
||||
SamplerType.TextureBuffer,
|
||||
TextureFlags.IntCoords,
|
||||
ResourceManager.Reservations.IndexBufferTextureBinding,
|
||||
1,
|
||||
new[] { vertexIndexVr },
|
||||
new[] { this.IAdd(ibBaseOffset, outputVertexOffset) });
|
||||
|
||||
this.Store(StorageKind.LocalMemory, ResourceManager.LocalVertexIndexVertexRateMemoryId, this.IAdd(firstVertex, vertexIndexVr));
|
||||
this.Store(StorageKind.LocalMemory, ResourceManager.LocalVertexIndexInstanceRateMemoryId, this.IAdd(firstInstance, outputInstanceOffset));
|
||||
}
|
||||
else if (TranslatorContext.Stage == ShaderStage.Geometry)
|
||||
{
|
||||
int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices();
|
||||
|
||||
Operand baseVertex = this.IMultiply(outputVertexOffset, Const(inputVertices));
|
||||
|
||||
for (int index = 0; index < inputVertices; index++)
|
||||
{
|
||||
Operand vertexIndex = Local();
|
||||
|
||||
this.TextureSample(
|
||||
SamplerType.TextureBuffer,
|
||||
TextureFlags.IntCoords,
|
||||
ResourceManager.Reservations.TopologyRemapBufferTextureBinding,
|
||||
1,
|
||||
new[] { vertexIndex },
|
||||
new[] { this.IAdd(baseVertex, Const(index)) });
|
||||
|
||||
this.Store(StorageKind.LocalMemory, ResourceManager.LocalTopologyRemapMemoryId, Const(index), vertexIndex);
|
||||
}
|
||||
|
||||
this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputVertexCountMemoryId, Const(0));
|
||||
this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId, Const(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T GetOp<T>() where T : unmanaged
|
||||
@ -166,16 +244,21 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
public void PrepareForVertexReturn()
|
||||
{
|
||||
if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() && TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled())
|
||||
{
|
||||
Operand vertexCount = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(1));
|
||||
// TODO: Support transform feedback emulation on stages other than vertex.
|
||||
// Those stages might produce more primitives, so it needs a way to "compact" the output after it is written.
|
||||
|
||||
for (int tfbIndex = 0; tfbIndex < Constants.TfeBuffersCount; tfbIndex++)
|
||||
if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() &&
|
||||
TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled() &&
|
||||
TranslatorContext.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
Operand vertexCount = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.TfeVertexCount));
|
||||
|
||||
for (int tfbIndex = 0; tfbIndex < ResourceReservations.TfeBuffersCount; tfbIndex++)
|
||||
{
|
||||
var locations = TranslatorContext.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
|
||||
var stride = TranslatorContext.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
|
||||
|
||||
Operand baseOffset = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(0), Const(tfbIndex));
|
||||
Operand baseOffset = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.TfeOffset), Const(tfbIndex));
|
||||
Operand baseVertex = this.Load(StorageKind.Input, IoVariable.BaseVertex);
|
||||
Operand baseInstance = this.Load(StorageKind.Input, IoVariable.BaseInstance);
|
||||
Operand vertexIndex = this.Load(StorageKind.Input, IoVariable.VertexIndex);
|
||||
@ -200,7 +283,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
Operand offset = this.IAdd(baseOffset, Const(j));
|
||||
Operand value = Instructions.AttributeMap.GenerateAttributeLoad(this, null, location * 4, isOutput: true, isPerPatch: false);
|
||||
|
||||
this.Store(StorageKind.StorageBuffer, Constants.TfeBufferBaseBinding + tfbIndex, Const(0), offset, value);
|
||||
int binding = ResourceManager.Reservations.GetTfeBufferStorageBufferBinding(tfbIndex);
|
||||
|
||||
this.Store(StorageKind.StorageBuffer, binding, Const(0), offset, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,16 +310,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
|
||||
}
|
||||
|
||||
if (TranslatorContext.Definitions.Stage != ShaderStage.Geometry && TranslatorContext.HasLayerInputAttribute)
|
||||
{
|
||||
int attrVecIndex = TranslatorContext.GpLayerInputAttribute >> 2;
|
||||
int attrComponentIndex = TranslatorContext.GpLayerInputAttribute & 3;
|
||||
|
||||
Operand layer = this.Load(StorageKind.Output, IoVariable.UserDefined, null, Const(attrVecIndex), Const(attrComponentIndex));
|
||||
|
||||
this.Store(StorageKind.Output, IoVariable.Layer, null, layer);
|
||||
}
|
||||
}
|
||||
|
||||
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
|
||||
@ -308,9 +383,30 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
if (TranslatorContext.Definitions.GpPassthrough && !TranslatorContext.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
|
||||
{
|
||||
int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices();
|
||||
int inputStart, inputEnd, inputStep;
|
||||
|
||||
for (int primIndex = 0; primIndex < inputVertices; primIndex++)
|
||||
InputTopology topology = TranslatorContext.Definitions.InputTopology;
|
||||
|
||||
if (topology == InputTopology.LinesAdjacency)
|
||||
{
|
||||
inputStart = 1;
|
||||
inputEnd = 3;
|
||||
inputStep = 1;
|
||||
}
|
||||
else if (topology == InputTopology.TrianglesAdjacency)
|
||||
{
|
||||
inputStart = 0;
|
||||
inputEnd = 6;
|
||||
inputStep = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
inputStart = 0;
|
||||
inputEnd = topology.ToInputVerticesNoAdjacency();
|
||||
inputStep = 1;
|
||||
}
|
||||
|
||||
for (int primIndex = inputStart; primIndex < inputEnd; primIndex += inputStep)
|
||||
{
|
||||
WritePositionOutput(primIndex);
|
||||
|
||||
@ -428,6 +524,65 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
}
|
||||
|
||||
if (VertexAsCompute)
|
||||
{
|
||||
if (TranslatorContext.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||
int vertexOutputSbBinding = ResourceManager.Reservations.VertexOutputStorageBufferBinding;
|
||||
int stride = ResourceManager.Reservations.OutputSizePerInvocation;
|
||||
|
||||
Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0));
|
||||
|
||||
Operand outputVertexOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0));
|
||||
Operand outputInstanceOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1));
|
||||
|
||||
Operand outputBaseVertex = this.IMultiply(outputInstanceOffset, vertexCount);
|
||||
|
||||
Operand baseOffset = this.IMultiply(this.IAdd(outputBaseVertex, outputVertexOffset), Const(stride));
|
||||
|
||||
for (int offset = 0; offset < stride; offset++)
|
||||
{
|
||||
Operand vertexOffset = this.IAdd(baseOffset, Const(offset));
|
||||
Operand value = this.Load(StorageKind.LocalMemory, ResourceManager.LocalVertexDataMemoryId, Const(offset));
|
||||
|
||||
this.Store(StorageKind.StorageBuffer, vertexOutputSbBinding, Const(0), vertexOffset, value);
|
||||
}
|
||||
}
|
||||
else if (TranslatorContext.Stage == ShaderStage.Geometry)
|
||||
{
|
||||
Operand lblLoopHead = Label();
|
||||
Operand lblExit = Label();
|
||||
|
||||
this.MarkLabel(lblLoopHead);
|
||||
|
||||
Operand writtenIndices = this.Load(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId);
|
||||
|
||||
int maxIndicesPerPrimitiveInvocation = TranslatorContext.Definitions.GetGeometryOutputIndexBufferStridePerInstance();
|
||||
int maxIndicesPerPrimitive = maxIndicesPerPrimitiveInvocation * TranslatorContext.Definitions.ThreadsPerInputPrimitive;
|
||||
|
||||
this.BranchIfTrue(lblExit, this.ICompareGreaterOrEqualUnsigned(writtenIndices, Const(maxIndicesPerPrimitiveInvocation)));
|
||||
|
||||
int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||
|
||||
Operand primitiveIndex = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0));
|
||||
Operand instanceIndex = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1));
|
||||
Operand invocationId = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(2));
|
||||
Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0));
|
||||
Operand primitiveId = this.IAdd(this.IMultiply(instanceIndex, vertexCount), primitiveIndex);
|
||||
Operand ibOffset = this.IMultiply(primitiveId, Const(maxIndicesPerPrimitive));
|
||||
ibOffset = this.IAdd(ibOffset, this.IMultiply(invocationId, Const(maxIndicesPerPrimitiveInvocation)));
|
||||
ibOffset = this.IAdd(ibOffset, writtenIndices);
|
||||
|
||||
this.Store(StorageKind.StorageBuffer, ResourceManager.Reservations.GeometryIndexOutputStorageBufferBinding, Const(0), ibOffset, Const(-1));
|
||||
this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId, this.IAdd(writtenIndices, Const(1)));
|
||||
|
||||
this.Branch(lblLoopHead);
|
||||
|
||||
this.MarkLabel(lblExit);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -112,9 +112,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return context.Add(Instruction.AtomicXor, storageKind, Local(), Const(binding), e0, e1, value);
|
||||
}
|
||||
|
||||
public static Operand Ballot(this EmitterContext context, Operand a)
|
||||
public static Operand Ballot(this EmitterContext context, Operand a, int index)
|
||||
{
|
||||
return context.Add(Instruction.Ballot, Local(), a);
|
||||
Operand dest = Local();
|
||||
|
||||
context.Add(new Operation(Instruction.Ballot, index, dest, a));
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Operand Barrier(this EmitterContext context)
|
||||
@ -782,21 +786,41 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return context.Add(Instruction.ShiftRightU32, Local(), a, b);
|
||||
}
|
||||
|
||||
public static Operand Shuffle(this EmitterContext context, Operand a, Operand b)
|
||||
{
|
||||
return context.Add(Instruction.Shuffle, Local(), a, b);
|
||||
}
|
||||
|
||||
public static (Operand, Operand) Shuffle(this EmitterContext context, Operand a, Operand b, Operand c)
|
||||
{
|
||||
return context.Add(Instruction.Shuffle, (Local(), Local()), a, b, c);
|
||||
}
|
||||
|
||||
public static Operand ShuffleDown(this EmitterContext context, Operand a, Operand b)
|
||||
{
|
||||
return context.Add(Instruction.ShuffleDown, Local(), a, b);
|
||||
}
|
||||
|
||||
public static (Operand, Operand) ShuffleDown(this EmitterContext context, Operand a, Operand b, Operand c)
|
||||
{
|
||||
return context.Add(Instruction.ShuffleDown, (Local(), Local()), a, b, c);
|
||||
}
|
||||
|
||||
public static Operand ShuffleUp(this EmitterContext context, Operand a, Operand b)
|
||||
{
|
||||
return context.Add(Instruction.ShuffleUp, Local(), a, b);
|
||||
}
|
||||
|
||||
public static (Operand, Operand) ShuffleUp(this EmitterContext context, Operand a, Operand b, Operand c)
|
||||
{
|
||||
return context.Add(Instruction.ShuffleUp, (Local(), Local()), a, b, c);
|
||||
}
|
||||
|
||||
public static Operand ShuffleXor(this EmitterContext context, Operand a, Operand b)
|
||||
{
|
||||
return context.Add(Instruction.ShuffleXor, Local(), a, b);
|
||||
}
|
||||
|
||||
public static (Operand, Operand) ShuffleXor(this EmitterContext context, Operand a, Operand b, Operand c)
|
||||
{
|
||||
return context.Add(Instruction.ShuffleXor, (Local(), Local()), a, b, c);
|
||||
@ -807,6 +831,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return context.Add(Instruction.Store, storageKind, null, e0, e1, value);
|
||||
}
|
||||
|
||||
public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand value)
|
||||
{
|
||||
return context.Add(Instruction.Store, storageKind, null, Const(binding), value);
|
||||
}
|
||||
|
||||
public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand value)
|
||||
{
|
||||
return context.Add(Instruction.Store, storageKind, null, Const(binding), e0, value);
|
||||
|
@ -18,8 +18,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
InstanceId = 1 << 3,
|
||||
DrawParameters = 1 << 4,
|
||||
RtLayer = 1 << 5,
|
||||
Shuffle = 1 << 6,
|
||||
ViewportIndex = 1 << 7,
|
||||
ViewportMask = 1 << 8,
|
||||
FixedFuncAttr = 1 << 9,
|
||||
LocalMemory = 1 << 10,
|
||||
SharedMemory = 1 << 11,
|
||||
Store = 1 << 12,
|
||||
VtgAsCompute = 1 << 13,
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user