Compare commits

...

15 Commits

Author SHA1 Message Date
eca8808649 Headless: Add support for Scaling Filters, Anti-aliasing and Exclusive Fullscreen (#5412)
* Headless: Added support for fullscreen option

* Headless: cleanup of fullscreen support

* Headless: fullscreen support : implemented proposed changes

* Headless: fullscreen support: cleanup

* Headless: exclusive fullscreen support: wip

* Headless: exclusive fullscreen support: add. windows scale interop

* Headless: exclusive fullscreen support: cleanup

* Headless: exclusive fullscreen support: cleanup

* Headless: fullscreen support: fix for OpenGL scaling

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: add. Vulkan fix

* Headless: fullscreen support: add. macOS fullscreen fix

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: exclusive fullscreen support: add. display selection logic

* Headless: exclusive fullscreen support: add. anti-aliasing + scaling-filter logic

* Headless: exclusive fullscreen support: upd. options to be case-insensitive

* Headless: exclusive fullscreen support: force default values for scaling + anti-aliasing options

* Headless: upd. OpenGL --fullscreen window size logic

* Headless: upd. fullscreen logic

* Headless: cleanup

* Headless: refac. DisplayId option naming

* Headless: refac. scaling + anti-aliasing option handling

* Headless: refac. namespace handling

* Headless: upd. imports ordering

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-09-25 23:40:16 +02:00
f6c3f1cdfd GPU: Discard data when getting texture before full clear (#5719)
* GPU: Discard data when getting texture before full clear

* Fix rules and order of clear checks

* Fix formatting
2023-09-25 23:07:03 +02:00
8026e1c804 nuget: bump Microsoft.NET.Test.Sdk from 17.6.3 to 17.7.2 (#5622)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.3 to 17.7.2.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.6.3...v17.7.2)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 21:29:09 +02:00
d9f9bbfaa6 Vulkan: Fix barriers on macOS (#5700)
* Use old method on macOS

* gdk suggestions

* Update src/Ryujinx.Graphics.Vulkan/TextureStorage.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update src/Ryujinx.Graphics.Vulkan/TextureStorage.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-09-23 19:32:36 -03:00
fe9e19d8cc [INFRA] Addition of basic contributor guides and docs framework. (#5581)
* Addition of basic contributor docs.

- Main contributor guide landing page.
- C# codestyle doc.
- Pull request guide doc.

All files and structure heavily inspired by the dotnet/runtime docs: https://github.com/dotnet/runtime/tree/main/docs

* fix typos and review changes

* Update XML doc requirement & conversation review.
2023-09-22 16:21:11 +02:00
fb55f57da7 Horizon: Migrate wlan and stubs latest services (#5708)
* Horizon: Migrate wlan and stubs latest services

This PR migrate empty wlan services, values are found by RE.
Latest firmwares added some other services which are now stubbed and up-to-date.

* Fix imports ordering
2023-09-20 22:55:27 +02:00
44862dce3e Stub unsupported BSD socket options (#5670)
* Stub unsupported BSD socket options

* Span.Clear
2023-09-19 19:35:56 +02:00
e601419bd4 make cheat list binding public (#5697) 2023-09-19 16:51:56 +00:00
d6bc0de785 use compiled bidning for localizations (#5684) 2023-09-18 22:20:59 +02:00
9f26fd3600 remove some usages of reflection binding (#5686) 2023-09-18 22:09:22 +02:00
88df636c87 Replace ShaderOutputLayer with equivalent ShaderViewportIndexLayerEXT capability (#5683) 2023-09-16 18:49:13 +02:00
7ccff037e8 Fix some Vulkan validation errors (mostly related to barriers) (#5603)
* Replace image barriers inside render pass with more generic memory barrier

* Remove forceStorage since it was creating images with storage bit for formats that are not StorageImage compatible

* Add missing flags on subpass dependency

* Don't call vkCmdSetScissor with a scissor count of 0

* One semaphore per swapchain image

* Remove compute stage from read to write barriers

* Try to improve Pipeline.Barrier nonsense

* Set PipelineStateFlags based on supported stages
2023-09-14 19:58:11 +02:00
a745913329 Fix gl_Layer to geometry shader change not writing gl_Layer (#5682)
* Fix gl_Layer to geometry shader change not writing gl_Layer

* Shader cache version bump
2023-09-14 14:53:53 -03:00
e6700b314f lbl: Migrate service to Horizon (#5628)
* lbl: Migrate service to Horizon

* Fix formatting

* Addresses gdkchan's feedback

* Fix comments
2023-09-14 09:50:19 +02:00
e2cfe6fe44 Fix shader GlobalToStorage pass when base address comes from local or shared memory (#5668)
* Fix shader GlobalToStorage pass when base address comes from local or shared memory

* Shader cache version bump
2023-09-11 01:22:18 +00:00
89 changed files with 1621 additions and 377 deletions

147
CONTRIBUTING.md Normal file
View 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.

View File

@ -21,7 +21,7 @@
<PackageVersion Include="LibHac" Version="0.18.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NUnit" Version="3.13.3" />

40
docs/README.md Normal file
View 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

View 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
View 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.

View File

@ -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);
}

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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; }

View File

@ -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>

View File

@ -335,6 +335,45 @@ namespace Ryujinx.Graphics.GAL
return 1;
}
/// <summary>
/// Checks if the texture format is a depth or depth-stencil format.
/// </summary>
/// <param name="format">Texture format</param>
/// <returns>True if the format is a depth or depth-stencil format, false otherwise</returns>
public static bool HasDepth(this Format format)
{
switch (format)
{
case Format.D16Unorm:
case Format.D24UnormS8Uint:
case Format.S8UintD24Unorm:
case Format.D32Float:
case Format.D32FloatS8Uint:
return true;
}
return false;
}
/// <summary>
/// Checks if the texture format is a stencil or depth-stencil format.
/// </summary>
/// <param name="format">Texture format</param>
/// <returns>True if the format is a stencil or depth-stencil format, false otherwise</returns>
public static bool HasStencil(this Format format)
{
switch (format)
{
case Format.D24UnormS8Uint:
case Format.S8UintD24Unorm:
case Format.D32FloatS8Uint:
case Format.S8Uint:
return true;
}
return false;
}
/// <summary>
/// Checks if the texture format is valid to use as image format.
/// </summary>

View File

@ -1,6 +1,7 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using System;
@ -806,25 +807,69 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
updateFlags |= RenderTargetUpdateFlags.Layered;
}
if (clearDepth || clearStencil)
bool clearDS = clearDepth || clearStencil;
if (clearDS)
{
updateFlags |= RenderTargetUpdateFlags.UpdateDepthStencil;
}
engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1);
// If there is a mismatch on the host clip region and the one explicitly defined by the guest
// on the screen scissor state, then we need to force only one texture to be bound to avoid
// host clipping.
var screenScissorState = _state.State.ScreenScissorState;
bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
if (clearDS || componentMask == 15)
{
// A full clear if scissor is disabled, or it matches the screen scissor state.
bool fullClear = screenScissorState.X == 0 && screenScissorState.Y == 0;
if (fullClear && clearAffectedByScissor && _state.State.ScissorState[0].Enable)
{
ref var scissorState = ref _state.State.ScissorState[0];
fullClear = scissorState.X1 == screenScissorState.X &&
scissorState.Y1 == screenScissorState.Y &&
scissorState.X2 >= screenScissorState.X + screenScissorState.Width &&
scissorState.Y2 >= screenScissorState.Y + screenScissorState.Height;
}
if (fullClear && clearDS)
{
// Must clear all aspects of the depth-stencil format.
FormatInfo dsFormat = _state.State.RtDepthStencilState.Format.Convert();
bool hasDepth = dsFormat.Format.HasDepth();
bool hasStencil = dsFormat.Format.HasStencil();
if (hasStencil && (!clearStencil || (clearAffectedByStencilMask && _state.State.StencilTestState.FrontMask != 0xff)))
{
fullClear = false;
}
else if (hasDepth && !clearDepth)
{
fullClear = false;
}
}
if (fullClear)
{
updateFlags |= RenderTargetUpdateFlags.DiscardClip;
}
}
engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1);
// Must happen after UpdateRenderTargetState to have up-to-date clip region values.
bool clipMismatch = (screenScissorState.X | screenScissorState.Y) != 0 ||
screenScissorState.Width != _channel.TextureManager.ClipRegionWidth ||
screenScissorState.Height != _channel.TextureManager.ClipRegionHeight;
bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
bool needsCustomScissor = !clearAffectedByScissor || clipMismatch;
// Scissor and rasterizer discard also affect clears.

View File

@ -33,6 +33,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
UpdateDepthStencil = 1 << 3,
/// <summary>
/// Indicates that the data in the clip region can be discarded for the next use.
/// </summary>
DiscardClip = 1 << 4,
/// <summary>
/// Default update flags for draw.
/// </summary>

View File

@ -447,6 +447,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool useControl = updateFlags.HasFlag(RenderTargetUpdateFlags.UseControl);
bool layered = updateFlags.HasFlag(RenderTargetUpdateFlags.Layered);
bool singleColor = updateFlags.HasFlag(RenderTargetUpdateFlags.SingleColor);
bool discard = updateFlags.HasFlag(RenderTargetUpdateFlags.DiscardClip);
int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets;
@ -486,6 +487,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
memoryManager,
colorState,
_vtgWritesRtLayer || layered,
discard,
samplesInX,
samplesInY,
sizeHint);
@ -525,6 +527,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
dsState,
dsSize,
_vtgWritesRtLayer || layered,
discard,
samplesInX,
samplesInY,
sizeHint);

View File

@ -570,6 +570,18 @@ namespace Ryujinx.Graphics.Gpu.Image
return Group.CheckDirty(this, consume);
}
/// <summary>
/// Discards all data for this texture.
/// This clears all dirty flags, modified flags, and pending copies from other textures.
/// It should be used if the texture data will be fully overwritten by the next use.
/// </summary>
public void DiscardData()
{
Group.DiscardData(this);
_dirty = false;
}
/// <summary>
/// Synchronizes guest and host memory.
/// This will overwrite the texture data with the texture data on the guest memory, if a CPU

View File

@ -311,7 +311,7 @@ namespace Ryujinx.Graphics.Gpu.Image
flags |= TextureSearchFlags.NoCreate;
}
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0);
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint: sizeHint);
texture?.SynchronizeMemory();
@ -324,6 +324,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
/// <param name="colorState">Color buffer texture to find or create</param>
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
/// <param name="discard">Indicates that the sizeHint region's data will be overwritten</param>
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
@ -332,6 +333,7 @@ namespace Ryujinx.Graphics.Gpu.Image
MemoryManager memoryManager,
RtColorState colorState,
bool layered,
bool discard,
int samplesInX,
int samplesInY,
Size sizeHint)
@ -398,7 +400,14 @@ namespace Ryujinx.Graphics.Gpu.Image
int layerSize = !isLinear ? colorState.LayerSize * 4 : 0;
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, layerSize);
var flags = TextureSearchFlags.WithUpscale;
if (discard)
{
flags |= TextureSearchFlags.DiscardData;
}
Texture texture = FindOrCreateTexture(memoryManager, flags, info, layerSize, sizeHint: sizeHint);
texture?.SynchronizeMemory();
@ -412,6 +421,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="dsState">Depth-stencil buffer texture to find or create</param>
/// <param name="size">Size of the depth-stencil texture</param>
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
/// <param name="discard">Indicates that the sizeHint region's data will be overwritten</param>
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
@ -421,6 +431,7 @@ namespace Ryujinx.Graphics.Gpu.Image
RtDepthStencilState dsState,
Size3D size,
bool layered,
bool discard,
int samplesInX,
int samplesInY,
Size sizeHint)
@ -465,7 +476,14 @@ namespace Ryujinx.Graphics.Gpu.Image
target,
formatInfo);
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4);
var flags = TextureSearchFlags.WithUpscale;
if (discard)
{
flags |= TextureSearchFlags.DiscardData;
}
Texture texture = FindOrCreateTexture(memoryManager, flags, info, dsState.LayerSize * 4, sizeHint: sizeHint);
texture?.SynchronizeMemory();
@ -500,6 +518,37 @@ namespace Ryujinx.Graphics.Gpu.Image
return Math.Clamp(widthAligned - alignment + 1, minimumWidth, widthAligned);
}
/// <summary>
/// Determines if texture data should be fully discarded
/// based on the size hint region and whether it is set to be discarded.
/// </summary>
/// <param name="discard">Whether the size hint region should be discarded</param>
/// <param name="texture">The texture being discarded</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
/// <returns>True if the data should be discarded, false otherwise</returns>
private static bool ShouldDiscard(bool discard, Texture texture, Size? sizeHint)
{
return discard &&
texture.Info.DepthOrLayers == 1 &&
sizeHint != null &&
texture.Width <= sizeHint.Value.Width &&
texture.Height <= sizeHint.Value.Height;
}
/// <summary>
/// Discards texture data if requested and possible.
/// </summary>
/// <param name="discard">Whether the size hint region should be discarded</param>
/// <param name="texture">The texture being discarded</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
private static void DiscardIfNeeded(bool discard, Texture texture, Size? sizeHint)
{
if (ShouldDiscard(discard, texture, sizeHint))
{
texture.DiscardData();
}
}
/// <summary>
/// Tries to find an existing texture, or create a new one if not found.
/// </summary>
@ -507,6 +556,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="flags">The texture search flags, defines texture comparison rules</param>
/// <param name="info">Texture information of the texture to be found or created</param>
/// <param name="layerSize">Size in bytes of a single texture layer</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
/// <param name="range">Optional ranges of physical memory where the texture data is located</param>
/// <returns>The texture</returns>
public Texture FindOrCreateTexture(
@ -514,9 +564,11 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureSearchFlags flags,
TextureInfo info,
int layerSize = 0,
Size? sizeHint = null,
MultiRange? range = null)
{
bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0;
bool discard = (flags & TextureSearchFlags.DiscardData) != 0;
TextureScaleMode scaleMode = IsUpscaleCompatible(info, (flags & TextureSearchFlags.WithUpscale) != 0);
@ -612,6 +664,8 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture != null)
{
DiscardIfNeeded(discard, texture, sizeHint);
texture.SynchronizeMemory();
return texture;
@ -907,7 +961,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// We need to synchronize before copying the old view data to the texture,
// otherwise the copied data would be overwritten by a future synchronization.
texture.InitializeData(false, setData);
texture.InitializeData(false, setData && !ShouldDiscard(discard, texture, sizeHint));
texture.Group.InitializeOverlaps();

View File

@ -278,6 +278,24 @@ namespace Ryujinx.Graphics.Gpu.Image
return dirty;
}
/// <summary>
/// Discards all data for a given texture.
/// This clears all dirty flags, modified flags, and pending copies from other textures.
/// </summary>
/// <param name="texture">The texture being discarded</param>
public void DiscardData(Texture texture)
{
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
{
for (int i = 0; i < regionCount; i++)
{
TextureGroupHandle group = _handles[baseHandle + i];
group.DiscardData();
}
});
}
/// <summary>
/// Synchronize memory for a given texture.
/// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.

View File

@ -2,7 +2,6 @@
using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image
@ -155,6 +154,24 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Discards all data for this handle.
/// This clears all dirty flags, modified flags, and pending copies from other handles.
/// </summary>
public void DiscardData()
{
Modified = false;
DeferredCopy = null;
foreach (RegionHandle handle in Handles)
{
if (handle.Dirty)
{
handle.Reprotect();
}
}
}
/// <summary>
/// Calculate a list of which views overlap this handle.
/// </summary>

View File

@ -14,5 +14,6 @@ namespace Ryujinx.Graphics.Gpu.Image
DepthAlias = 1 << 3,
WithUpscale = 1 << 4,
NoCreate = 1 << 5,
DiscardData = 1 << 6,
}
}

View File

@ -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 = 5551;
private const uint CodeGenVersion = 5682;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Gpu
{
pt.AcquireCallback(_context, pt.UserObj);
Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, pt.Range);
Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, range: pt.Range);
pt.Cache.Tick();

View File

@ -89,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");
@ -277,14 +287,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
localSizeZ);
}
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.AddCapability(Capability.ShaderLayer);
}
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
context.AddExecutionMode(spvFunc, ExecutionMode.Xfb);

View File

@ -1126,7 +1126,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// so we want to get the byte offset back, since each one of those word
// offsets are a new "local variable" which will not match.
if (operation.GetSource(0).AsgOp is Operation shiftRightOp &&
if (operation.GetSource(1).AsgOp is Operation shiftRightOp &&
shiftRightOp.Inst == Instruction.ShiftRightU32 &&
shiftRightOp.GetSource(1).Type == OperandType.Constant &&
shiftRightOp.GetSource(1).Value == 2)
@ -1158,9 +1158,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static bool TryGetLocalMemoryOffset(Operation operation, out int constOffset)
{
if (operation.GetSource(0).Type == OperandType.Constant)
Operand offset = operation.GetSource(1);
if (offset.Type == OperandType.Constant)
{
constOffset = operation.GetSource(0).Value;
constOffset = offset.Value;
return true;
}

View File

@ -576,6 +576,11 @@ namespace Ryujinx.Graphics.Shader.Translation
int outputAttributesMask = AttributeUsage.UsedOutputAttributes;
int layerOutputAttr = LayerOutputAttribute;
if (LayerOutputWritten)
{
outputAttributesMask |= 1 << ((layerOutputAttr - AttributeConsts.UserAttributeBase) / 16);
}
OutputTopology outputTopology;
int maxOutputVertices;

View File

@ -257,14 +257,22 @@ namespace Ryujinx.Graphics.Vulkan
if (realIndex != -1)
{
_colors[realIndex].Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
_colors[realIndex].Storage?.InsertReadToWriteBarrier(
cbs,
AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit,
insideRenderPass: true);
}
}
}
public void InsertClearBarrierDS(CommandBufferScoped cbs)
{
_depthStencil?.Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit);
_depthStencil?.Storage?.InsertReadToWriteBarrier(
cbs,
AccessFlags.DepthStencilAttachmentWriteBit,
PipelineStageFlags.LateFragmentTestsBit,
insideRenderPass: true);
}
}
}

View File

@ -41,6 +41,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsPreciseOcclusionQueries;
public readonly bool SupportsPipelineStatisticsQuery;
public readonly bool SupportsGeometryShader;
public readonly bool SupportsTessellationShader;
public readonly bool SupportsViewportArray2;
public readonly bool SupportsHostImportedMemory;
public readonly bool SupportsDepthClipControl;
@ -77,6 +78,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsPreciseOcclusionQueries,
bool supportsPipelineStatisticsQuery,
bool supportsGeometryShader,
bool supportsTessellationShader,
bool supportsViewportArray2,
bool supportsHostImportedMemory,
bool supportsDepthClipControl,
@ -112,6 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsPreciseOcclusionQueries = supportsPreciseOcclusionQueries;
SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery;
SupportsGeometryShader = supportsGeometryShader;
SupportsTessellationShader = supportsTessellationShader;
SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory;
SupportsDepthClipControl = supportsDepthClipControl;

View File

@ -149,10 +149,22 @@ namespace Ryujinx.Graphics.Vulkan
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
};
PipelineStageFlags pipelineStageFlags = PipelineStageFlags.VertexShaderBit | PipelineStageFlags.FragmentShaderBit;
if (Gd.Capabilities.SupportsGeometryShader)
{
pipelineStageFlags |= PipelineStageFlags.GeometryShaderBit;
}
if (Gd.Capabilities.SupportsTessellationShader)
{
pipelineStageFlags |= PipelineStageFlags.TessellationControlShaderBit | PipelineStageFlags.TessellationEvaluationShaderBit;
}
Gd.Api.CmdPipelineBarrier(
CommandBuffer,
PipelineStageFlags.FragmentShaderBit,
PipelineStageFlags.FragmentShaderBit,
pipelineStageFlags,
pipelineStageFlags,
0,
1,
memoryBarrier,

View File

@ -9,8 +9,12 @@ namespace Ryujinx.Graphics.Vulkan
{
static class PipelineConverter
{
private const AccessFlags SubpassSrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ColorAttachmentWriteBit;
private const AccessFlags SubpassDstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ShaderReadBit;
private const AccessFlags SubpassAccessMask =
AccessFlags.MemoryReadBit |
AccessFlags.MemoryWriteBit |
AccessFlags.ShaderReadBit |
AccessFlags.ColorAttachmentWriteBit |
AccessFlags.DepthStencilAttachmentWriteBit;
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
{
@ -132,8 +136,8 @@ namespace Ryujinx.Graphics.Vulkan
0,
PipelineStageFlags.AllGraphicsBit,
PipelineStageFlags.AllGraphicsBit,
SubpassSrcAccessMask,
SubpassDstAccessMask,
SubpassAccessMask,
SubpassAccessMask,
0);
}
@ -146,8 +150,8 @@ namespace Ryujinx.Graphics.Vulkan
0,
PipelineStageFlags.AllGraphicsBit,
PipelineStageFlags.AllGraphicsBit,
SubpassSrcAccessMask,
SubpassDstAccessMask,
SubpassAccessMask,
SubpassAccessMask,
0);
}

View File

@ -146,7 +146,10 @@ namespace Ryujinx.Graphics.Vulkan
private void RecordScissor(Vk api, CommandBuffer commandBuffer)
{
api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan());
if (ScissorsCount != 0)
{
api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan());
}
}
private readonly void RecordStencilMasks(Vk api, CommandBuffer commandBuffer)

View File

@ -78,7 +78,7 @@ namespace Ryujinx.Graphics.Vulkan
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample, forceStorage: true);
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var flags = ImageCreateFlags.CreateMutableFormatBit;
@ -291,7 +291,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage, bool forceStorage = false)
public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage)
{
var usage = DefaultUsageFlags;
@ -304,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan
usage |= ImageUsageFlags.ColorAttachmentBit;
}
if (((forceStorage && !format.IsDepthOrStencil()) || format.IsImageCompatible()) && (supportsMsStorage || !target.IsMultisample()))
if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample()))
{
usage |= ImageUsageFlags.StorageBit;
}
@ -440,25 +440,50 @@ namespace Ryujinx.Graphics.Vulkan
_lastModificationStage = stage;
}
public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags, bool insideRenderPass)
{
if (_lastReadAccess != AccessFlags.None)
{
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
var lastReadStage = _lastReadStage;
TextureView.InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
_imageAuto.Get(cbs).Value,
_lastReadAccess,
dstAccessFlags,
_lastReadStage,
dstStageFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
if (insideRenderPass)
{
// We can't have barrier from compute inside a render pass,
// as it is invalid to specify compute in the subpass dependency stage mask.
lastReadStage &= ~PipelineStageFlags.ComputeShaderBit;
}
if (lastReadStage != PipelineStageFlags.None)
{
// This would result in a validation error, but is
// required on MoltenVK as the generic barrier results in
// severe texture flickering in some scenarios.
if (_gd.IsMoltenVk)
{
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
TextureView.InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
_imageAuto.Get(cbs).Value,
_lastReadAccess,
dstAccessFlags,
_lastReadStage,
dstStageFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
}
else
{
TextureView.InsertMemoryBarrier(
_gd.Api,
cbs.CommandBuffer,
_lastReadAccess,
dstAccessFlags,
lastReadStage,
dstStageFlags);
}
_lastReadAccess = AccessFlags.None;
_lastReadStage = PipelineStageFlags.None;
@ -472,21 +497,36 @@ namespace Ryujinx.Graphics.Vulkan
if (_lastModificationAccess != AccessFlags.None)
{
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
TextureView.InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
_imageAuto.Get(cbs).Value,
_lastModificationAccess,
dstAccessFlags,
_lastModificationStage,
dstStageFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
// This would result in a validation error, but is
// required on MoltenVK as the generic barrier results in
// severe texture flickering in some scenarios.
if (_gd.IsMoltenVk)
{
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
TextureView.InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
_imageAuto.Get(cbs).Value,
_lastModificationAccess,
dstAccessFlags,
_lastModificationStage,
dstStageFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
}
else
{
TextureView.InsertMemoryBarrier(
_gd.Api,
cbs.CommandBuffer,
_lastModificationAccess,
dstAccessFlags,
_lastModificationStage,
dstStageFlags);
}
_lastModificationAccess = AccessFlags.None;
}

View File

@ -435,6 +435,34 @@ namespace Ryujinx.Graphics.Vulkan
ImageAspectFlags.ColorBit);
}
public static unsafe void InsertMemoryBarrier(
Vk api,
CommandBuffer commandBuffer,
AccessFlags srcAccessMask,
AccessFlags dstAccessMask,
PipelineStageFlags srcStageMask,
PipelineStageFlags dstStageMask)
{
MemoryBarrier memoryBarrier = new()
{
SType = StructureType.MemoryBarrier,
SrcAccessMask = srcAccessMask,
DstAccessMask = dstAccessMask,
};
api.CmdPipelineBarrier(
commandBuffer,
srcStageMask,
dstStageMask,
DependencyFlags.None,
1,
memoryBarrier,
0,
null,
0,
null);
}
public static unsafe void InsertImageBarrier(
Vk api,
CommandBuffer commandBuffer,

View File

@ -327,6 +327,7 @@ namespace Ryujinx.Graphics.Vulkan
features2.Features.OcclusionQueryPrecise,
_physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
_physicalDevice.PhysicalDeviceFeatures.TessellationShader,
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,

View File

@ -22,8 +22,10 @@ namespace Ryujinx.Graphics.Vulkan
private Image[] _swapchainImages;
private Auto<DisposableImageView>[] _swapchainImageViews;
private Semaphore _imageAvailableSemaphore;
private Semaphore _renderFinishedSemaphore;
private Semaphore[] _imageAvailableSemaphores;
private Semaphore[] _renderFinishedSemaphores;
private int _frameIndex;
private int _width;
private int _height;
@ -48,14 +50,6 @@ namespace Ryujinx.Graphics.Vulkan
_surface = surface;
CreateSwapchain();
var semaphoreCreateInfo = new SemaphoreCreateInfo
{
SType = StructureType.SemaphoreCreateInfo,
};
gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _imageAvailableSemaphore).ThrowOnError();
gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _renderFinishedSemaphore).ThrowOnError();
}
private void RecreateSwapchain()
@ -69,7 +63,22 @@ namespace Ryujinx.Graphics.Vulkan
}
// Destroy old Swapchain.
_gd.Api.DeviceWaitIdle(_device);
unsafe
{
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
{
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
}
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
{
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
}
}
_gd.SwapchainApi.DestroySwapchain(_device, oldSwapchain, Span<AllocationCallbacks>.Empty);
CreateSwapchain();
@ -151,6 +160,25 @@ namespace Ryujinx.Graphics.Vulkan
{
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format);
}
var semaphoreCreateInfo = new SemaphoreCreateInfo
{
SType = StructureType.SemaphoreCreateInfo,
};
_imageAvailableSemaphores = new Semaphore[imageCount];
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
{
_gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
}
_renderFinishedSemaphores = new Semaphore[imageCount];
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
{
_gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
}
}
private unsafe Auto<DisposableImageView> CreateSwapchainImageView(Image swapchainImage, VkFormat format)
@ -185,6 +213,7 @@ namespace Ryujinx.Graphics.Vulkan
{
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr);
}
var formatToReturn = availableFormats[0];
if (colorSpacePassthroughEnabled)
{
@ -212,6 +241,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
}
return formatToReturn;
}
@ -265,6 +295,7 @@ namespace Ryujinx.Graphics.Vulkan
_gd.PipelineInternal.AutoFlush.Present();
uint nextImage = 0;
int semaphoreIndex = _frameIndex++ % _imageAvailableSemaphores.Length;
while (true)
{
@ -272,7 +303,7 @@ namespace Ryujinx.Graphics.Vulkan
_device,
_swapchain,
ulong.MaxValue,
_imageAvailableSemaphore,
_imageAvailableSemaphores[semaphoreIndex],
new Fence(),
ref nextImage);
@ -411,12 +442,12 @@ namespace Ryujinx.Graphics.Vulkan
_gd.CommandBufferPool.Return(
cbs,
stackalloc[] { _imageAvailableSemaphore },
stackalloc[] { _imageAvailableSemaphores[semaphoreIndex] },
stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit },
stackalloc[] { _renderFinishedSemaphore });
stackalloc[] { _renderFinishedSemaphores[semaphoreIndex] });
// TODO: Present queue.
var semaphore = _renderFinishedSemaphore;
var semaphore = _renderFinishedSemaphores[semaphoreIndex];
var swapchain = _swapchain;
Result result;
@ -593,14 +624,21 @@ namespace Ryujinx.Graphics.Vulkan
{
unsafe
{
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null);
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null);
for (int i = 0; i < _swapchainImageViews.Length; i++)
{
_swapchainImageViews[i].Dispose();
}
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
{
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
}
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
{
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
}
_gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null);
}

View File

@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Services.Settings.Types;
using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Lbl;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
@ -15,7 +16,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
private readonly Apm.ManagerServer _apmManagerServer;
private readonly Apm.SystemManagerServer _apmSystemManagerServer;
private readonly Lbl.LblControllerServer _lblControllerServer;
private bool _vrModeEnabled;
#pragma warning disable CS0414, IDE0052 // Remove unread private member
@ -34,7 +34,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
_apmManagerServer = new Apm.ManagerServer(context);
_apmSystemManagerServer = new Apm.SystemManagerServer(context);
_lblControllerServer = new Lbl.LblControllerServer(context);
_acquiredSleepLockEvent = new KEvent(context.Device.System.KernelContext);
}
@ -215,13 +214,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
_vrModeEnabled = vrModeEnabled;
using var lblApi = new LblApi();
if (vrModeEnabled)
{
_lblControllerServer.EnableVrMode();
lblApi.EnableVrMode().AbortOnFailure();
}
else
{
_lblControllerServer.DisableVrMode();
lblApi.DisableVrMode().AbortOnFailure();
}
// TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used.

View File

@ -1,92 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Lbl
{
abstract class ILblController : IpcService
{
public ILblController(ServiceCtx context) { }
protected abstract void SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode);
protected abstract float GetCurrentBrightnessSettingForVrMode();
internal abstract void EnableVrMode();
internal abstract void DisableVrMode();
protected abstract bool IsVrModeEnabled();
[CommandCmif(17)]
// SetBrightnessReflectionDelayLevel(float, float)
public ResultCode SetBrightnessReflectionDelayLevel(ServiceCtx context)
{
return ResultCode.Success;
}
[CommandCmif(18)]
// GetBrightnessReflectionDelayLevel(float) -> float
public ResultCode GetBrightnessReflectionDelayLevel(ServiceCtx context)
{
context.ResponseData.Write(0.0f);
return ResultCode.Success;
}
[CommandCmif(21)]
// SetCurrentAmbientLightSensorMapping(unknown<0xC>)
public ResultCode SetCurrentAmbientLightSensorMapping(ServiceCtx context)
{
return ResultCode.Success;
}
[CommandCmif(22)]
// GetCurrentAmbientLightSensorMapping() -> unknown<0xC>
public ResultCode GetCurrentAmbientLightSensorMapping(ServiceCtx context)
{
return ResultCode.Success;
}
[CommandCmif(24)] // 3.0.0+
// SetCurrentBrightnessSettingForVrMode(float)
public ResultCode SetCurrentBrightnessSettingForVrMode(ServiceCtx context)
{
float currentBrightnessSettingForVrMode = context.RequestData.ReadSingle();
SetCurrentBrightnessSettingForVrMode(currentBrightnessSettingForVrMode);
return ResultCode.Success;
}
[CommandCmif(25)] // 3.0.0+
// GetCurrentBrightnessSettingForVrMode() -> float
public ResultCode GetCurrentBrightnessSettingForVrMode(ServiceCtx context)
{
float currentBrightnessSettingForVrMode = GetCurrentBrightnessSettingForVrMode();
context.ResponseData.Write(currentBrightnessSettingForVrMode);
return ResultCode.Success;
}
[CommandCmif(26)] // 3.0.0+
// EnableVrMode()
public ResultCode EnableVrMode(ServiceCtx context)
{
EnableVrMode();
return ResultCode.Success;
}
[CommandCmif(27)] // 3.0.0+
// DisableVrMode()
public ResultCode DisableVrMode(ServiceCtx context)
{
DisableVrMode();
return ResultCode.Success;
}
[CommandCmif(28)] // 3.0.0+
// IsVrModeEnabled() -> bool
public ResultCode IsVrModeEnabled(ServiceCtx context)
{
context.ResponseData.Write(IsVrModeEnabled());
return ResultCode.Success;
}
}
}

View File

@ -1,54 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Lbl
{
[Service("lbl")]
class LblControllerServer : ILblController
{
private bool _vrModeEnabled;
private float _currentBrightnessSettingForVrMode;
public LblControllerServer(ServiceCtx context) : base(context) { }
protected override void SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode)
{
if (float.IsNaN(currentBrightnessSettingForVrMode) || float.IsInfinity(currentBrightnessSettingForVrMode))
{
_currentBrightnessSettingForVrMode = 0.0f;
return;
}
_currentBrightnessSettingForVrMode = currentBrightnessSettingForVrMode;
}
protected override float GetCurrentBrightnessSettingForVrMode()
{
if (float.IsNaN(_currentBrightnessSettingForVrMode) || float.IsInfinity(_currentBrightnessSettingForVrMode))
{
return 0.0f;
}
return _currentBrightnessSettingForVrMode;
}
internal override void EnableVrMode()
{
_vrModeEnabled = true;
// NOTE: Service check _vrModeEnabled field value in a thread and then change the screen brightness.
// Since we don't support that. It's fine to do nothing.
}
internal override void DisableVrMode()
{
_vrModeEnabled = false;
// NOTE: Service check _vrModeEnabled field value in a thread and then change the screen brightness.
// Since we don't support that. It's fine to do nothing.
}
protected override bool IsVrModeEnabled()
{
return _vrModeEnabled;
}
}
}

View File

@ -6,6 +6,7 @@ using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Ipc;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon;
using Ryujinx.Horizon.Common;
using System;
using System.Buffers;
@ -172,6 +173,13 @@ namespace Ryujinx.HLE.HOS.Services
_selfProcess = KernelStatic.GetCurrentProcess();
_selfThread = KernelStatic.GetCurrentThread();
HorizonStatic.Register(
default,
_context.Syscall,
_selfProcess.CpuMemory,
_selfThread.ThreadContext,
(int)_selfThread.ThreadContext.GetX(1));
if (SmObjectFactory != null)
{
_context.Syscall.ManageNamedPort(out int serverPortHandle, "sm:", 50);

View File

@ -299,11 +299,21 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{
try
{
LinuxError result = WinSockHelper.ValidateSocketOption(option, level, write: false);
if (result != LinuxError.SUCCESS)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Invalid GetSockOpt Option: {option} Level: {level}");
return result;
}
if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName))
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}");
optionValue.Clear();
return LinuxError.EOPNOTSUPP;
return LinuxError.SUCCESS;
}
byte[] tempOptionValue = new byte[optionValue.Length];
@ -324,11 +334,20 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{
try
{
LinuxError result = WinSockHelper.ValidateSocketOption(option, level, write: true);
if (result != LinuxError.SUCCESS)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Invalid SetSockOpt Option: {option} Level: {level}");
return result;
}
if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName))
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}");
return LinuxError.EOPNOTSUPP;
return LinuxError.SUCCESS;
}
int value = optionValue.Length >= 4 ? MemoryMarshal.Read<int>(optionValue) : MemoryMarshal.Read<byte>(optionValue);

View File

@ -183,6 +183,104 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{ BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount },
};
[Flags]
private enum OptionDir
{
Get = 1 << 0,
Set = 1 << 1,
GetSet = Get | Set,
}
private static readonly Dictionary<BsdSocketOption, OptionDir> _validSoSocketOptionMap = new()
{
{ BsdSocketOption.SoDebug, OptionDir.GetSet },
{ BsdSocketOption.SoAcceptConn, OptionDir.Get },
{ BsdSocketOption.SoReuseAddr, OptionDir.GetSet },
{ BsdSocketOption.SoKeepAlive, OptionDir.GetSet },
{ BsdSocketOption.SoDontRoute, OptionDir.GetSet },
{ BsdSocketOption.SoBroadcast, OptionDir.GetSet },
{ BsdSocketOption.SoUseLoopBack, OptionDir.GetSet },
{ BsdSocketOption.SoLinger, OptionDir.GetSet },
{ BsdSocketOption.SoOobInline, OptionDir.GetSet },
{ BsdSocketOption.SoReusePort, OptionDir.GetSet },
{ BsdSocketOption.SoTimestamp, OptionDir.GetSet },
{ BsdSocketOption.SoNoSigpipe, OptionDir.GetSet },
{ BsdSocketOption.SoAcceptFilter, OptionDir.GetSet },
{ BsdSocketOption.SoSndBuf, OptionDir.GetSet },
{ BsdSocketOption.SoRcvBuf, OptionDir.GetSet },
{ BsdSocketOption.SoSndLoWat, OptionDir.GetSet },
{ BsdSocketOption.SoRcvLoWat, OptionDir.GetSet },
{ BsdSocketOption.SoSndTimeo, OptionDir.GetSet },
{ BsdSocketOption.SoRcvTimeo, OptionDir.GetSet },
{ BsdSocketOption.SoError, OptionDir.Get },
{ BsdSocketOption.SoType, OptionDir.Get },
{ BsdSocketOption.SoLabel, OptionDir.Get },
{ BsdSocketOption.SoPeerLabel, OptionDir.Get },
{ BsdSocketOption.SoListenQLimit, OptionDir.Get },
{ BsdSocketOption.SoListenQLen, OptionDir.Get },
{ BsdSocketOption.SoListenIncQLen, OptionDir.Get },
{ BsdSocketOption.SoSetFib, OptionDir.Set },
{ BsdSocketOption.SoUserCookie, OptionDir.Set },
{ BsdSocketOption.SoProtocol, OptionDir.Get },
{ BsdSocketOption.SoBinTime, OptionDir.GetSet },
{ BsdSocketOption.SoNoOffload, OptionDir.Set },
{ BsdSocketOption.SoNoDdp, OptionDir.Set },
{ BsdSocketOption.SoReusePortLb, OptionDir.GetSet },
};
private static readonly Dictionary<BsdSocketOption, OptionDir> _validIpSocketOptionMap = new()
{
{ BsdSocketOption.IpOptions, OptionDir.GetSet },
{ BsdSocketOption.IpHdrIncl, OptionDir.GetSet },
{ BsdSocketOption.IpTos, OptionDir.GetSet },
{ BsdSocketOption.IpTtl, OptionDir.GetSet },
{ BsdSocketOption.IpRecvOpts, OptionDir.GetSet },
{ BsdSocketOption.IpRecvRetOpts, OptionDir.GetSet },
{ BsdSocketOption.IpRecvDstAddr, OptionDir.GetSet },
{ BsdSocketOption.IpRetOpts, OptionDir.GetSet },
{ BsdSocketOption.IpMulticastIf, OptionDir.GetSet },
{ BsdSocketOption.IpMulticastTtl, OptionDir.GetSet },
{ BsdSocketOption.IpMulticastLoop, OptionDir.GetSet },
{ BsdSocketOption.IpAddMembership, OptionDir.GetSet },
{ BsdSocketOption.IpDropMembership, OptionDir.GetSet },
{ BsdSocketOption.IpMulticastVif, OptionDir.GetSet },
{ BsdSocketOption.IpRsvpOn, OptionDir.GetSet },
{ BsdSocketOption.IpRsvpOff, OptionDir.GetSet },
{ BsdSocketOption.IpRsvpVifOn, OptionDir.GetSet },
{ BsdSocketOption.IpRsvpVifOff, OptionDir.GetSet },
{ BsdSocketOption.IpPortRange, OptionDir.GetSet },
{ BsdSocketOption.IpRecvIf, OptionDir.GetSet },
{ BsdSocketOption.IpIpsecPolicy, OptionDir.GetSet },
{ BsdSocketOption.IpOnesBcast, OptionDir.GetSet },
{ BsdSocketOption.IpBindany, OptionDir.GetSet },
{ BsdSocketOption.IpBindMulti, OptionDir.GetSet },
{ BsdSocketOption.IpRssListenBucket, OptionDir.GetSet },
{ BsdSocketOption.IpOrigDstAddr, OptionDir.GetSet },
{ BsdSocketOption.IpRecvTtl, OptionDir.GetSet },
{ BsdSocketOption.IpMinTtl, OptionDir.GetSet },
{ BsdSocketOption.IpDontFrag, OptionDir.GetSet },
{ BsdSocketOption.IpRecvTos, OptionDir.GetSet },
{ BsdSocketOption.IpAddSourceMembership, OptionDir.GetSet },
{ BsdSocketOption.IpDropSourceMembership, OptionDir.GetSet },
{ BsdSocketOption.IpBlockSource, OptionDir.GetSet },
{ BsdSocketOption.IpUnblockSource, OptionDir.GetSet },
};
private static readonly Dictionary<BsdSocketOption, OptionDir> _validTcpSocketOptionMap = new()
{
{ BsdSocketOption.TcpNoDelay, OptionDir.GetSet },
{ BsdSocketOption.TcpMaxSeg, OptionDir.GetSet },
{ BsdSocketOption.TcpNoPush, OptionDir.GetSet },
{ BsdSocketOption.TcpNoOpt, OptionDir.GetSet },
{ BsdSocketOption.TcpMd5Sig, OptionDir.GetSet },
{ BsdSocketOption.TcpInfo, OptionDir.GetSet },
{ BsdSocketOption.TcpCongestion, OptionDir.GetSet },
{ BsdSocketOption.TcpKeepInit, OptionDir.GetSet },
{ BsdSocketOption.TcpKeepIdle, OptionDir.GetSet },
{ BsdSocketOption.TcpKeepIntvl, OptionDir.GetSet },
{ BsdSocketOption.TcpKeepCnt, OptionDir.GetSet },
};
public static LinuxError ConvertError(WsaError errorCode)
{
if (OperatingSystem.IsMacOS())
@ -221,5 +319,29 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
return table.TryGetValue(option, out name);
}
public static LinuxError ValidateSocketOption(BsdSocketOption option, SocketOptionLevel level, bool write)
{
var table = level switch
{
SocketOptionLevel.Socket => _validSoSocketOptionMap,
SocketOptionLevel.IP => _validIpSocketOptionMap,
SocketOptionLevel.Tcp => _validTcpSocketOptionMap,
_ => null,
};
OptionDir dir = write ? OptionDir.Set : OptionDir.Get;
if (table == null || !table.TryGetValue(option, out OptionDir validDir))
{
return LinuxError.ENOPROTOOPT;
}
else if ((validDir & dir) != dir)
{
return LinuxError.EOPNOTSUPP;
}
return LinuxError.SUCCESS;
}
}
}

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Wlan
{
[Service("wlan:inf")]
class IInfraManager : IpcService
{
public IInfraManager(ServiceCtx context) { }
}
}

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Wlan
{
[Service("wlan:lga")]
class ILocalGetActionFrame : IpcService
{
public ILocalGetActionFrame(ServiceCtx context) { }
}
}

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Wlan
{
[Service("wlan:lg")]
class ILocalGetFrame : IpcService
{
public ILocalGetFrame(ServiceCtx context) { }
}
}

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Wlan
{
[Service("wlan:lcl")]
class ILocalManager : IpcService
{
public ILocalManager(ServiceCtx context) { }
}
}

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Wlan
{
[Service("wlan:sg")]
class ISocketGetFrame : IpcService
{
public ISocketGetFrame(ServiceCtx context) { }
}
}

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Wlan
{
[Service("wlan:soc")]
class ISocketManager : IpcService
{
public ISocketManager(ServiceCtx context) { }
}
}

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Wlan
{
[Service("wlan:dtc")] // 6.0.0+
class IUnknown1 : IpcService
{
public IUnknown1(ServiceCtx context) { }
}
}

View File

@ -151,11 +151,15 @@ namespace Ryujinx.Headless.SDL2.OpenGL
GL.Clear(ClearBufferMask.ColorBufferBit);
SwapBuffers();
if (IsFullscreen)
if (IsExclusiveFullscreen)
{
Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
}
else if (IsFullscreen)
{
// NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow.
// we might have to amend this if people run this on a non-primary display set to a different resolution.
if (SDL_GetDisplayBounds(0, out SDL_Rect displayBounds) < 0)
if (SDL_GetDisplayBounds(DisplayId, out SDL_Rect displayBounds) < 0)
{
Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}");

View File

@ -14,9 +14,21 @@ namespace Ryujinx.Headless.SDL2
[Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
public string UserProfile { get; set; }
[Option("fullscreen", Required = false, HelpText = "Launch the game in fullscreen mode.")]
[Option("display-id", Required = false, Default = 0, HelpText = "Set the display to use - especially helpful for fullscreen mode. [0-n]")]
public int DisplayId { get; set; }
[Option("fullscreen", Required = false, Default = false, HelpText = "Launch the game in fullscreen mode.")]
public bool IsFullscreen { get; set; }
[Option("exclusive-fullscreen", Required = false, Default = false, HelpText = "Launch the game in exclusive fullscreen mode.")]
public bool IsExclusiveFullscreen { get; set; }
[Option("exclusive-fullscreen-width", Required = false, Default = 1920, HelpText = "Set horizontal resolution for exclusive fullscreen mode.")]
public int ExclusiveFullscreenWidth { get; set; }
[Option("exclusive-fullscreen-height", Required = false, Default = 1080, HelpText = "Set vertical resolution for exclusive fullscreen mode.")]
public int ExclusiveFullscreenHeight { get; set; }
// Input
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
@ -196,6 +208,15 @@ namespace Ryujinx.Headless.SDL2
[Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")]
public string PreferredGPUVendor { get; set; }
[Option("anti-aliasing", Required = false, Default = AntiAliasing.None, HelpText = "Set the type of anti aliasing being used. [None|Fxaa|SmaaLow|SmaaMedium|SmaaHigh|SmaaUltra]")]
public AntiAliasing AntiAliasing { get; set; }
[Option("scaling-filter", Required = false, Default = ScalingFilter.Bilinear, HelpText = "Set the scaling filter. [Bilinear|Nearest|Fsr]")]
public ScalingFilter ScalingFilter { get; set; }
[Option("scaling-filter-level", Required = false, Default = 0, HelpText = "Set the scaling filter intensity (currently only applies to FSR). [0-100]")]
public int ScalingFilterLevel { get; set; }
// Hacks
[Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")]

View File

@ -596,6 +596,13 @@ namespace Ryujinx.Headless.SDL2
_window = window;
_window.IsFullscreen = options.IsFullscreen;
_window.DisplayId = options.DisplayId;
_window.IsExclusiveFullscreen = options.IsExclusiveFullscreen;
_window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth;
_window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight;
_window.AntiAliasing = options.AntiAliasing;
_window.ScalingFilter = options.ScalingFilter;
_window.ScalingFilterLevel = options.ScalingFilterLevel;
_emulationContext = InitializeEmulationContext(window, renderer, options);

View File

@ -29,8 +29,16 @@ namespace Ryujinx.Headless.SDL2.Vulkan
protected override void InitializeRenderer()
{
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
if (IsExclusiveFullscreen)
{
Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
}
else
{
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
}
}
private static void BasicInvoke(Action action)

View File

@ -20,6 +20,8 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using static SDL2.SDL;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless.SDL2
@ -28,8 +30,9 @@ namespace Ryujinx.Headless.SDL2
{
protected const int DefaultWidth = 1280;
protected const int DefaultHeight = 720;
private const SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN;
private const int TargetFps = 60;
private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN;
private SDL_WindowFlags FullscreenFlag = 0;
private static readonly ConcurrentQueue<Action> _mainThreadActions = new();
@ -54,7 +57,14 @@ namespace Ryujinx.Headless.SDL2
public IHostUiTheme HostUiTheme { get; }
public int Width { get; private set; }
public int Height { get; private set; }
public int DisplayId { get; set; }
public bool IsFullscreen { get; set; }
public bool IsExclusiveFullscreen { get; set; }
public int ExclusiveFullscreenWidth { get; set; }
public int ExclusiveFullscreenHeight { get; set; }
public AntiAliasing AntiAliasing { get; set; }
public ScalingFilter ScalingFilter { get; set; }
public int ScalingFilterLevel { get; set; }
protected SDL2MouseDriver MouseDriver;
private readonly InputManager _inputManager;
@ -158,9 +168,24 @@ namespace Ryujinx.Headless.SDL2
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
SDL_WindowFlags fullscreenFlag = IsFullscreen ? SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
Width = DefaultWidth;
Height = DefaultHeight;
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | fullscreenFlag | GetWindowFlags());
if (IsExclusiveFullscreen)
{
Width = ExclusiveFullscreenWidth;
Height = ExclusiveFullscreenHeight;
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
}
else if (IsFullscreen)
{
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
}
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags());
if (WindowHandle == IntPtr.Zero)
{
@ -175,9 +200,6 @@ namespace Ryujinx.Headless.SDL2
_windowId = SDL_GetWindowID(WindowHandle);
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
Width = DefaultWidth;
Height = DefaultHeight;
}
private void HandleWindowEvent(SDL_Event evnt)
@ -189,8 +211,8 @@ namespace Ryujinx.Headless.SDL2
case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
// Unlike on Windows, this event fires on macOS when triggering fullscreen mode.
// And promptly crashes the process because `Renderer?.window.SetSize` is undefined.
// As we don't need this to fire in either case we can test for isFullscreen.
if (!IsFullscreen)
// As we don't need this to fire in either case we can test for fullscreen.
if (!IsFullscreen && !IsExclusiveFullscreen)
{
Width = evnt.window.data1;
Height = evnt.window.data2;
@ -225,6 +247,17 @@ namespace Ryujinx.Headless.SDL2
return Renderer.GetHardwareInfo().GpuVendor;
}
private void SetAntiAliasing()
{
Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)AntiAliasing);
}
private void SetScalingFilter()
{
Renderer?.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ScalingFilter);
Renderer?.Window.SetScalingFilterLevel(ScalingFilterLevel);
}
public void Render()
{
InitializeWindowRenderer();
@ -233,6 +266,10 @@ namespace Ryujinx.Headless.SDL2
InitializeRenderer();
SetAntiAliasing();
SetScalingFilter();
_gpuVendorName = GetGpuVendorName();
Device.Gpu.Renderer.RunLoop(() =>

View File

@ -6,8 +6,8 @@ namespace Ryujinx.Horizon.Bcat
{
internal class BcatIpcServer
{
private const int BcatMaxSessionsCount = 8;
private const int BcatTotalMaxSessionsCount = BcatMaxSessionsCount * 4;
private const int MaxSessionsCount = 8;
private const int TotalMaxSessionsCount = MaxSessionsCount * 4;
private const int PointerBufferSize = 0x400;
private const int MaxDomains = 64;
@ -17,7 +17,7 @@ namespace Ryujinx.Horizon.Bcat
private SmApi _sm;
private BcatServerManager _serverManager;
private static readonly ManagerOptions _bcatManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
internal void Initialize()
{
@ -26,13 +26,13 @@ namespace Ryujinx.Horizon.Bcat
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new BcatServerManager(allocator, _sm, MaxPortsCount, _bcatManagerOptions, BcatTotalMaxSessionsCount);
_serverManager = new BcatServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount);
#pragma warning disable IDE0055 // Disable formatting
_serverManager.RegisterServer((int)BcatPortIndex.Admin, ServiceName.Encode("bcat:a"), BcatMaxSessionsCount);
_serverManager.RegisterServer((int)BcatPortIndex.Manager, ServiceName.Encode("bcat:m"), BcatMaxSessionsCount);
_serverManager.RegisterServer((int)BcatPortIndex.User, ServiceName.Encode("bcat:u"), BcatMaxSessionsCount);
_serverManager.RegisterServer((int)BcatPortIndex.System, ServiceName.Encode("bcat:s"), BcatMaxSessionsCount);
_serverManager.RegisterServer((int)BcatPortIndex.Admin, ServiceName.Encode("bcat:a"), MaxSessionsCount);
_serverManager.RegisterServer((int)BcatPortIndex.Manager, ServiceName.Encode("bcat:m"), MaxSessionsCount);
_serverManager.RegisterServer((int)BcatPortIndex.User, ServiceName.Encode("bcat:u"), MaxSessionsCount);
_serverManager.RegisterServer((int)BcatPortIndex.System, ServiceName.Encode("bcat:s"), MaxSessionsCount);
#pragma warning restore IDE0055
}

View File

@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Horizon
{
static class HorizonStatic
public static class HorizonStatic
{
[ThreadStatic]
private static HorizonOptions _options;

View File

@ -0,0 +1,130 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Lbl;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Lbl.Ipc
{
partial class LblController : ILblController
{
private bool _vrModeEnabled;
private float _currentBrightnessSettingForVrMode;
[CmifCommand(17)]
public Result SetBrightnessReflectionDelayLevel(float unknown0, float unknown1)
{
// NOTE: Stubbed in system module.
return Result.Success;
}
[CmifCommand(18)]
public Result GetBrightnessReflectionDelayLevel(out float unknown1, float unknown0)
{
// NOTE: Stubbed in system module.
unknown1 = 0.0f;
return Result.Success;
}
[CmifCommand(19)]
public Result SetCurrentBrightnessMapping(float unknown0, float unknown1, float unknown2)
{
// NOTE: Stubbed in system module.
return Result.Success;
}
[CmifCommand(20)]
public Result GetCurrentBrightnessMapping(out float unknown0, out float unknown1, out float unknown2)
{
// NOTE: Stubbed in system module.
unknown0 = 0.0f;
unknown1 = 0.0f;
unknown2 = 0.0f;
return Result.Success;
}
[CmifCommand(21)]
public Result SetCurrentAmbientLightSensorMapping(float unknown0, float unknown1, float unknown2)
{
// NOTE: Stubbed in system module.
return Result.Success;
}
[CmifCommand(22)]
public Result GetCurrentAmbientLightSensorMapping(out float unknown0, out float unknown1, out float unknown2)
{
// NOTE: Stubbed in system module.
unknown0 = 0.0f;
unknown1 = 0.0f;
unknown2 = 0.0f;
return Result.Success;
}
[CmifCommand(24)]
public Result SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode)
{
if (float.IsNaN(currentBrightnessSettingForVrMode) || float.IsInfinity(currentBrightnessSettingForVrMode))
{
_currentBrightnessSettingForVrMode = 0.0f;
}
else
{
_currentBrightnessSettingForVrMode = currentBrightnessSettingForVrMode;
}
return Result.Success;
}
[CmifCommand(25)]
public Result GetCurrentBrightnessSettingForVrMode(out float currentBrightnessSettingForVrMode)
{
if (float.IsNaN(_currentBrightnessSettingForVrMode) || float.IsInfinity(_currentBrightnessSettingForVrMode))
{
currentBrightnessSettingForVrMode = 0.0f;
}
else
{
currentBrightnessSettingForVrMode = _currentBrightnessSettingForVrMode;
}
return Result.Success;
}
[CmifCommand(26)]
public Result EnableVrMode()
{
_vrModeEnabled = true;
// NOTE: The service checks _vrModeEnabled field value in a thread and then changes the screen brightness.
// Since we don't support that, it's fine to do nothing.
return Result.Success;
}
[CmifCommand(27)]
public Result DisableVrMode()
{
_vrModeEnabled = false;
// NOTE: The service checks _vrModeEnabled field value in a thread and then changes the screen brightness.
// Since we don't support that, it's fine to do nothing.
return Result.Success;
}
[CmifCommand(28)]
public Result IsVrModeEnabled(out bool vrModeEnabled)
{
vrModeEnabled = _vrModeEnabled;
return Result.Success;
}
}
}

View File

@ -0,0 +1,43 @@
using Ryujinx.Horizon.Lbl.Ipc;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
namespace Ryujinx.Horizon.Lbl
{
class LblIpcServer
{
private const int MaxSessionsCount = 5;
private const int PointerBufferSize = 0;
private const int MaxDomains = 0;
private const int MaxDomainObjects = 0;
private const int MaxPortsCount = 1;
private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private ServerManager _serverManager;
public void Initialize()
{
HeapAllocator allocator = new();
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount);
_serverManager.RegisterObjectForServer(new LblController(), ServiceName.Encode("lbl"), MaxSessionsCount);
}
public void ServiceRequests()
{
_serverManager.ServiceRequests();
}
public void Shutdown()
{
_serverManager.Dispose();
}
}
}

View File

@ -0,0 +1,17 @@
namespace Ryujinx.Horizon.Lbl
{
class LblMain : IService
{
public static void Main(ServiceTable serviceTable)
{
LblIpcServer ipcServer = new();
ipcServer.Initialize();
serviceTable.SignalServiceReady();
ipcServer.ServiceRequests();
ipcServer.Shutdown();
}
}
}

View File

@ -6,14 +6,14 @@ namespace Ryujinx.Horizon.LogManager
{
class LmIpcServer
{
private const int LogMaxSessionsCount = 42;
private const int MaxSessionsCount = 42;
private const int PointerBufferSize = 0x400;
private const int MaxDomains = 31;
private const int MaxDomainObjects = 61;
private const int MaxPortsCount = 1;
private static readonly ManagerOptions _logManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private ServerManager _serverManager;
@ -25,9 +25,9 @@ namespace Ryujinx.Horizon.LogManager
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, LogMaxSessionsCount);
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount);
_serverManager.RegisterObjectForServer(new LogService(), ServiceName.Encode("lm"), LogMaxSessionsCount);
_serverManager.RegisterObjectForServer(new LogService(), ServiceName.Encode("lm"), MaxSessionsCount);
}
public void ServiceRequests()

View File

@ -6,14 +6,14 @@ namespace Ryujinx.Horizon.MmNv
{
class MmNvIpcServer
{
private const int MmNvMaxSessionsCount = 9;
private const int MaxSessionsCount = 40;
private const int PointerBufferSize = 0;
private const int MaxDomains = 0;
private const int MaxDomainObjects = 0;
private const int MaxPortsCount = 1;
private static readonly ManagerOptions _mmNvOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private ServerManager _serverManager;
@ -25,9 +25,9 @@ namespace Ryujinx.Horizon.MmNv
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _mmNvOptions, MmNvMaxSessionsCount);
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount);
_serverManager.RegisterObjectForServer(new Request(), ServiceName.Encode("mm:u"), MmNvMaxSessionsCount);
_serverManager.RegisterObjectForServer(new Request(), ServiceName.Encode("mm:u"), MaxSessionsCount);
}
public void ServiceRequests()

View File

@ -6,15 +6,15 @@ namespace Ryujinx.Horizon.Prepo
{
class PrepoIpcServer
{
private const int PrepoMaxSessionsCount = 12;
private const int PrepoTotalMaxSessionsCount = PrepoMaxSessionsCount * 6;
private const int MaxSessionsCount = 12;
private const int TotalMaxSessionsCount = MaxSessionsCount * 6;
private const int PointerBufferSize = 0x80;
private const int MaxDomains = 64;
private const int MaxDomainObjects = 16;
private const int MaxPortsCount = 6;
private static readonly ManagerOptions _prepoManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private PrepoServerManager _serverManager;
@ -26,15 +26,15 @@ namespace Ryujinx.Horizon.Prepo
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _prepoManagerOptions, PrepoTotalMaxSessionsCount);
_serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount);
#pragma warning disable IDE0055 // Disable formatting
_serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), PrepoMaxSessionsCount); // 1.0.0-5.1.0
_serverManager.RegisterServer((int)PrepoPortIndex.Admin2, ServiceName.Encode("prepo:a2"), PrepoMaxSessionsCount); // 6.0.0+
_serverManager.RegisterServer((int)PrepoPortIndex.Manager, ServiceName.Encode("prepo:m"), PrepoMaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.User, ServiceName.Encode("prepo:u"), PrepoMaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.System, ServiceName.Encode("prepo:s"), PrepoMaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.Debug, ServiceName.Encode("prepo:d"), PrepoMaxSessionsCount); // 1.0.0
_serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), MaxSessionsCount); // 1.0.0-5.1.0
_serverManager.RegisterServer((int)PrepoPortIndex.Admin2, ServiceName.Encode("prepo:a2"), MaxSessionsCount); // 6.0.0+
_serverManager.RegisterServer((int)PrepoPortIndex.Manager, ServiceName.Encode("prepo:m"), MaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.User, ServiceName.Encode("prepo:u"), MaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.System, ServiceName.Encode("prepo:s"), MaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.Debug, ServiceName.Encode("prepo:d"), MaxSessionsCount); // 1.0.0
#pragma warning restore IDE0055
}

View File

@ -0,0 +1,20 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Lbl
{
interface ILblController : IServiceObject
{
Result SetBrightnessReflectionDelayLevel(float unknown0, float unknown1);
Result GetBrightnessReflectionDelayLevel(out float unknown1, float unknown0);
Result SetCurrentBrightnessMapping(float unknown0, float unknown1, float unknown2);
Result GetCurrentBrightnessMapping(out float unknown0, out float unknown1, out float unknown2);
Result SetCurrentAmbientLightSensorMapping(float unknown0, float unknown1, float unknown2);
Result GetCurrentAmbientLightSensorMapping(out float unknown0, out float unknown1, out float unknown2);
Result SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode);
Result GetCurrentBrightnessSettingForVrMode(out float currentBrightnessSettingForVrMode);
Result EnableVrMode();
Result DisableVrMode();
Result IsVrModeEnabled(out bool vrModeEnabled);
}
}

View File

@ -0,0 +1,43 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sm;
using System;
namespace Ryujinx.Horizon.Sdk.Lbl
{
public class LblApi : IDisposable
{
private const string LblName = "lbl";
private int _sessionHandle;
public LblApi()
{
using var smApi = new SmApi();
smApi.Initialize();
smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(LblName)).AbortOnFailure();
}
public Result EnableVrMode()
{
return ServiceUtil.SendRequest(out _, _sessionHandle, 26, sendPid: false, ReadOnlySpan<byte>.Empty);
}
public Result DisableVrMode()
{
return ServiceUtil.SendRequest(out _, _sessionHandle, 27, sendPid: false, ReadOnlySpan<byte>.Empty);
}
public void Dispose()
{
if (_sessionHandle != 0)
{
HorizonStatic.Syscall.CloseHandle(_sessionHandle);
_sessionHandle = 0;
}
GC.SuppressFinalize(this);
}
}
}

View File

@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Sm
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
readonly struct ServiceName
public readonly struct ServiceName
{
public static ServiceName Invalid { get; } = new(0);

View File

@ -5,7 +5,7 @@ using System;
namespace Ryujinx.Horizon.Sdk.Sm
{
class SmApi
public class SmApi : IDisposable
{
private const string SmName = "sm:";
@ -109,5 +109,17 @@ namespace Ryujinx.Horizon.Sdk.Sm
return ServiceUtil.SendRequest(out _, _portHandle, 4, sendPid: true, data);
}
public void Dispose()
{
if (_portHandle != 0)
{
HorizonStatic.Syscall.CloseHandle(_portHandle);
_portHandle = 0;
}
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface IDetectManager : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface IGeneralServiceCreator : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface IInfraManager : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface ILocalGetActionFrame : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface ILocalGetFrame : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface ILocalManager : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface IPrivateServiceCreator : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface ISfDriverServiceCreator : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface ISocketGetFrame : IServiceObject
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Wlan
{
interface ISocketManager : IServiceObject
{
}
}

View File

@ -1,7 +1,9 @@
using Ryujinx.Horizon.Bcat;
using Ryujinx.Horizon.Lbl;
using Ryujinx.Horizon.LogManager;
using Ryujinx.Horizon.MmNv;
using Ryujinx.Horizon.Prepo;
using Ryujinx.Horizon.Wlan;
using System.Collections.Generic;
using System.Threading;
@ -23,10 +25,12 @@ namespace Ryujinx.Horizon
entries.Add(new ServiceEntry(T.Main, this, options));
}
RegisterService<LmMain>();
RegisterService<PrepoMain>();
RegisterService<BcatMain>();
RegisterService<LblMain>();
RegisterService<LmMain>();
RegisterService<MmNvMain>();
RegisterService<PrepoMain>();
RegisterService<WlanMain>();
_totalServices = entries.Count;

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class DetectManager : IDetectManager
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class GeneralServiceCreator : IGeneralServiceCreator
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class InfraManager : IInfraManager
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class LocalGetActionFrame : ILocalGetActionFrame
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class LocalGetFrame : ILocalGetFrame
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class LocalManager : ILocalManager
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class PrivateServiceCreator : IPrivateServiceCreator
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class SfDriverServiceCreator : ISfDriverServiceCreator
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class SocketGetFrame : ISocketGetFrame
{
}
}

View File

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Wlan;
namespace Ryujinx.Horizon.Wlan.Ipc
{
partial class SocketManager : ISocketManager
{
}
}

View File

@ -0,0 +1,59 @@
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
using Ryujinx.Horizon.Wlan.Ipc;
namespace Ryujinx.Horizon.Wlan
{
class WlanIpcServer
{
private const int WlanOtherMaxSessionsCount = 10;
private const int WlanDtcMaxSessionsCount = 4;
private const int WlanMaxSessionsCount = 30;
private const int WlanNdMaxSessionsCount = 5;
private const int WlanPMaxSessionsCount = 30;
private const int TotalMaxSessionsCount = WlanDtcMaxSessionsCount + WlanMaxSessionsCount + WlanNdMaxSessionsCount + WlanPMaxSessionsCount + WlanOtherMaxSessionsCount * 6;
private const int PointerBufferSize = 0x1000;
private const int MaxDomains = 16;
private const int MaxDomainObjects = 10;
private const int MaxPortsCount = 10;
private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private ServerManager _serverManager;
public void Initialize()
{
HeapAllocator allocator = new();
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount);
#pragma warning disable IDE0055 // Disable formatting
_serverManager.RegisterObjectForServer(new GeneralServiceCreator(), ServiceName.Encode("wlan"), WlanMaxSessionsCount); // 15.0.0+
_serverManager.RegisterObjectForServer(new DetectManager(), ServiceName.Encode("wlan:dtc"), WlanDtcMaxSessionsCount); // 6.0.0-14.1.2
_serverManager.RegisterObjectForServer(new InfraManager(), ServiceName.Encode("wlan:inf"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2
_serverManager.RegisterObjectForServer(new LocalManager(), ServiceName.Encode("wlan:lcl"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2
_serverManager.RegisterObjectForServer(new LocalGetFrame(), ServiceName.Encode("wlan:lg"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2
_serverManager.RegisterObjectForServer(new LocalGetActionFrame(), ServiceName.Encode("wlan:lga"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2
_serverManager.RegisterObjectForServer(new SfDriverServiceCreator(), ServiceName.Encode("wlan:nd"), WlanNdMaxSessionsCount); // 15.0.0+
_serverManager.RegisterObjectForServer(new PrivateServiceCreator(), ServiceName.Encode("wlan:p"), WlanPMaxSessionsCount); // 15.0.0+
_serverManager.RegisterObjectForServer(new SocketGetFrame(), ServiceName.Encode("wlan:sg"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2
_serverManager.RegisterObjectForServer(new SocketManager(), ServiceName.Encode("wlan:soc"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2
#pragma warning restore IDE0055
}
public void ServiceRequests()
{
_serverManager.ServiceRequests();
}
public void Shutdown()
{
_serverManager.Dispose();
}
}
}

View File

@ -0,0 +1,17 @@
namespace Ryujinx.Horizon.Wlan
{
class WlanMain : IService
{
public static void Main(ServiceTable serviceTable)
{
WlanIpcServer ipcServer = new();
ipcServer.Initialize();
serviceTable.SignalServiceReady();
ipcServer.ServiceRequests();
ipcServer.Shutdown();
}
}
}