Compare commits

...

17 Commits

Author SHA1 Message Date
crueter c2d9564072 Only fail on 2026
Signed-off-by: crueter <crueter@eden-emu.dev>
2026-05-06 01:04:29 -04:00
crueter 18c11b88a0 [*] Drop MSVC support
To keep this PR description as short as possible, I will save the
ranting about MSVC for the blog post. In short: MSVC has been nothing
but a complete and utter slog to maintain for the *entire* existence of
Eden. CI is an absolute nightmare, it requires *completely* different
compile flags and definitions, has several sets of hacks dedicated to
getting its codegen to cooperate, is a horror story with respect to
dependency management, and is now completely broken and can't launch
games anymore.

Microsoft, you have long overstayed your welcome. And you're not getting
it back ever again. Good riddance.

Signed-off-by: crueter <crueter@eden-emu.dev>
2026-05-06 00:59:49 -04:00
PavelBARABANOV 86eae5cc41 [core] Fix qlaunch crash on second launch (#3930)
partial revert [08232ce642](https://git.eden-emu.dev/eden-emu/eden/commit/08232ce64275eaf388eba7005f4dae28fc18a0b7)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3930
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-05-06 04:20:55 +02:00
crueter 8b9a841d99 [desktop] Fix QtCommon missing header in bootmanager.cpp (#3931)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3931
2026-05-06 03:27:51 +02:00
lizzie ca1fcaca3b [opengl] remove GLAD symbols from builds w/o OpenGL (#3922)
removes unused symbols from non-OpenGL builds, notably mac

I REMEMBER MAKING THIS PR A WHILE AGO but I have no record of it here, so hell lets redo it

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3922
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-05-06 03:23:27 +02:00
Eden CI 8f4e8c6d6a [dist, android] Update translations from Transifex for May 05 (#3929)
Automatic translation update for May 05

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3929
2026-05-05 21:48:58 +02:00
lizzie 4f4c298a39 [hle, service] fix errors related to race conditions triggering under SMG1 and SMG2 (#3927)
- service manager may add a service while someone else is finding it, so properly lock
- nvhost doesn't properly account for the fact that iterators GET FUCKING INVALIDATED
- use proper exit routine for mapping locked that failed (try/catch hell)

the last two were introduced by #3858, but the first one has been present since ???

either way, remember that ankerl map has invalidated iterators upon erase/insert, so i accounted for that and SMG1 (and probably smg2) boot fine now

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3927
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-05-05 07:31:08 +02:00
maufeat fee603f0b9 [kernel, hle] Initial 22.0.0 kernel changes and cmd stubs (#3761)
- KProcess::Run() and CreateThread() SVC now write the current thread handle to TLS+0x110
- KPageTableBase::LockForMapDeviceAddressSpace now checks for a new KPageTableBase boolean, m_allowed_exec_device_mapping
- Stub `am` + `acc` + `settings` cmd module that needs to work for qlaunch

Thanks to: @alula and @yellows8
Source for changes: https://github.com/Atmosphere-NX/Atmosphere/pull/2744, https://switchbrew.org/, https://yls8.mtheall.com/

Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3761
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-05-05 01:29:46 +02:00
MaranBr fc5fa7f1b2 [video_core] Reapply "Simplify TextureCache GC and remove redundant code" (#3723)
This enhances the garbage collection in TextureCache to make it more responsive and reliable during long gameplay sessions.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3723
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-05-04 21:28:43 +02:00
crueter 17c341ff6c [video_core] index rescaling metadata by descriptor (#3899) (#3924)
#3898 fix was good but exposed a rescaling metadata mismatch that can cause scaled texture descriptors to read the wrong state so this patch tries to keep them aligned with shader lookup

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3899
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>

Co-authored-by: ryana <ryanamayque@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3924
2026-05-04 20:27:53 +02:00
PavelBARABANOV 2deee80f29 [android] move GENSHIN_SPOOF flag to genshinSpoof flavor (#3923)
The standard build was downloading the optimized APK and vice versa due to GENSHIN_SPOOF being incorrectly defined in the mainline flavor. This should resolve the issue.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3923
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-04 19:54:26 +02:00
ryana 27e5cb0f12 [spirv] mark sampled image descriptor indices non-uniform (#3900)
fixes incorrect texture selection on vk when shaders use per-pixel descriptor indices, in line with #3898 so dynamic descs are no longer treated as uniform

also fixes TD;LTD spotty grass issue on SD not addressed by above pr

you can test out all the fixes here: https://git.eden-emu.dev/may/eden/src/branch/may/integrate-texture-descriptor-fixes

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3900
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-04 18:16:11 +02:00
ryana a76c76d122 [shader_recompiler] handle dynamic texture descriptor strides (#3898)
this fixes dynamic texture descriptors that are not laid out as simple 8-byte entries

tested on steam deck/amd

notes
- DynamicDescriptorSizeShift called twice because i moved it away from the struct but doing it this way keeps the patch just in this single file than adding a new derived field in the shared struct (i also think its just a cheap recomputation anyways)
- removed cbuf scanning because i figured out how to do a bounds check statically

credits:
- Mythrax <mythrax@mytrax-rs.org> (identified the 1024 descriptor cap fix in #3897)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3898
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-04 18:15:55 +02:00
rayman30 44fa2805d6 [macos, dynarmic] Implement VectorMin/Max{S,U}64 emitters (Apple Silicon fix) (#3421)
[macos] dynarmic: Implement VectorMin/Max{S,U}64 emitters (Apple Silicon fix)

Implements the missing A64 backend emitters for VectorMinS64, VectorMinU64, VectorMaxS64, and VectorMaxU64.

These IR opcodes are generated by the optimizer but lack direct hardware instruction support for 64-bit elements in the base NEON set (e.g., UMIN.2D does not exist). They are implemented using a compare (CMGT/CMHI) followed by a bitwise select (BSL). This correctly selects between the two source registers, whereas using BIT would incorrectly zero out elements.

This implementation is guarded by #ifdef __APPLE__ to ensure no impact on other platforms.

Unit tests could not be added to a64.cpp because UMIN.2D is not a valid A64 instruction, causing the assembler (Oaknut) to reject it. The fix was verified by running Team Sonic Racing, which previously crashed on this synthetic opcode.

Fixes crash in Team Sonic Racing on macOS.

Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3421
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-03 04:29:49 +02:00
Eden CI 7d0e79335e [dist, android] Update translations from Transifex for May 02 (#3918)
Automatic translation update for May 02

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3918
2026-05-02 19:31:02 +02:00
lizzie d17ecb01af [file_sys] fix compilation on GCC 16 (#3917)
Signed-off-by: lizzie lizzie@eden-emu.dev
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3917
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-02 01:12:24 +02:00
crueter b537e83bed [android] Fix update checker crash on release mode (#3909)
R8 minifies the UpdateResult's data class to remove the `Set...`
methods, as it's technically not used in Kotlin/Java land.

Just force the minifier to keep it

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3909
2026-04-30 20:32:19 +02:00
94 changed files with 8028 additions and 6182 deletions
+14 -1
View File
@@ -1,6 +1,19 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
if (MSVC)
message(WARNING "Support for the MSVC compiler has been dropped indefinitely. "
"This is not a bug. You will not receive support with issues related "
"to using Eden with MSVC, and there is ABSOLUTELY NO GUARANTEE that Eden "
"can be built on MSVC. Read more on our blog: https://eden-emu.dev/blog"
"\nConsider switching to MSYS2 instead: https://www.msys2.org")
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.50 AND NOT I_WANT_A_BROKEN_MSVC_BUILD)
message(FATAL_ERROR "MSVC 19.50+ (Visual Studio 2026+) is entirely unsupported."
"\nIf you *really* want to build anyways, set I_WANT_A_BROKEN_MSVC_BUILD=ON.")
endif()
endif()
cmake_minimum_required(VERSION 3.22)
project(yuzu)
@@ -564,7 +577,7 @@ if (ENABLE_QT)
else()
message(STATUS "Using system Qt")
if (NOT Qt6_DIR)
set(Qt6_DIR "" CACHE PATH "Additional path to search for Qt6 libraries like C:/Qt/6.8.3/msvc2022_64/lib/cmake/Qt6")
set(Qt6_DIR "" CACHE PATH "Additional path to search for Qt6 libraries")
endif()
list(APPEND CMAKE_PREFIX_PATH "${Qt6_DIR}")
endif()
+272 -188
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+272 -188
View File
File diff suppressed because it is too large Load Diff
+333 -248
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+271 -188
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+271 -188
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -189
View File
File diff suppressed because it is too large Load Diff
+271 -188
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+272 -188
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+281 -196
View File
File diff suppressed because it is too large Load Diff
+273 -190
View File
File diff suppressed because it is too large Load Diff
+2 -62
View File
@@ -35,9 +35,6 @@ This is the recommended GUI method for Linux, macOS, and Windows.
<details>
<summary>Click to Open</summary>
> [!WARNING]
> On MSYS2, to use Qt Creator you are recommended to *also* install Qt from the online installer, ensuring to select the "MinGW" version.
Open the CMakeLists.txt file in your cloned directory via File -> Open File or Project (Ctrl+O), if you didn't clone Eden via the project import tool.
Select your desired "kit" (usually, the default is okay). RelWithDebInfo or Release is recommended:
@@ -58,18 +55,10 @@ Hit "Configure Project", then wait for CMake to finish configuring (may take a w
>
>- *CMake* **MUST** be in your PATH (and also *ninja*, if you are using it as `<GENERATOR>`)
>- You *MUST* be in the cloned *Eden* directory
>
>On Windows:
>
> - It's recommended to install **[Ninja](https://ninja-build.org/)**
> - You must load **Visual C++ development environment**, this can be done by running our convenience script:
> - `tools/windows/load-msvc-env.ps1` (for PowerShell 5+)
> - `tools/windows/load-msvc-env.sh` (for MSYS2, Git Bash, etc)
Available `<GENERATOR>`:
- MSYS2: `MSYS Makefiles`
- MSVC: `Ninja` (preferred) or `Visual Studio 17 2022`
- MSYS2: `Ninja`
- macOS: `Ninja` (preferred) or `Xcode`
- Others: `Ninja` (preferred) or `UNIX Makefiles`
@@ -79,61 +68,12 @@ Available `<BUILD_TYPE>`:
- `RelWithDebInfo` (debug symbols--compiled executable will be large)
- `Debug` (if you are using a debugger and annoyed with stuff getting optimized out)
Caveat for Debug Builds:
- If you're building with CCache, you will need to add the environment variable `CL` with the `/FS` flag ([Reference](https://learn.microsoft.com/pt-br/cpp/build/reference/fs-force-synchronous-pdb-writes?view=msvc-170))
Also see the root CMakeLists.txt for more build options. Usually the default will provide the best experience, however.
Also see the root CMakeLists.txt for more build options. Usually the default will provide the best experience.
```sh
cmake -S . -B build -G "<GENERATOR>" -DCMAKE_BUILD_TYPE=<BUILD_TYPE> -DYUZU_TESTS=OFF
```
If you are on Windows and prefer to use Clang:
```sh
cmake -S . -B build -G "<GENERATOR>" -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl
```
</details>
### Option C: [CLion](https://www.jetbrains.com/clion/)
<details>
<summary>Click to Open</summary>
- Clone the Repository:
<img src="https://user-images.githubusercontent.com/42481638/216899046-0d41d7d6-8e4d-4ed2-9587-b57088af5214.png" width="500">
<img src="https://user-images.githubusercontent.com/42481638/216899061-b2ea274a-e88c-40ae-bf0b-4450b46e9fea.png" width="500">
<img src="https://user-images.githubusercontent.com/42481638/216899076-0e5988c4-d431-4284-a5ff-9ecff973db76.png" width="500">
---
### Building & Setup
- Once Cloned, You will be taken to a prompt like the image below:
<img src="https://user-images.githubusercontent.com/42481638/216899092-3fe4cec6-a540-44e3-9e1e-3de9c2fffc2f.png" width="500">
- Set the settings to the image below:
- Change `Build type: Release`
- Change `Name: Release`
- Change `Toolchain Visual Studio`
- Change `Generator: Let CMake decide`
- Change `Build directory: build`
<img src="https://user-images.githubusercontent.com/42481638/216899164-6cee8482-3d59-428f-b1bc-e6dc793c9b20.png" width="500">
- Click OK; now Clion will build a directory and index your code to allow for IntelliSense. Please be patient.
- Once this process has been completed (No loading bar bottom right), you can now build eden
- In the top right, click on the drop-down menu, select all configurations, then select eden
<img src="https://user-images.githubusercontent.com/42481638/216899226-975048e9-bc6d-4ec1-bc2d-bd8a1e15ed04.png" height="500" >
- Now run by clicking the play button or pressing Shift+F10, and eden will auto-launch once built.
<img src="https://user-images.githubusercontent.com/42481638/216899275-d514ec6a-e563-470e-81e2-3e04f0429b68.png" width="500">
</details>
## Troubleshooting
+26 -22
View File
@@ -9,6 +9,7 @@ When reporting issues or finding bugs, we often need backtraces, debug logs, or
If your bug is related to a graphical issue--e.g. mismatched colors, vertex explosions, flickering, etc.--then you are required to include graphical debugging logs in your issue reports.
Graphics Debugging is found in General -> Debug on desktop, and Advanced Settings -> Debug on Android. Android users are all set; however, desktop users may need to install the Vulkan Validation Layers:
- Windows: Install the [Vulkan SDK](https://vulkan.lunarg.com/sdk/home)
- Linux, BSD, etc: Install `vulkan-validation-layers`, `vulkan-layers`, or similar from your package manager. It should be located in e.g. `/usr/lib64/libVkLayer_khronos_validation.so`
@@ -30,15 +31,17 @@ Ignoring SIGSEGV when debugging in host:
### gdb
You must have GDB installed for aarch64 to debug the target. Install it through your package manager, e.g.:
* On Arch:
* `sudo pacman -Syu aarch64-linux-gnu-gdb`
* On Gentoo:
* `sudo emerge --ask crossdev`
* `sudo crossdev -t aarch64-unknown-linux-gnu --ex-gdb`
- On Arch:
- `sudo pacman -Syu aarch64-linux-gnu-gdb`
- On Gentoo:
- `sudo emerge --ask crossdev`
- `sudo crossdev -t aarch64-unknown-linux-gnu --ex-gdb`
Run `./build/bin/eden-cli -c <path to your config file (see logs where you run eden normally to see where it is)> -d -g <path to game>`
Or `Enable GDB Stub` at General > Debug, then hook up an aarch64-gdb:
* `target remote localhost:6543`
- `target remote localhost:6543`
Type `c` (for continue) and then if it crashes just do a `bt` (backtrace) and `layout asm`
@@ -69,26 +72,27 @@ For more information type `info gdb` and read [the man page](https://man7.org/li
## Simple checklist for debugging black screens using Renderdoc
Renderdoc is a free, cross platform, multi-graphics API debugger. It is an invaluable tool for diagnosing issues with graphics applications, and includes support for Vulkan. Get it [here](https://renderdoc.org).
Renderdoc is a free, cross platform, multi-graphics API debugger. It is an invaluable tool for diagnosing issues with graphics applications, and includes support for Vulkan. Get it at [renderdoc.org](https://renderdoc.org).
Before using renderdoc to diagnose issues, it is always good to make sure there are no validation errors. Any errors means the behavior of the application is undefined. That said, renderdoc can help debug validation errors if you do have them.
When debugging a black screen, there are many ways the application could have setup Vulkan wrong.
Here is a short checklist of items to look at to make sure are appropriate:
* Draw call counts are correct (aka not zero, or if rendering many triangles, not 3)
* Vertex buffers are bound
* vertex attributes are correct - Make sure the size & offset of each attribute matches what should it should be
* Any bound push constants and descriptors have the right data - including:
* Matrices have correct values - double check the model, view, & projection matrices are uploaded correctly
* Pipeline state is correct
* viewport range is correct - x,y are 0,0; width & height are screen dimensions, minDepth is 0, maxDepth is 1, NDCDepthRange is 0,1
* Fill mode matches expected - usually solid
* Culling mode makes sense - commonly back or none
* The winding direction is correct - typically CCW (counter clockwise)
* Scissor region is correct - usually same as viewport's x,y,width, &height
* Blend state is correct
* Depth state is correct - typically enabled with Function set to Less than or Equal
* Swapchain images are bound when rendering to the swapchain
* Image being rendered to is the same as the one being presented when rendering to the swapchain
- Draw call counts are correct (aka not zero, or if rendering many triangles, not 3)
- Vertex buffers are bound
- vertex attributes are correct - Make sure the size & offset of each attribute matches what should it should be
- Any bound push constants and descriptors have the right data - including:
- Matrices have correct values - double check the model, view, & projection matrices are uploaded correctly
- Pipeline state is correct
- viewport range is correct - x,y are 0,0; width & height are screen dimensions, minDepth is 0, maxDepth is 1, NDCDepthRange is 0,1
- Fill mode matches expected - usually solid
- Culling mode makes sense - commonly back or none
- The winding direction is correct - typically CCW (counter clockwise)
- Scissor region is correct - usually same as viewport's x,y,width, &height
- Blend state is correct
- Depth state is correct - typically enabled with Function set to Less than or Equal
- Swapchain images are bound when rendering to the swapchain
- Image being rendered to is the same as the one being presented when rendering to the swapchain
Alternatively, a [RenderDoc Extension](https://github.com/baldurk/renderdoc-contrib/tree/main/baldurk/whereismydraw) ([Archive](https://web.archive.org/web/20250000000000*/https://github.com/baldurk/renderdoc-contrib/tree/main/baldurk/whereismydraw)) exists which automates doing a lot of these manual steps.
+15 -28
View File
@@ -4,19 +4,15 @@ To build Eden, you MUST have a C++ compiler.
* On Linux, this is usually [GCC](https://gcc.gnu.org/) 11+ or [Clang](https://clang.llvm.org/) v14+
* GCC 12 also requires Clang 14+
* On Windows, we support:
* **[MSVC](https://visualstudio.microsoft.com/downloads/)** (default)
* It's STRONGLY RECOMMENDED to use the **Community** option and **Visual Studio 2022**
* You need to install: **[Desktop development with C++](https://learn.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=msvc-170)**
* **[clang-cl](https://learn.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=msvc-180)**
* You need to install: **C++ Clang tools for Windows**
* **[MSYS2](https://www.msys2.org)**
* On Windows, install **[MSYS2](https://www.msys2.org)**
* MINGW64 is preferred for x86_64/amd64 compilation, but this is subject to change.
* ARM should use CLANGARM64
* On macOS, this is Apple Clang
* This can be installed with `xcode-select --install`
The following additional tools are also required:
* **[CMake](https://www.cmake.org/)** 3.22+ - already included with the Android SDK
* **[CMake](https://www.cmake.org/)** 3.22+
* **[Git](https://git-scm.com/)** for version control
* **[Windows installer](https://gitforwindows.org)**
* **[Python3](https://www.python.org/downloads/)** 3.10+ - necessary to download external repositories
@@ -29,13 +25,8 @@ If you are on desktop and plan to use the Qt frontend, you *must* install Qt 6,
* On Linux, *BSD and macOS, this can be done by the package manager
* If you wish to use Qt Creator, append `qtcreator` or `qt-creator` to the commands seen below.
* MSVC/clang-cl users on Windows must install through the official [Qt](https://www.qt.io/download-qt-installer-oss) installer
* Linux and macOS users may choose to use the installer as well.
* MSYS2 can also install Qt 6 via the package manager
* For help setting up Qt Creator, run `./install.sh -h qtcreator`
If you are on **Windows** and building with **MSVC** or **clang-cl**, you may go [back home](Build.md) and continue.
* MSYS2 must install Qt 6 via the package manager (`pacman`)
## Externals
@@ -44,7 +35,7 @@ The following are handled by Eden's externals:
* [FFmpeg](https://ffmpeg.org/) (should use `-DYUZU_USE_EXTERNAL_FFMPEG=ON`)
* [SDL2](https://www.libsdl.org/download-2.0.php) 2.0.18+ (should use `-DYUZU_USE_EXTERNAL_SDL2=ON` OR `-DYUZU_USE_BUNDLED_SDL2=ON` to reduce compile time)
All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/) if `YUZU_USE_CPM` is on, but will always use system dependencies if available (UNIX-like only):
All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/). System dependencies can be used if available.
* [Boost](https://www.boost.org/users/download/) 1.57.0+
* [Catch2](https://github.com/catchorg/Catch2) 3.0.1 if `YUZU_TESTS` or `DYNARMIC_TESTS` are on
@@ -56,27 +47,23 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/
* [zstd](https://facebook.github.io/zstd/) 1.5+
* [enet](http://enet.bespin.org/) 1.3+
* [Opus](https://opus-codec.org/) 1.3+
Vulkan 1.3.274+ is also needed:
* [VulkanUtilityLibraries](https://github.com/KhronosGroup/Vulkan-Utility-Libraries)
* [VulkanHeaders](https://github.com/KhronosGroup/Vulkan-Headers)
* [SPIRV-Tools](https://github.com/KhronosGroup/SPIRV-Tools)
* [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers)
Certain other dependencies will be fetched by CPM regardless. System packages *can* be used for these libraries, but many are either not packaged by most distributions OR have issues when used by the system:
* [SimpleIni](https://github.com/brofield/simpleini)
* [DiscordRPC](https://github.com/eden-emulator/discord-rpc)
* [cubeb](https://github.com/mozilla/cubeb)
* [libusb](https://github.com/libusb/libusb)
* [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
* [sirit](https://github.com/eden-emulator/sirit)
* [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on
* This package is known to be broken on the AUR.
* [sirit](https://github.com/eden-emulator/sirit) 1.0.1+
* [httplib](https://github.com/yhirose/cpp-httplib) 0.19+
* [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on
* [unordered-dense](https://github.com/martinus/unordered_dense)
Vulkan 1.4.317+ is also needed:
* [VulkanUtilityLibraries](https://github.com/KhronosGroup/Vulkan-Utility-Libraries)
* [VulkanHeaders](https://github.com/KhronosGroup/Vulkan-Headers)
* [SPIRV-Tools](https://github.com/KhronosGroup/SPIRV-Tools)
* [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers)
On amd64:
* [xbyak](https://github.com/herumi/xbyak) - 7.22 or earlier is recommended
+4 -4
View File
@@ -7,7 +7,7 @@ To change these options, add `-DOPTION_NAME=NEWVALUE` to the command line.
Notes:
- Defaults are marked per-platform.
- "Non-UNIX" just means Windows/MSVC and Android (yes, macOS is UNIX
- "Non-UNIX" just means everything except Android (yes, macOS is UNIX)
- Android generally doesn't need to change anything; if you do, go to `src/android/app/build.gradle.kts`
- To set a boolean variable to on, use `ON` for the value; to turn it off, use `OFF`
- If a variable is mentioned as being e.g. "ON" for a specific platform(s), that means it is defaulted to OFF on others
@@ -28,9 +28,9 @@ These options control dependencies.
- Currently, build fails without this
- `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS and Gentoo)
- `YUZU_USE_BUNDLED_MOLTENVK` (ON, macOS only) Download bundled MoltenVK lib
- `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC, Android, Solaris, and OpenBSD) Download bundled OpenSSL build
- `YUZU_USE_BUNDLED_OPENSSL` (ON for Android, Solaris, and OpenBSD) Download bundled OpenSSL build
- `YUZU_USE_EXTERNAL_SDL2` (OFF) Compiles SDL2 from source
- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2
- `YUZU_USE_BUNDLED_SDL2` (OFF) Download a prebuilt SDL2
### Miscellaneous
@@ -81,7 +81,7 @@ Also desktop-only, but apply strictly to Qt
- `ENABLE_QT` (ON) Enable the Qt frontend (recommended)
- `ENABLE_QT_TRANSLATION` (OFF) Enable translations for the Qt frontend
- `YUZU_USE_BUNDLED_QT` (ON for MSVC) Download bundled Qt binaries
- `YUZU_USE_BUNDLED_QT` (OFF) Download bundled Qt binaries
- Not recommended on Linux. For Windows and macOS, the provided build is statically linked.
- `YUZU_QT_MIRROR` (string) What mirror to use for downloading the bundled Qt libraries
- `YUZU_USE_QT_MULTIMEDIA` (OFF) Use QtMultimedia for camera support
+6 -7
View File
@@ -88,7 +88,6 @@ android {
"-DBUILD_TESTING=OFF",
"-DYUZU_TESTS=OFF",
"-DDYNARMIC_TESTS=OFF",
"-DENABLE_UPDATE_CHECKER=ON",
*extraCMakeArgs.toTypedArray()
)
)
@@ -193,12 +192,6 @@ android {
manifestPlaceholders += mapOf("appNameBase" to "Eden")
resValue("string", "app_name_suffixed", "Eden")
externalNativeBuild {
cmake {
arguments.add("-DGENSHIN_SPOOF=ON")
}
}
ndk {
abiFilters += listOf("arm64-v8a")
}
@@ -210,6 +203,12 @@ android {
resValue("string", "app_name_suffixed", "Eden Optimized")
applicationId = "com.miHoYo.Yuanshen"
externalNativeBuild {
cmake {
arguments.add("-DGENSHIN_SPOOF=ON")
}
}
ndk {
abiFilters += listOf("arm64-v8a")
}
@@ -33,6 +33,7 @@ import org.yuzu.yuzu_emu.applets.web.WebBrowser
* with the native side of the Yuzu code.
*/
object NativeLibrary {
@Keep
data class UpdateResult(
var tag: String = "",
var title: String = "",
@@ -1159,5 +1159,6 @@
<string name="license_fidelityfx_fsr_description">تحسين الجودة بدرجة عالية من AMD</string>
<string name="external_content">محتوى خارجي</string>
<string name="add_folders">إضافة مجلد</string>
<string name="percent">%1$d%%</string>
</resources>
@@ -1153,5 +1153,6 @@
<string name="license_fidelityfx_fsr_description">Upscaling de alta calidad de AMD</string>
<string name="external_content">Contenido externo</string>
<string name="add_folders">Añadir carpeta</string>
<string name="percent">%1$d%%</string>
</resources>
@@ -413,7 +413,9 @@
<string name="frame_limit_slider">Limiter le pourcentage de vitesse</string>
<string name="frame_limit_slider_description">Spécifier le pourcentage pour limiter la vitesse d\'émulation. 100% correspond à la vitesse normale. Des valeurs plus élevées ou plus basses augmenteront ou diminueront la limite de vitesse.</string>
<string name="turbo_speed_limit">Vitesse turbo</string>
<string name="turbo_speed_limit_description">Lorsque le mode Turbo est activé, l\'émulation s\'exécute à cette vitesse.</string>
<string name="slow_speed_limit">Vitesse lente</string>
<string name="slow_speed_limit_description">Lorsque le mode Lent est activé, l\'émulation s\'exécute à cette vitesse.</string>
<string name="cpu_backend">Backend du CPU</string>
<string name="cpu_accuracy">Précision du CPU</string>
<string name="value_with_units">%1$s%2$s</string>
@@ -489,6 +489,8 @@
<string name="enable_buffer_history">Включить историю буфера</string>
<string name="enable_buffer_history_description">Позволяет обращаться к предыдущим состояниям буфера. Эта опция может повысить качество рендеринга и стабильность производительности в некоторых играх.</string>
<string name="use_optimized_vertex_buffers">Оптимизированные вершинные буферы</string>
<string name="use_optimized_vertex_buffers_description">Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM драйверы. Приводит к вылету на старых версиях Turnip.</string>
<string name="hacks">Хаки</string>
<string name="fast_gpu_time">Быстрое время ГПУ</string>
@@ -496,6 +498,9 @@
<string name="skip_cpu_inner_invalidation">Пропустить внутреннюю инвалидацию ЦП</string>
<string name="skip_cpu_inner_invalidation_description">Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх.</string>
<string name="fix_bloom_effects">Исправить эффекты размытия</string>
<string name="fix_bloom_effects_description">Частично убирает размытие в LA/EOW (Adreno A6XX - A7XX/ Turnip), полностью отключает его в Burnout. Внимание: может вызывать графические артефакты в других играх.</string>
<string name="emulate_bgr565">Эмулировать BGR565</string>
<string name="emulate_bgr565_description">Исправляет проблемы с инвертированными цветами в играх, а также со странными артефактами или некорректными тенями.</string>
<string name="renderer_asynchronous_shaders">Использовать асинхронные шейдеры</string>
<string name="renderer_asynchronous_shaders_description">Компилирует шейдеры асинхронно. Это может уменьшить подтормаживания, но также может вызвать графические артефакты.</string>
<string name="gpu_unswizzle_settings">Настройки распаковки текстур (Unswizzle)</string>
@@ -1150,5 +1155,4 @@
<string name="license_fidelityfx_fsr_description">Высококачественное масштабирование от AMD</string>
<string name="external_content">Дополнительный контент</string>
<string name="add_folders">Добавить папку</string>
</resources>
</resources>
@@ -1155,5 +1155,6 @@
<string name="license_fidelityfx_fsr_description">Високоякісне масштабування від AMD</string>
<string name="external_content">Зовнішній вміст</string>
<string name="add_folders">Додати теку</string>
<string name="percent">%1$d%%</string>
</resources>
@@ -483,6 +483,8 @@
<string name="enable_buffer_history">启用缓冲区历史</string>
<string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string>
<string name="use_optimized_vertex_buffers">优化顶点缓冲区</string>
<string name="use_optimized_vertex_buffers_description">启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动则会导致崩溃。</string>
<string name="hacks">Hacks</string>
<string name="fast_gpu_time">GPU 超频频率</string>
@@ -490,6 +492,9 @@
<string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string>
<string name="skip_cpu_inner_invalidation_description">在内存更新期间跳过某些CPU端缓存无效化,减少CPU使用率并提高其性能。可能会导致某些游戏出现故障或崩溃。</string>
<string name="fix_bloom_effects">修复 Bloom 效果</string>
<string name="fix_bloom_effects_description">减少《塞尔达传说:智慧的再现》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。</string>
<string name="emulate_bgr565">模拟 BGR565</string>
<string name="emulate_bgr565_description">修复了游戏中的颜色反转以及出现的异常画面瑕疵或奇怪阴影问题</string>
<string name="renderer_asynchronous_shaders">使用异步着色器</string>
<string name="renderer_asynchronous_shaders_description">异步编译着色器。这可能会减少卡顿,但也可能会导致图形错误。</string>
<string name="gpu_unswizzle_settings">GPU 还原设置</string>
@@ -1144,5 +1149,6 @@
<string name="license_fidelityfx_fsr_description">AMD 的高品质画面增强技术</string>
<string name="external_content">外部内容</string>
<string name="add_folders">添加文件夹</string>
<string name="percent">%1$d%%</string>
</resources>
-1
View File
@@ -415,7 +415,6 @@ struct System::Impl {
cpu_manager.Shutdown();
debugger.reset();
kernel.Shutdown();
device_memory.reset();
stop_event = {};
Network::RestartSocketOperations();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -109,7 +109,7 @@ Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_e
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
// Check that our range is valid.
BucketTree::Offsets table_offsets;
BucketTree::Offsets table_offsets{};
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
@@ -167,7 +167,7 @@ size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset
ASSERT(Common::IsAligned(offset, BlockSize));
ASSERT(Common::IsAligned(size, BlockSize));
BucketTree::Offsets table_offsets;
BucketTree::Offsets table_offsets{};
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets))));
ASSERT(table_offsets.IsInclude(offset, size));
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -89,7 +92,7 @@ public:
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
virtual size_t GetSize() const override {
BucketTree::Offsets offsets;
BucketTree::Offsets offsets{};
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets))));
return offsets.end_offset;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -270,7 +270,7 @@ Result BucketTree::Find(Visitor* visitor, s64 virtual_address) {
R_UNLESS(virtual_address >= 0, ResultInvalidOffset);
R_UNLESS(!this->IsEmpty(), ResultOutOfRange);
BucketTree::Offsets offsets;
BucketTree::Offsets offsets{};
R_TRY(this->GetOffsets(std::addressof(offsets)));
R_TRY(visitor->Initialize(this, offsets));
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -125,7 +125,7 @@ private:
}
// Get the table offsets.
BucketTree::Offsets table_offsets;
BucketTree::Offsets table_offsets{};
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments.
@@ -177,7 +177,7 @@ private:
ASSERT(out != nullptr);
// Get our table offsets.
BucketTree::Offsets offsets;
BucketTree::Offsets offsets{};
R_TRY(m_table.GetOffsets(std::addressof(offsets)));
// Set the output.
@@ -195,7 +195,7 @@ private:
R_SUCCEED_IF(size == 0);
// Get the table offsets.
BucketTree::Offsets table_offsets;
BucketTree::Offsets table_offsets{};
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments.
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -52,7 +55,7 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
// Check that our range is valid.
BucketTree::Offsets table_offsets;
BucketTree::Offsets table_offsets{};
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -164,7 +167,7 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
R_SUCCEED_IF(size == 0);
// Get the table offsets.
BucketTree::Offsets table_offsets;
BucketTree::Offsets table_offsets{};
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments.
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -18,7 +21,7 @@ size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const {
SparseStorage* self = const_cast<SparseStorage*>(this);
if (self->GetEntryTable().IsEmpty()) {
BucketTree::Offsets table_offsets;
BucketTree::Offsets table_offsets{};
ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets))));
ASSERT(table_offsets.IsInclude(offset, size));
@@ -268,6 +268,7 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
// Set other basic fields.
m_enable_aslr = enable_aslr;
m_enable_device_address_space_merge = enable_das_merge;
m_allowed_exec_device_mapping = false;
m_address_space_start = start;
m_address_space_end = end;
m_is_kernel = false;
+5
View File
@@ -223,6 +223,7 @@ private:
bool m_is_kernel{};
bool m_enable_aslr{};
bool m_enable_device_address_space_merge{};
bool m_allowed_exec_device_mapping{};
KMemoryBlockSlabManager* m_memory_block_slab_manager{};
KBlockInfoManager* m_block_info_manager{};
KResourceLimit* m_resource_limit{};
@@ -255,6 +256,10 @@ public:
return m_enable_aslr;
}
void AllowDeviceMappingOfExecPages() {
m_allowed_exec_device_mapping = true;
}
bool Contains(KProcessAddress addr) const {
return m_address_space_start <= addr && addr <= m_address_space_end - 1;
}
+13
View File
@@ -341,6 +341,11 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa
// Initialize capabilities.
R_TRY(m_capabilities.InitializeForKip(caps, std::addressof(m_page_table)));
// Enable mapping device pages as executable on legacy processes.
if (m_capabilities.GetIntendedKernelMajorVersion() < 26) {
m_page_table.AllowDeviceMappingOfExecPages();
}
// Initialize the process id.
m_process_id = m_kernel.CreateNewUserProcessID();
ASSERT(InitialProcessIdMin <= m_process_id);
@@ -437,6 +442,11 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
// Initialize capabilities.
R_TRY(m_capabilities.InitializeForUser(user_caps, std::addressof(m_page_table)));
// Enable mapping device pages as executable on legacy processes.
if (m_capabilities.GetIntendedKernelMajorVersion() < 26) {
m_page_table.AllowDeviceMappingOfExecPages();
}
// Initialize the process id.
m_process_id = m_kernel.CreateNewUserProcessID();
ASSERT(ProcessIdMin <= m_process_id);
@@ -989,6 +999,9 @@ Result KProcess::Run(s32 priority, size_t stack_size) {
main_thread->GetContext().r[0] = 0;
main_thread->GetContext().r[1] = thread_handle;
// Pass the thread handle to the thread local region.
this->GetMemory().Write32(GetInteger(main_thread->GetTlsAddress()) + 0x110, thread_handle);
// Update our state.
this->ChangeState((state == State::Created) ? State::Running : State::RunningAttached);
ON_RESULT_FAILURE_2 {
@@ -33,6 +33,10 @@ public:
m_page_table.Finalize();
}
void AllowDeviceMappingOfExecPages() {
m_page_table.AllowDeviceMappingOfExecPages();
}
[[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return m_page_table.GetMemory(); }
[[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return m_page_table.GetMemory(); }
[[nodiscard]] Common::PageTable& GetImpl() noexcept { return m_page_table.GetImpl(); }
+20 -18
View File
@@ -76,11 +76,13 @@ namespace Kernel {
std::atomic_uint16_t interrupt_flag;
std::atomic_uint8_t cache_maintenance_flag;
std::atomic_int64_t thread_cpu_time;
std::atomic_uint32_t current_thread_handle;
};
static_assert(offsetof(ThreadLocalRegion, disable_count) == 0x100);
static_assert(offsetof(ThreadLocalRegion, interrupt_flag) == 0x102);
static_assert(offsetof(ThreadLocalRegion, cache_maintenance_flag) == 0x104);
static_assert(offsetof(ThreadLocalRegion, thread_cpu_time) == 0x108);
static_assert(offsetof(ThreadLocalRegion, current_thread_handle) == 0x110);
class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait {
public:
@@ -128,24 +130,24 @@ namespace Kernel {
// Next, assert things based on the type.
switch (type) {
case ThreadType::Main:
ASSERT(arg == 0);
[[fallthrough]];
case ThreadType::User:
ASSERT(((owner == nullptr) ||
(owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) ||
(owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask()));
break;
case ThreadType::HighPriority:
case ThreadType::Dummy:
break;
case ThreadType::Kernel:
UNIMPLEMENTED();
break;
default:
ASSERT_MSG(false, "KThread::Initialize: Unknown ThreadType {}", static_cast<u32>(type));
break;
case ThreadType::Main:
ASSERT(arg == 0);
[[fallthrough]];
case ThreadType::User:
ASSERT(((owner == nullptr) ||
(owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) ||
(owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask()));
break;
case ThreadType::HighPriority:
case ThreadType::Dummy:
break;
case ThreadType::Kernel:
UNIMPLEMENTED();
break;
default:
ASSERT_MSG(false, "KThread::Initialize: Unknown ThreadType {}", static_cast<u32>(type));
break;
}
m_thread_type = type;
+7 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -75,7 +75,12 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u
KThread::Register(kernel, thread);
// Add the thread to the handle table.
R_RETURN(process.GetHandleTable().Add(out_handle, thread));
R_TRY(process.GetHandleTable().Add(out_handle, thread));
// Pass the thread handle to the thread local region.
process.GetMemory().Write32(GetInteger(thread->GetTlsAddress()) + 0x110, *out_handle);
R_SUCCEED();
}
/// Starts the thread for the provided handle
+3 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -53,8 +53,8 @@ constexpr inline u32 RequiredKernelVersion =
// This is the highest SVC version supported, to be updated on new kernel releases.
// NOTE: Official kernel versions have SVC major = SDK major + 4, SVC minor = SDK minor.
constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(20);
constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(5);
constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(22);
constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(2);
constexpr inline u32 SupportedKernelVersion =
EncodeKernelVersion(SupportedKernelMajorVersion, SupportedKernelMinorVersion);
+8
View File
@@ -1155,6 +1155,14 @@ void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) {
StoreSaveDataThumbnail(ctx, uuid, tid);
}
void Module::Interface::GetPinCodeLength(HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u32>(0);
}
void Module::Interface::StoreSaveDataThumbnail(HLERequestContext& ctx, const Common::UUID& uuid,
const u64 tid) {
IPC::ResponseBuilder rb{ctx, 2};
+2 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -48,6 +48,7 @@ public:
void StoreSaveDataThumbnailApplication(HLERequestContext& ctx);
void GetBaasAccountManagerForSystemService(HLERequestContext& ctx);
void StoreSaveDataThumbnailSystem(HLERequestContext& ctx);
void GetPinCodeLength(HLERequestContext& ctx);
private:
Result InitializeApplicationInfoBase();
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -60,7 +60,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
{291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"},
{299, nullptr, "SuspendBackgroundDaemon"},
{400, nullptr, "SetPinCode"}, // 18.0.0+
{401, nullptr, "GetPinCodeLength"}, // 18.0.0+
{401, &ACC_SU::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+
{402, nullptr, "GetPinCode"}, // 18.0.0+
{410, nullptr, "GetPinCodeErrorCount"}, // 18.0.0+
{411, nullptr, "ResetPinCodeErrorCount"}, // 18.0.0+
+4 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -40,7 +43,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
{152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
{190, nullptr, "GetUserLastOpenedApplication"},
{191, nullptr, "ActivateOpenContextHolder"},
{401, nullptr, "GetPinCodeLength"}, // 18.0.0+
{401, &ACC_U1::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+
{402, nullptr, "GetPinCode"}, // 18.0.0+
{997, nullptr, "DebugInvalidateTokenCacheForUser"},
{998, nullptr, "DebugSetUserStateClose"},
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -92,7 +92,7 @@ Result IApplicationCreator::CreateSystemApplication(
std::unique_ptr<Loader::AppLoader> loader;
auto process =
CreateProcess(system, application_id, 1, 21);
CreateProcess(system, application_id, 1, 22);
R_UNLESS(process != nullptr, ResultUnknown);
const auto applet = std::make_shared<Applet>(system, std::move(process), true);
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -75,7 +75,8 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<Ap
{502, nullptr, "IsSleepEnabled"},
{503, nullptr, "IsDisablingSleepSuppressed"},
{600, nullptr, "Unknown600"}, //20.0.0+
{610, nullptr, "Unknown610"}, //21.0.0+
{610, D<&ICommonStateGetter::Unknown610>, "Unknown610"}, //21.0.0+
{611, D<&ICommonStateGetter::Unknown611>, "Unknown611"}, //22.0.0+
{900, D<&ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled>, "SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled"}, //11.0.0+
{910, nullptr, "GetLaunchRequiredTick"}, //17.0.0+
{1000, nullptr, "BeginVrMode3d"}, //19.0.0+
@@ -362,4 +363,14 @@ Result ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled(bool enabled
R_SUCCEED();
}
Result ICommonStateGetter::Unknown610() {
LOG_WARNING(Service_AM, "(STUBBED) called");
R_SUCCEED();
}
Result ICommonStateGetter::Unknown611() {
LOG_WARNING(Service_AM, "(STUBBED) called");
R_SUCCEED();
}
} // namespace Service::AM
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -63,6 +63,8 @@ private:
Result SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled();
Result PushToGeneralChannel(SharedPointer<IStorage> storage); // cmd 20
Result SetHandlingHomeButtonShortPressedEnabled(bool enabled);
Result Unknown610();
Result Unknown611();
void SetCpuBoostMode(HLERequestContext& ctx);
@@ -78,6 +78,7 @@ ILibraryAppletAccessor::ILibraryAppletAccessor(Core::System& system_,
{120, nullptr, "GetLibraryAppletInfo"},
{150, nullptr, "RequestForAppletToGetForeground"},
{160, D<&ILibraryAppletAccessor::GetIndirectLayerConsumerHandle>, "GetIndirectLayerConsumerHandle"}, //2.0.0+
{170, D<&ILibraryAppletAccessor::Unknown170>, "Unknown170"}, //22.0.0+
};
// clang-format on
@@ -217,6 +218,12 @@ Result ILibraryAppletAccessor::GetIndirectLayerConsumerHandle(Out<u64> out_handl
R_SUCCEED();
}
Result ILibraryAppletAccessor::Unknown170(OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_AM, "(STUBBED) called");
*out_event = m_applet->unknown_event.GetHandle();
R_SUCCEED();
}
void ILibraryAppletAccessor::FrontendExecute() {
if (m_applet->frontend) {
m_applet->frontend->Initialize();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -37,6 +37,7 @@ private:
Result GetPopOutDataEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetPopInteractiveOutDataEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetIndirectLayerConsumerHandle(Out<u64> out_handle);
Result Unknown170(OutCopyHandle<Kernel::KReadableEvent> out_event);
void FrontendExecute();
void FrontendExecuteInteractive();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -118,9 +118,10 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
Firmware1900 = 19,
Firmware2000 = 20,
Firmware2100 = 21,
Firmware2200 = 22,
};
auto process = CreateProcess(system, program_id, Firmware1400, Firmware2100);
auto process = CreateProcess(system, program_id, Firmware1400, Firmware2200);
if (!process) {
// Couldn't initialize the guest process
return {};
@@ -199,25 +199,28 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
return NvResult::Success;
}
void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
auto const it = mapping_map.find(offset);
auto const mapping = it->second;
if (!mapping.fixed) {
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
u32 page_size{mapping.big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
u64 aligned_size{Common::AlignUp(mapping.size, page_size)};
allocator.Free(u32(mapping.offset >> page_size_bits), u32(aligned_size >> page_size_bits));
bool nvhost_as_gpu::FreeMappingLocked(u64 offset) noexcept {
if (auto const it = mapping_map.find(offset); it != mapping_map.end()) {
auto const mapping = it->second;
if (!mapping.fixed) {
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
u32 page_size{mapping.big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
u64 aligned_size{Common::AlignUp(mapping.size, page_size)};
allocator.Free(u32(mapping.offset >> page_size_bits), u32(aligned_size >> page_size_bits));
}
nvmap.UnpinHandle(mapping.handle);
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully
if (mapping.sparse_alloc) {
gmmu->MapSparse(offset, mapping.size, mapping.big_page);
} else {
gmmu->Unmap(offset, mapping.size);
}
mapping_map.erase(offset);
return true;
}
nvmap.UnpinHandle(mapping.handle);
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully
if (mapping.sparse_alloc) {
gmmu->MapSparse(offset, mapping.size, mapping.big_page);
} else {
gmmu->Unmap(offset, mapping.size);
}
mapping_map.erase(it);
return false;
}
NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) {
@@ -232,7 +235,8 @@ NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) {
return NvResult::BadValue;
for (const auto mapping_offset : allocation.mappings)
FreeMappingLocked(mapping_offset);
if (!FreeMappingLocked(mapping_offset))
return NvResult::BadValue;
// Unset sparse flag if required
if (allocation.sparse)
@@ -402,8 +406,8 @@ NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
}
nvmap.UnpinHandle(mapping.handle);
mapping_map.erase(it);
map_buffer_offsets.erase(offset_it);
mapping_map.erase(params.offset);
map_buffer_offsets.erase(params.offset);
}
return NvResult::Success;
}
@@ -157,7 +157,7 @@ private:
NvResult GetVARegions1(IoctlGetVaRegions& params);
NvResult GetVARegions3(IoctlGetVaRegions& params, std::span<VaRegion> regions);
void FreeMappingLocked(u64 offset);
[[nodiscard]] bool FreeMappingLocked(u64 offset) noexcept;
Module& module;
@@ -409,6 +409,13 @@ struct AccountNotificationSettings {
static_assert(sizeof(AccountNotificationSettings) == 0x18,
"AccountNotificationSettings is an invalid size");
/// This is nn::settings::system::AccountUserSettings (stubbed)
struct AccountUserSettings {
std::array<u8, 0x40> data;
};
static_assert(sizeof(AccountUserSettings) == 0x40,
"AccountUserSettings is an invalid size");
/// This is nn::settings::factory::BatteryLot
struct BatteryLot {
std::array<char, 0x18> lot_number;
@@ -340,9 +340,9 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_)
{306, nullptr, "GetPinCodeReregistrationGuideAccounts"}, //20.0.0+
{307, nullptr, "SetPinCodeReregistrationGuideAccounts"}, //20.0.0+
{315, C<&ISystemSettingsServer::GetHttpAuthConfigs>, "GetHttpAuthConfigs"}, //21.0.0+
{319, nullptr, "GetAccountUserSettings"}, //21.0.0+
{319, C<&ISystemSettingsServer::GetAccountUserSettings>, "GetAccountUserSettings"}, //21.0.0+
{320, nullptr, "SetAccountUserSettings"}, //21.0.0+
{321, nullptr, "GetDefaultAccountUserSettings"}, //21.0.0+
{321, C<&ISystemSettingsServer::GetDefaultAccountUserSettings>, "GetDefaultAccountUserSettings"}, //21.0.0+
};
// clang-format on
@@ -1428,6 +1428,23 @@ Result ISystemSettingsServer::GetHttpAuthConfigs(Out<s32> out_count, OutBuffer<B
R_SUCCEED();
}
Result ISystemSettingsServer::GetAccountUserSettings(
Out<u32> out_count,
OutLargeData<AccountUserSettings, BufferAttr_HipcMapAlias> out_settings) {
LOG_WARNING(Service_SET, "(STUBBED) called");
*out_count = 0;
*out_settings = {};
R_SUCCEED();
}
Result ISystemSettingsServer::GetDefaultAccountUserSettings(Out<AccountUserSettings> out_settings) {
LOG_WARNING(Service_SET, "(STUBBED) called");
*out_settings = {};
R_SUCCEED();
}
void ISystemSettingsServer::SetupSettings() {
auto system_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/8000000000000050";
if (!LoadSettingsFile(system_dir, []() { return DefaultSystemSettings(); })) {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -161,6 +161,10 @@ public:
Result GetPanelCrcMode(Out<s32> out_panel_crc_mode);
Result SetPanelCrcMode(s32 panel_crc_mode);
Result GetHttpAuthConfigs(Out<s32> out_count, OutBuffer<BufferAttr_HipcMapAlias> out_configs);
Result GetAccountUserSettings(
Out<u32> out_count,
OutLargeData<AccountUserSettings, BufferAttr_HipcMapAlias> out_settings);
Result GetDefaultAccountUserSettings(Out<AccountUserSettings> out_settings);
private:
bool LoadSettingsFile(std::filesystem::path& path, auto&& default_func);
+11 -9
View File
@@ -67,22 +67,24 @@ public:
Result GetServicePort(Kernel::KClientPort** out_client_port, const std::string& name);
template <std::derived_from<SessionRequestHandler> T>
std::shared_ptr<T> GetService(const std::string& service_name, bool block = false) const {
auto service = registered_services.find(service_name);
if (service == registered_services.end() && !block) {
LOG_DEBUG(Service, "Can't find service: {}", service_name);
std::shared_ptr<T> GetService(const std::string& name, bool block = false) const {
std::unique_lock l{lock};
auto it = registered_services.find(name);
if (it == registered_services.end() && !block) {
LOG_DEBUG(Service, "Can't find service: {}", name);
return nullptr;
} else if (block) {
using namespace std::literals::chrono_literals;
while (service == registered_services.end()) {
while (it == registered_services.end()) {
l.unlock();
Kernel::Svc::SleepThread(
kernel.System(),
std::chrono::duration_cast<std::chrono::nanoseconds>(100ms).count());
service = registered_services.find(service_name);
l.lock();
it = registered_services.find(name);
}
}
return std::static_pointer_cast<T>(service->second());
return std::static_pointer_cast<T>(it->second());
}
void InvokeControlRequest(HLERequestContext& context);
@@ -96,7 +98,7 @@ private:
std::unique_ptr<Controller> controller_interface;
/// Map of registered services, retrieved using GetServicePort.
std::mutex lock;
mutable std::mutex lock;
ankerl::unordered_dense::map<std::string, SessionRequestHandlerFactory> registered_services;
ankerl::unordered_dense::map<std::string, Kernel::KClientPort*> service_ports;
@@ -866,10 +866,10 @@ void EmitIR<IR::Opcode::VectorMaxS32>(oaknut::CodeGenerator& code, EmitContext&
template<>
void EmitIR<IR::Opcode::VectorMaxS64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code;
(void)ctx;
(void)inst;
UNREACHABLE();
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
code.CMGT(Qresult->D2(), Qa->D2(), Qb->D2());
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
});
}
template<>
@@ -889,10 +889,10 @@ void EmitIR<IR::Opcode::VectorMaxU32>(oaknut::CodeGenerator& code, EmitContext&
template<>
void EmitIR<IR::Opcode::VectorMaxU64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code;
(void)ctx;
(void)inst;
UNREACHABLE();
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
code.CMHI(Qresult->D2(), Qa->D2(), Qb->D2());
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
});
}
template<>
@@ -912,10 +912,10 @@ void EmitIR<IR::Opcode::VectorMinS32>(oaknut::CodeGenerator& code, EmitContext&
template<>
void EmitIR<IR::Opcode::VectorMinS64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code;
(void)ctx;
(void)inst;
UNREACHABLE();
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
code.CMGT(Qresult->D2(), Qb->D2(), Qa->D2());
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
});
}
template<>
@@ -935,10 +935,10 @@ void EmitIR<IR::Opcode::VectorMinU32>(oaknut::CodeGenerator& code, EmitContext&
template<>
void EmitIR<IR::Opcode::VectorMinU64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
(void)code;
(void)ctx;
(void)inst;
UNREACHABLE();
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
code.CMHI(Qresult->D2(), Qb->D2(), Qa->D2());
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
});
}
template<>
File diff suppressed because one or more lines are too long
@@ -14,6 +14,38 @@
namespace Shader::Backend::SPIRV {
namespace {
class DescriptorIndex {
public:
explicit DescriptorIndex(EmitContext& ctx, const IR::Value& index)
: id{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)},
is_non_uniform{ctx.profile.support_sampled_image_array_nonuniform_indexing &&
!index.IsImmediate()} {
if (!is_non_uniform) {
return;
}
if (ctx.profile.supported_spirv < 0x00010400) {
ctx.AddExtension("SPV_EXT_descriptor_indexing");
}
ctx.AddCapability(spv::Capability::ShaderNonUniform);
ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing);
Decorate(ctx, id);
}
Id Value() const {
return id;
}
void Decorate(EmitContext& ctx, Id object) const {
if (is_non_uniform) {
ctx.Decorate(object, spv::Decoration::NonUniform);
}
}
private:
Id id;
bool is_non_uniform;
};
class ImageOperands {
public:
[[maybe_unused]] static constexpr bool ImageSampleOffsetAllowed = false;
@@ -189,8 +221,12 @@ private:
Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR::Value& index) {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) {
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, ctx.Def(index))};
return ctx.OpLoad(def.sampled_type, pointer);
const DescriptorIndex idx{ctx, index};
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
idx.Decorate(ctx, pointer);
const Id object{ctx.OpLoad(def.sampled_type, pointer)};
idx.Decorate(ctx, object);
return object;
} else {
return ctx.OpLoad(def.sampled_type, def.id);
}
@@ -208,9 +244,14 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
} else {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) {
const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)};
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)};
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, ptr));
const DescriptorIndex idx{ctx, index};
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
idx.Decorate(ctx, ptr);
const Id object{ctx.OpLoad(def.sampled_type, ptr)};
idx.Decorate(ctx, object);
const Id image{ctx.OpImage(def.image_type, object)};
idx.Decorate(ctx, image);
return image;
}
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id));
}
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -17,6 +20,9 @@ struct HostTranslateInfo {
bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers
bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS
u32 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs
u32 max_per_stage_descriptor_sampled_images{1024}; ///< maximum sampled descriptors per stage
u32 max_per_stage_resources{4096}; ///< maximum resources per stage
u32 max_descriptor_set_sampled_images{1024}; ///< maximum sampled descriptors per set
bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry
///< passthrough shaders
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
+117 -12
View File
@@ -32,6 +32,71 @@ using TextureInstVector = boost::container::small_vector<TextureInst, 24>;
constexpr u32 DESCRIPTOR_SIZE = 8;
constexpr u32 DESCRIPTOR_SIZE_SHIFT = static_cast<u32>(std::countr_zero(DESCRIPTOR_SIZE));
constexpr u32 DYNAMIC_DESCRIPTOR_CBUF_BYTES = 16 * 1024;
constexpr u32 MAX_DYNAMIC_DESCRIPTOR_COUNT = 1024;
u32 DynamicDescriptorSizeShift(const IR::U32& dynamic_offset) {
const IR::Inst* const inst{dynamic_offset.InstRecursive()};
if (!inst || inst->GetOpcode() != IR::Opcode::ShiftLeftLogical32) {
return DESCRIPTOR_SIZE_SHIFT;
}
const IR::Value shift{inst->Arg(1)};
if (!shift.IsImmediate()) {
return DESCRIPTOR_SIZE_SHIFT;
}
const u32 size_shift{shift.U32()};
return size_shift >= DESCRIPTOR_SIZE_SHIFT && size_shift < 31 ? size_shift
: DESCRIPTOR_SIZE_SHIFT;
}
u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift) {
if (size_shift >= 31 || base_offset >= DYNAMIC_DESCRIPTOR_CBUF_BYTES) {
return 1;
}
const u32 stride{1U << size_shift};
const u32 available{DYNAMIC_DESCRIPTOR_CBUF_BYTES - base_offset};
if (available < DESCRIPTOR_SIZE) {
return 1;
}
const u32 available_count{1U + (available - DESCRIPTOR_SIZE) / stride};
return std::min(MAX_DYNAMIC_DESCRIPTOR_COUNT, available_count);
}
u32 SaturatingSub(u32 lhs, u32 rhs) {
return lhs > rhs ? lhs - rhs : 0;
}
template <typename Descriptors>
u32 StaticDescriptorCount(const Descriptors& descriptors) {
u32 count{};
for (const auto& desc : descriptors) {
if (desc.count <= 1) {
count += desc.count;
}
}
return count;
}
u32 DynamicSampledTextureCap(const Info& info, const HostTranslateInfo& host_info,
u32 dynamic_arrays) {
if (dynamic_arrays == 0) {
return MAX_DYNAMIC_DESCRIPTOR_COUNT;
}
const u32 sampled_static_count{StaticDescriptorCount(info.texture_buffer_descriptors) +
StaticDescriptorCount(info.texture_descriptors)};
const u32 resource_static_count{
NumDescriptors(info.constant_buffer_descriptors) +
NumDescriptors(info.storage_buffers_descriptors) + sampled_static_count +
NumDescriptors(info.image_buffer_descriptors) + NumDescriptors(info.image_descriptors)};
const u32 sampled_limit{std::min(host_info.max_per_stage_descriptor_sampled_images,
host_info.max_descriptor_set_sampled_images)};
const u32 sampled_budget{SaturatingSub(sampled_limit, sampled_static_count)};
const u32 resource_budget{SaturatingSub(host_info.max_per_stage_resources,
resource_static_count)};
const u32 sampled_cap{sampled_budget / dynamic_arrays};
const u32 resource_cap{resource_budget / dynamic_arrays};
return std::max(1U, std::min({MAX_DYNAMIC_DESCRIPTOR_COUNT, sampled_cap, resource_cap}));
}
IR::Opcode IndexedInstruction(const IR::Inst& inst) {
switch (inst.GetOpcode()) {
@@ -109,6 +174,39 @@ IR::Opcode IndexedInstruction(const IR::Inst& inst) {
}
}
bool IsStorageImageOpcode(IR::Opcode opcode) {
switch (opcode) {
case IR::Opcode::ImageRead:
case IR::Opcode::ImageAtomicIAdd32:
case IR::Opcode::ImageAtomicSMin32:
case IR::Opcode::ImageAtomicUMin32:
case IR::Opcode::ImageAtomicSMax32:
case IR::Opcode::ImageAtomicUMax32:
case IR::Opcode::ImageAtomicInc32:
case IR::Opcode::ImageAtomicDec32:
case IR::Opcode::ImageAtomicAnd32:
case IR::Opcode::ImageAtomicOr32:
case IR::Opcode::ImageAtomicXor32:
case IR::Opcode::ImageAtomicExchange32:
case IR::Opcode::ImageWrite:
return true;
default:
return false;
}
}
u32 DynamicSampledTextureArrayCount(const TextureInstVector& to_replace) {
u32 count{};
for (const TextureInst& inst : to_replace) {
const auto flags{inst.inst->Flags<IR::TextureInstInfo>()};
if (inst.cbuf.count > 1 && !IsStorageImageOpcode(IndexedInstruction(*inst.inst)) &&
flags.type != TextureType::Buffer) {
++count;
}
}
return count;
}
bool IsBindless(const IR::Inst& inst) {
switch (inst.GetOpcode()) {
case IR::Opcode::BindlessImageSampleImplicitLod:
@@ -354,6 +452,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
} else {
return std::nullopt;
}
const u32 size_shift{DynamicDescriptorSizeShift(dynamic_offset)};
return ConstBufferAddr{
.index = index.U32(),
.offset = base_offset,
@@ -362,7 +461,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
.secondary_offset = 0,
.secondary_shift_left = 0,
.dynamic_offset = dynamic_offset,
.count = 8,
.count = DynamicDescriptorCount(base_offset, size_shift),
.has_secondary = false,
};
}
@@ -589,6 +688,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
program.info.texture_descriptors,
program.info.image_descriptors,
};
const u32 sampled_dynamic_cap{
DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace))};
for (TextureInst& texture_inst : to_replace) {
// TODO: Handle arrays
IR::Inst* const inst{texture_inst.inst};
@@ -632,6 +733,9 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
break;
}
u32 index;
const u32 size_shift{cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset)
: DESCRIPTOR_SIZE_SHIFT};
u32 count{cbuf.count};
switch (inst->GetOpcode()) {
case IR::Opcode::ImageRead:
case IR::Opcode::ImageAtomicIAdd32:
@@ -660,8 +764,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.is_integer = is_integer,
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
.count = count,
.size_shift = size_shift,
});
} else {
index = descriptors.Add(ImageDescriptor{
@@ -672,8 +776,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.is_integer = is_integer,
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
.count = count,
.size_shift = size_shift,
});
}
break;
@@ -688,10 +792,11 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.secondary_cbuf_index = cbuf.secondary_index,
.secondary_cbuf_offset = cbuf.secondary_offset,
.secondary_shift_left = cbuf.secondary_shift_left,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
.count = count,
.size_shift = size_shift,
});
} else {
count = std::min(count, sampled_dynamic_cap);
index = descriptors.Add(TextureDescriptor{
.type = flags.type,
.is_depth = flags.is_depth != 0,
@@ -703,8 +808,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.secondary_cbuf_index = cbuf.secondary_index,
.secondary_cbuf_offset = cbuf.secondary_offset,
.secondary_shift_left = cbuf.secondary_shift_left,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
.count = count,
.size_shift = size_shift,
});
}
break;
@@ -712,12 +817,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
flags.descriptor_index.Assign(index);
inst->SetFlags(flags);
if (cbuf.count > 1) {
if (count > 1) {
const auto insert_point{IR::Block::InstructionList::s_iterator_to(*inst)};
IR::IREmitter ir{*texture_inst.block, insert_point};
const IR::U32 shift{ir.Imm32(DESCRIPTOR_SIZE_SHIFT)};
const IR::U32 shift{ir.Imm32(size_shift)};
inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift),
ir.Imm32(DESCRIPTOR_SIZE - 1)));
ir.Imm32(count - 1)));
} else {
inst->SetArg(0, IR::Value{});
}
+4
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -45,6 +48,7 @@ struct Profile {
bool support_scaled_attributes{};
bool support_multi_viewport{};
bool support_geometry_streams{};
bool support_sampled_image_array_nonuniform_indexing{};
bool warp_size_potentially_larger_than_guest{};
+4 -1
View File
@@ -323,7 +323,10 @@ if (ENABLE_OPENGL)
endif()
target_link_libraries(video_core PUBLIC common core)
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder gpu_logging)
target_link_libraries(video_core PUBLIC shader_recompiler stb bc_decoder gpu_logging)
if (ENABLE_OPENGL)
target_link_libraries(video_core PUBLIC glad)
endif()
if (YUZU_USE_EXTERNAL_FFMPEG)
add_dependencies(video_core ffmpeg-build)
@@ -189,6 +189,7 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
views += num_texture_buffers;
views += num_image_buffers;
for (const auto& desc : info.texture_descriptors) {
bool is_rescaled{};
for (u32 index = 0; index < desc.count; ++index) {
const VideoCommon::ImageViewId image_view_id{(views++)->id};
const VideoCommon::SamplerId sampler_id{*(samplers++)};
@@ -200,10 +201,13 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
rescaling.PushTexture(texture_cache.IsRescaling(image_view));
const bool element_rescaled{texture_cache.IsRescaling(image_view)};
is_rescaled |= element_rescaled;
}
rescaling.PushTexture(is_rescaled);
}
for (const auto& desc : info.image_descriptors) {
bool is_rescaled{};
for (u32 index = 0; index < desc.count; ++index) {
ImageView& image_view{texture_cache.GetImageView((views++)->id)};
if (desc.is_written) {
@@ -211,8 +215,10 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
}
const VkImageView vk_image_view{image_view.StorageView(desc.type, desc.format)};
guest_descriptor_queue.AddImage(vk_image_view);
rescaling.PushImage(texture_cache.IsRescaling(image_view));
const bool element_rescaled{texture_cache.IsRescaling(image_view)};
is_rescaled |= element_rescaled;
}
rescaling.PushImage(is_rescaled);
}
}
@@ -127,9 +127,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
texture_cache.SynchronizeComputeDescriptors();
static constexpr size_t max_elements = 64;
boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views;
boost::container::static_vector<VideoCommon::SamplerId, max_elements> samplers;
boost::container::small_vector<VideoCommon::ImageViewInOut, 64> views;
boost::container::small_vector<VideoCommon::SamplerId, 64> samplers;
const auto& qmd{kepler_compute.launch_description};
const auto& cbufs{qmd.const_buffer_config};
@@ -412,6 +412,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_scaled_attributes = !device.MustEmulateScaledFormats(),
.support_multi_viewport = device.SupportsMultiViewport(),
.support_geometry_streams = device.AreTransformFeedbackGeometryStreamsSupported(),
.support_sampled_image_array_nonuniform_indexing =
device.IsSampledImageArrayNonUniformIndexingSupported(),
.warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(),
@@ -447,6 +449,9 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_snorm_render_buffer = true,
.support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
.min_ssbo_alignment = static_cast<u32>(device.GetStorageBufferAlignment()),
.max_per_stage_descriptor_sampled_images = device.GetMaxPerStageDescriptorSampledImages(),
.max_per_stage_resources = device.GetMaxPerStageResources(),
.max_descriptor_set_sampled_images = device.GetMaxDescriptorSetSampledImages(),
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
.support_conditional_barrier = device.SupportsConditionalBarriers(),
};
+12 -127
View File
@@ -70,14 +70,10 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
(std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical),
DEFAULT_CRITICAL_MEMORY));
minimum_memory = static_cast<u64>((device_local_memory - mem_threshold) / 2);
lowmemorydevice = false;
} else {
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
minimum_memory = 0;
lowmemorydevice = true;
}
const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue();
@@ -122,102 +118,48 @@ void TextureCache<P>::RunGarbageCollector() {
bool aggressive_mode = false;
u64 ticks_to_destroy = 0;
size_t num_iterations = 0;
const auto Configure = [&](bool allow_aggressive) {
high_priority_mode = total_used_memory >= expected_memory;
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
};
const auto Cleanup = [this, &num_iterations, &high_priority_mode,
&aggressive_mode](ImageId image_id) {
const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) {
if (num_iterations == 0) {
return true;
}
--num_iterations;
auto& image = slot_images[image_id];
// Never delete recently allocated sparse textures (within 3 frames)
const bool is_recently_allocated = image.allocation_tick >= frame_tick - 3;
if (is_recently_allocated && image.info.is_sparse) {
return false;
}
if (True(image.flags & ImageFlagBits::IsDecoding)) {
// This image is still being decoded, deleting it will invalidate the slot
// used by the async decoder thread.
return false;
}
// Prioritize large sparse textures for cleanup
const bool is_large_sparse = lowmemorydevice &&
image.info.is_sparse &&
image.guest_size_bytes >= 256_MiB;
if (!aggressive_mode && !is_large_sparse &&
True(image.flags & ImageFlagBits::CostlyLoad)) {
const bool must_download = image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) {
return false;
}
const bool must_download =
image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
if (!high_priority_mode && !is_large_sparse && must_download) {
return false;
}
if (must_download && !is_large_sparse) {
--num_iterations;
if (must_download) {
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
image.DownloadMemory(map, copies);
runtime.Finish();
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
swizzle_data_buffer);
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, swizzle_data_buffer);
}
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, image_id);
}
UnregisterImage(image_id);
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
if (total_used_memory < critical_memory) {
if (aggressive_mode) {
// Sink the aggresiveness.
num_iterations >>= 2;
aggressive_mode = false;
return false;
}
if (high_priority_mode && total_used_memory < expected_memory) {
num_iterations >>= 1;
high_priority_mode = false;
}
if (aggressive_mode && total_used_memory < critical_memory) {
num_iterations >>= 2;
aggressive_mode = false;
} else if (high_priority_mode && total_used_memory < expected_memory) {
num_iterations >>= 1;
high_priority_mode = false;
}
return false;
};
// Aggressively clear massive sparse textures
if (total_used_memory >= expected_memory) {
lru_cache.ForEachItemBelow(frame_tick, [&](ImageId image_id) {
auto& image = slot_images[image_id];
// Only target sparse textures that are old enough
if (lowmemorydevice &&
image.info.is_sparse &&
image.guest_size_bytes >= 256_MiB &&
image.allocation_tick < frame_tick - 3) {
LOG_DEBUG(HW_GPU, "GC targeting old sparse texture at 0x{:X} ({} MiB, age: {} frames)",
image.gpu_addr, image.guest_size_bytes / (1024 * 1024),
frame_tick - image.allocation_tick);
return Cleanup(image_id);
}
return false;
});
}
Configure(false);
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
// If pressure is still too high, prune aggressively.
if (total_used_memory >= critical_memory) {
Configure(true);
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
@@ -1196,9 +1138,6 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
}
image.flags &= ~ImageFlagBits::CpuModified;
if( lowmemorydevice && image.info.format == PixelFormat::BC1_RGBA_UNORM && MapSizeBytes(image) >= 256_MiB ) {
return;
}
TrackImage(image, image_id);
@@ -1619,39 +1558,6 @@ ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
}
}
ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr);
// For large sparse textures, aggressively clean up old allocations at same address
if (lowmemorydevice && info.is_sparse && CalculateGuestSizeInBytes(info) >= 256_MiB) {
const auto alloc_it = image_allocs_table.find(gpu_addr);
if (alloc_it != image_allocs_table.end()) {
const ImageAllocId alloc_id = alloc_it->second;
auto& alloc_images = slot_image_allocs[alloc_id].images;
// Collect old images at this address that were created more than 2 frames ago
boost::container::small_vector<ImageId, 4> to_delete;
for (ImageId old_image_id : alloc_images) {
Image& old_image = slot_images[old_image_id];
if (old_image.info.is_sparse &&
old_image.gpu_addr == gpu_addr &&
old_image.allocation_tick < frame_tick - 2) { // Try not to delete fresh textures
to_delete.push_back(old_image_id);
}
}
// Delete old images immediately
for (ImageId old_id : to_delete) {
Image& old_image = slot_images[old_id];
LOG_DEBUG(HW_GPU, "Immediately deleting old sparse texture at 0x{:X} ({} MiB)",
gpu_addr, old_image.guest_size_bytes / (1024 * 1024));
if (True(old_image.flags & ImageFlagBits::Tracked)) {
UntrackImage(old_image, old_id);
}
UnregisterImage(old_id);
DeleteImage(old_id, true);
}
}
}
const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr);
const Image& image = slot_images[image_id];
// Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different
@@ -1667,27 +1573,6 @@ template <class P>
ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DAddr cpu_addr) {
ImageInfo new_info = info;
const size_t size_bytes = CalculateGuestSizeInBytes(new_info);
// Proactive cleanup for large sparse texture allocations
if (lowmemorydevice && new_info.is_sparse && size_bytes >= 256_MiB) {
const u64 estimated_alloc_size = size_bytes;
if (total_used_memory + estimated_alloc_size >= critical_memory) {
LOG_DEBUG(HW_GPU, "Large sparse texture allocation ({} MiB) - running aggressive GC. "
"Current memory: {} MiB, Critical: {} MiB",
size_bytes / (1024 * 1024),
total_used_memory / (1024 * 1024),
critical_memory / (1024 * 1024));
RunGarbageCollector();
// If still over threshold after GC, try one more aggressive pass
if (total_used_memory + estimated_alloc_size >= critical_memory) {
LOG_DEBUG(HW_GPU, "Still critically low on memory, running second GC pass");
RunGarbageCollector();
}
}
}
const bool broken_views = runtime.HasBrokenTextureViewFormats();
const bool native_bgr = runtime.HasNativeBgr();
join_overlap_ids.clear();
@@ -478,7 +478,6 @@ private:
u64 minimum_memory;
u64 expected_memory;
u64 critical_memory;
bool lowmemorydevice = false;
size_t gpu_unswizzle_maxsize = 0;
size_t swizzle_chunk_size = 0;
u32 swizzle_slices_per_batch = 0;
@@ -33,6 +33,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(KHR, VariablePointer, VARIABLE_POINTERS, variable_pointer)
#define FOR_EACH_VK_FEATURE_1_2(FEATURE) \
FEATURE(EXT, DescriptorIndexing, DESCRIPTOR_INDEXING, descriptor_indexing) \
FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \
FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \
FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore)
@@ -335,6 +336,18 @@ public:
return properties.properties.limits.maxDescriptorSetUniformBuffersDynamic;
}
u32 GetMaxPerStageDescriptorSampledImages() const {
return properties.properties.limits.maxPerStageDescriptorSampledImages;
}
u32 GetMaxPerStageResources() const {
return properties.properties.limits.maxPerStageResources;
}
u32 GetMaxDescriptorSetSampledImages() const {
return properties.properties.limits.maxDescriptorSetSampledImages;
}
/// Returns float control properties of the device.
const VkPhysicalDeviceFloatControlsPropertiesKHR& FloatControlProperties() const {
return properties.float_controls;
@@ -355,6 +368,10 @@ public:
return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
}
bool IsSampledImageArrayNonUniformIndexingSupported() const {
return features.descriptor_indexing.shaderSampledImageArrayNonUniformIndexing;
}
/// Returns true if the device supports float64 natively.
bool IsFloat64Supported() const {
return features.features.shaderFloat64;
+4 -1
View File
@@ -411,8 +411,11 @@ endif()
target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common)
target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets Qt6::Charts Qt6::Concurrent)
target_link_libraries(yuzu PRIVATE Boost::headers Qt6::Widgets Qt6::Charts Qt6::Concurrent)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (ENABLE_OPENGL)
target_link_libraries(yuzu PRIVATE glad)
endif()
if (UNIX AND NOT APPLE)
target_link_libraries(yuzu PRIVATE Qt6::DBus)
+25 -9
View File
@@ -8,11 +8,17 @@
#include <string>
#include <tuple>
#include <type_traits>
#ifdef HAS_OPENGL
#include <glad/glad.h>
#endif
#include <QtCore/qglobal.h>
#include "common/settings_enums.h"
#include "qt_common/config/uisettings.h"
#include "qt_common/abstract/frontend.h"
#if YUZU_USE_QT_MULTIMEDIA
#include <QCamera>
#include <QImageCapture>
@@ -1039,11 +1045,12 @@ void GRenderWindow::InitializeNull() {
}
bool GRenderWindow::LoadOpenGL() {
#ifdef HAS_OPENGL
auto context = CreateSharedContext();
auto scope = context->Acquire();
if (!gladLoadGL()) {
QMessageBox::warning(
this, tr("Error while initializing OpenGL!"),
QtCommon::Frontend::Warning(
tr("Error while initializing OpenGL!"),
tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver."));
return false;
}
@@ -1052,15 +1059,16 @@ bool GRenderWindow::LoadOpenGL() {
const QString renderer =
QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
if (!GLAD_GL_VERSION_4_6) {
QMessageBox::warning(this, tr("Error while initializing OpenGL 4.6!"),
tr("Your GPU may not support OpenGL 4.6, or you do not have the "
"latest graphics driver.<br><br>GL Renderer:<br>%1")
.arg(renderer));
QtCommon::Frontend::Warning(
tr("Error while initializing OpenGL 4.6!"),
tr("Your GPU may not support OpenGL 4.6, or you do not have the "
"latest graphics driver.<br><br>GL Renderer:<br>%1")
.arg(renderer));
return false;
}
if (QStringList missing_ext = GetUnsupportedGLExtensions(); !missing_ext.empty()) {
QMessageBox::warning(
this, tr("Error while initializing OpenGL!"),
QtCommon::Frontend::Warning(
tr("Error while initializing OpenGL!"),
tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
"have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported "
"extensions:<br>%2")
@@ -1069,10 +1077,17 @@ bool GRenderWindow::LoadOpenGL() {
// Non fatal
}
return true;
#else
QtCommon::Frontend::Warning(
tr("Error while initializing OpenGL!"),
tr("This build doesn't have OpenGL support."));
return false;
#endif
}
QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
QStringList missing_ext;
QStringList missing_ext{};
#ifdef HAS_OPENGL
// Extensions required to support some texture formats.
if (!GLAD_GL_EXT_texture_compression_s3tc)
missing_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
@@ -1082,6 +1097,7 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
LOG_ERROR(Frontend, "GPU does not support all required extensions");
for (const QString& ext : missing_ext)
LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString());
#endif
return missing_ext;
}
+13 -3
View File
@@ -15,11 +15,18 @@ function(create_resource file output filename)
file(WRITE "${PROJECT_BINARY_DIR}/dist/${output}" "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n")
endfunction()
if (ENABLE_OPENGL)
list(APPEND OPENGL_SOURCES
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
)
else()
set(OPENGL_SOURCES "")
endif()
add_executable(yuzu-cmd
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
emu_window/emu_window_sdl2_null.cpp
emu_window/emu_window_sdl2_null.h
emu_window/emu_window_sdl2_vk.cpp
@@ -28,10 +35,13 @@ add_executable(yuzu-cmd
sdl_config.h
yuzu.cpp
yuzu.rc
${OPENGL_SOURCES}
)
target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common video_core)
target_link_libraries(yuzu-cmd PRIVATE glad)
if (ENABLE_OPENGL)
target_link_libraries(yuzu-cmd PRIVATE glad)
endif()
if (MSVC)
target_link_libraries(yuzu-cmd PRIVATE getopt)
endif()
+6 -10
View File
@@ -59,20 +59,16 @@ private:
};
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
std::vector<std::string_view> unsupported_ext;
std::vector<std::string_view> unsupported_ext{};
#ifdef HAS_OPENGL
// Extensions required to support some texture formats.
if (!GLAD_GL_EXT_texture_compression_s3tc) {
if (!GLAD_GL_EXT_texture_compression_s3tc)
unsupported_ext.push_back("EXT_texture_compression_s3tc");
}
if (!GLAD_GL_ARB_texture_compression_rgtc) {
if (!GLAD_GL_ARB_texture_compression_rgtc)
unsupported_ext.push_back("ARB_texture_compression_rgtc");
}
for (const auto& extension : unsupported_ext) {
for (const auto& extension : unsupported_ext)
LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", extension);
}
#endif
return unsupported_ext.empty();
}
+7
View File
@@ -31,7 +31,9 @@
#include "sdl_config.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#ifdef HAS_OPENGL
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
#endif
#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
@@ -349,17 +351,22 @@ int main(int argc, char** argv) {
std::unique_ptr<EmuWindow_SDL2> emu_window;
switch (Settings::values.renderer_backend.GetValue()) {
#ifdef HAS_OPENGL
case Settings::RendererBackend::OpenGL_GLSL:
case Settings::RendererBackend::OpenGL_GLASM:
case Settings::RendererBackend::OpenGL_SPIRV:
emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, system, fullscreen);
break;
#endif
case Settings::RendererBackend::Vulkan:
emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem, system, fullscreen);
break;
case Settings::RendererBackend::Null:
emu_window = std::make_unique<EmuWindow_SDL2_Null>(&input_subsystem, system, fullscreen);
break;
default:
LOG_CRITICAL(Frontend, "Invalid renderer backend");
return -1;
}
#ifdef _WIN32
-57
View File
@@ -1,57 +0,0 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
$ErrorActionPreference = "Stop"
# Check if running as administrator
try {
net session 1>$null 2>$null
} catch {
Write-Host "This script must be run with administrator privileges!"
Exit 1
}
$VulkanSDKVer = "1.4.341.1"
if ((Get-WmiObject -Class Win32_ComputerSystem).SystemType -match '(x64)' -eq "True") {
$VulkanSDKArch = "X64"
$VulkanSDKOs = "windows"
} else {
$VulkanSDKArch = "ARM64"
$VulkanSDKOs = "warm"
}
$VULKAN_SDK = "C:/VulkanSDK/$VulkanSDKVer"
$ExeFile = "vulkansdk-windows-$VulkanSDKArch-$VulkanSDKVer.exe"
$Uri = "https://sdk.lunarg.com/sdk/download/$VulkanSDKVer/$VulkanSDKOs/$ExeFile"
$Destination = "./$ExeFile"
# Check if Vulkan SDK is already installed
if (Test-Path $VULKAN_SDK) {
Write-Host "-- Vulkan SDK already installed at $VULKAN_SDK"
return
}
echo "Downloading Vulkan SDK $VulkanSDKVer from $Uri"
$WebClient = New-Object System.Net.WebClient
$WebClient.DownloadFile($Uri, $Destination)
echo "Finished downloading $ExeFile"
$Arguments = "--root `"$VULKAN_SDK`" --accept-licenses --default-answer --confirm-command install"
echo "Installing Vulkan SDK $VulkanSDKVer"
$InstallProcess = Start-Process -FilePath $Destination -NoNewWindow -PassThru -Wait -ArgumentList $Arguments
$ExitCode = $InstallProcess.ExitCode
if ($ExitCode -ne 0) {
echo "Error installing Vulkan SDK $VulkanSDKVer (Error: $ExitCode)"
Exit $ExitCode
}
echo "Finished installing Vulkan SDK $VulkanSDKVer"
if ("$env:GITHUB_ACTIONS" -eq "true") {
echo "VULKAN_SDK=$VULKAN_SDK" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "$VULKAN_SDK/Bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
}
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/sh
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
: "${VULKAN_SDK_VER:=1.4.341.1}"
: "${VULKAN_ROOT:=C:/VulkanSDK/$VULKAN_SDK_VER}"
VULKAN_SDK_ARCH=X64
VULKAN_SDK_OS=windows
case "$(uname)" in
*ARM64*)
VULKAN_SDK_ARCH=ARM64
VULKAN_SDK_OS=warm
;;
esac
EXE_FILE="vulkansdk-windows-$VULKAN_SDK_ARCH-$VULKAN_SDK_VER.exe"
URI="https://sdk.lunarg.com/sdk/download/$VULKAN_SDK_VER/$VULKAN_SDK_OS/$EXE_FILE"
VULKAN_ROOT_UNIX=$(cygpath -u "$VULKAN_ROOT")
# Check if Vulkan SDK is already installed
if [ -d "$VULKAN_ROOT_UNIX" ]; then
echo "-- Vulkan SDK already installed at $VULKAN_ROOT_UNIX"
exit 0
fi
echo "Downloading Vulkan SDK $VULKAN_SDK_VER from $URI"
[ ! -f "./$EXE_FILE" ] && curl -L -o "./$EXE_FILE" "$URI"
chmod +x "./$EXE_FILE"
echo "Finished downloading $EXE_FILE"
echo "Installing Vulkan SDK $VULKAN_SDK_VER..."
if net session > /dev/null 2>&1; then
./$EXE_FILE --root "$VULKAN_ROOT" --accept-licenses --default-answer --confirm-command install
else
echo "This script must be run with administrator privileges!"
exit 1
fi
echo "Finished installing Vulkan SDK $VULKAN_SDK_VER"
# GitHub Actions integration
if [ \"${GITHUB_ACTIONS:-false}\" = \"true\" ]; then
echo \"VULKAN_SDK=$VULKAN_ROOT\" >> \"$GITHUB_ENV\"
echo \"$VULKAN_ROOT/bin\" >> \"$GITHUB_PATH\"
fi
-42
View File
@@ -1,42 +0,0 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
$osArch = $env:PROCESSOR_ARCHITECTURE
switch ($osArch) {
"AMD64" { $arch = "x64" }
"ARM64" { $arch = "arm64" }
default {
Write-Error "load-msvc-env.ps1: Unsupported architecture: $osArch"
exit 1
}
}
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (!(Test-Path $vswhere)) {
Write-Error "load-msvc-env.ps1: vswhere not found"
exit 1
}
$vs = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if (-not $vs) {
Write-Error "load-msvc-env.ps1: Visual Studio (with Desktop development with C++) not found"
exit 1
}
$bat = "$vs\VC\Auxiliary\Build\vcvarsall.bat"
if (!(Test-Path $bat)) {
Write-Error "load-msvc-env.ps1: (vcvarsall.bat) not found"
exit 1
}
cmd /c "`"$bat`" $arch && set" | ForEach-Object {
if ($_ -match "^(.*?)=(.*)$") {
[Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')
}
}
Write-Host "load-msvc-env.ps1: MSVC environment loaded for $arch ($vs)"
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/bash
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
ARCH_RAW="$PROCESSOR_ARCHITECTURE"
case "$ARCH_RAW" in
AMD64) ARCH="x64" ;;
ARM64) ARCH="arm64" ;;
*) echo "load-msvc-env.sh: Unsupported architecture: $ARCH_RAW"; exit 1 ;;
esac
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VCVARS_BASH="$SCRIPT_DIR/vcvarsall.sh"
if [ ! -f "$VCVARS_BASH" ]; then
echo "load-msvc-env.sh: vcvarsall.sh not found in $SCRIPT_DIR"
#exit 1
fi
chmod +x "$VCVARS_BASH"
eval "$("$VCVARS_BASH" "$ARCH")"
echo "MSVC environment loaded for $ARCH with vcvars-bash script"
-433
View File
@@ -1,433 +0,0 @@
#!/usr/bin/env bash
#
# SPDX-FileCopyrightText: Copyright 2023 Nathan Poirier <nathan@poirier.io>
# SPDX-License-Identifier: MIT License
#
# Source: https://github.com/nathan818fr/vcvars-bash
#
set -Eeuo pipefail
shopt -s inherit_errexit
declare -r VERSION='2025-07-09.1'
function detect_platform() {
case "${OSTYPE:-}" in
cygwin* | msys* | win32)
declare -gr bash_platform='win_cyg'
;;
*)
if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then
declare -gr bash_platform='win_wsl'
else
printf 'error: Unsupported platform (%s)\n' "${OSTYPE:-}" >&2
printf 'hint: This script only supports Bash on Windows (Git Bash, WSL, etc.)\n' >&2
return 1
fi
;;
esac
}
function detect_mode() {
# Detect the mode depending on the name called
if [[ "$(basename -- "$0")" == vcvarsrun* ]]; then
declare -gr script_mode='run' # vcvarsrun.sh
else
declare -gr script_mode='default' # vcvarsall.sh
fi
}
function print_usage() {
case "$script_mode" in
default)
cat <<EOF
Usage: eval "\$(vcvarsall.sh [vcvarsall.bat arguments])"
Script version: $VERSION
Load MSVC environment variables using vcvarsall.bat and export them.
The script writes a list of export commands to stdout, to be evaluated by a
POSIX-compliant shell.
Example: eval "\$(vcvarsall.sh x86)"
EOF
;;
run)
cat <<EOF
Usage: vcvarsrun.sh [vcvarsall.bat arguments] -- command [arguments...]
Script version: $VERSION
Load MSVC environment variables using vcvarsall.bat and execute a command with
them.
Example: vcvarsrun.sh x64 -- cl /nologo /EHsc /Fe:hello.exe hello.cpp
EOF
;;
esac
cat <<EOF
Environment variables:
VSINSTALLDIR
The path to the Visual Studio installation directory.
Example: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community"
Default: The latest Visual Studio installation directory found by
vswhere.exe
VSWHEREPATH
The path to the vswhere.exe executable.
Example: "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe"
Default: Automatically detected (in the PATH or well-known locations)
VSWHEREARGS
The arguments to pass to vswhere.exe.
This value is always automatically prefixed with "-latest -property installationPath".
Default: "-products *"
EOF
local vcvarsall
vcvarsall=$(find_vcvarsall)
printf '\nvcvarsall.bat arguments: %s\n' "$({ cmd "$(cmdesc "$vcvarsall")" -help </dev/null 2>/dev/null || true; } | fix_crlf | sed '/^Syntax:/d; s/^ vcvarsall.bat //')"
}
function main() {
detect_platform
detect_mode
# Parse arguments
if [[ $# -eq 0 ]]; then
print_usage >&2
return 1
fi
local arg vcvarsall_args=()
for arg in "$@"; do
shift
if [[ "$arg" == '--' ]]; then
if [[ "$script_mode" == 'default' ]]; then
printf 'error: Unexpected argument: --\n' >&2
printf 'hint: Use vcvarsrun to run a command\n' >&2
return 1
fi
break
fi
vcvarsall_args+=("$(cmdesc "$arg")")
done
if [[ "$script_mode" == 'run' && $# -eq 0 ]]; then
printf 'error: No command specified\n' >&2
return 1
fi
# Get MSVC environment variables from vcvarsall.bat
local vcvarsall vcvarsall_env
vcvarsall=$(find_vcvarsall)
vcvarsall_env=$({ cmd "$(cmdesc "$vcvarsall")" "${vcvarsall_args[@]}" '&&' 'set' </dev/null || true; } | fix_crlf)
# Filter MSVC environment variables and export them.
# The list of variables to export was based on a comparison between a clean environment and the vcvarsall.bat
# environment (on different MSVC versions, tools and architectures).
#
# Windows environment variables are case-insensitive while Unix-like environment variables are case-sensitive, so:
# - we always use uppercase names to prevent duplicates environment variables on the Unix-like side
# - we also ensure that only the first occurrence of a variable is exported (see below)
#
# While Windows environment variables are case-insensitive, it is possible to have duplicates in some edge cases.
# e.g. using Git Bash:
# export xxx=1; export XXX=2; export xXx=3; cmd.exe //c set XxX=4 '&&' set
# will output:
# XXX=4
# xXx=3
# xxx=1
declare -A seen_vars
function export_env() {
local name=${1^^}
local value=$2
if [[ ! "$name" =~ ^[A-Z0-9_]+$ ]]; then return; fi
if [[ -n "${seen_vars[$name]:-}" ]]; then return; fi
seen_vars[$name]=1
if [[ "$script_mode" == 'run' ]]; then
export "${name}=${value}"
else
printf "export %s='%s'\n" "$name" "${value//\'/\'\\\'\'}"
fi
}
local name value initialized=false
while IFS='=' read -r name value; do
if [[ "$initialized" == 'false' ]]; then
if [[ -n "$value" ]]; then name+="=$value"; fi
if [[ "$name" == *' Environment initialized for: '* ]]; then initialized=true; fi
printf '%s\n' "$name" >&2
continue
fi
case "${name^^}" in
LIB | LIBPATH | INCLUDE | EXTERNAL_INCLUDE | COMMANDPROMPTTYPE | DEVENVDIR | EXTENSIONSDKDIR | FRAMEWORK* | \
PLATFORM | PREFERREDTOOLARCHITECTURE | UCRT* | UNIVERSALCRTSDK* | VCIDE* | VCINSTALL* | VCPKG* | VCTOOLS* | \
VSCMD* | VSINSTALL* | VS[0-9]* | VISUALSTUDIO* | WINDOWSLIB* | WINDOWSSDK*)
export_env "$name" "$value"
;;
PATH)
# PATH is a special case, requiring special handling
local new_paths
new_paths=$(pathlist_win_to_unix "$value") # Convert to unix-style path list
new_paths=$(pathlist_normalize "${PATH}:${new_paths}") # Prepend the current PATH
export_env 'WINDOWS_PATH' "$value"
export_env 'PATH' "$new_paths"
;;
esac
done <<<"$vcvarsall_env"
if [[ "$initialized" == 'false' ]]; then
printf 'error: vcvarsall.bat failed' >&2
return 1
fi
# Execute command if needed
if [[ "$script_mode" == 'run' ]]; then
exec "$@"
fi
}
# Locate vcvarsall.bat
# Inputs:
# VSINSTALLDIR: The path to the Visual Studio installation directory (optional)
# VSWHEREPATH: The path to the vswhere.exe executable (optional)
# VSWHEREARGS: The arguments to pass to vswhere.exe (optional)
# Outputs:
# stdout: The windows-style path to vcvarsall.bat
function find_vcvarsall() {
local vsinstalldir
if [[ -n "${VSINSTALLDIR:-}" ]]; then
vsinstalldir="$VSINSTALLDIR"
else
local vswhere
if [[ -n "${VSWHEREPATH:-}" ]]; then
vswhere=$(unixpath "$VSWHEREPATH")
else
vswhere=$(command -v 'vswhere' 2>/dev/null || unixpath 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe')
fi
local vswhereargs=(-latest -property installationPath)
if [[ -n "${VSWHEREARGS:-}" ]]; then
parse_args_simple "$VSWHEREARGS"
vswhereargs+=("${result[@]}")
else
vswhereargs+=(-products '*')
fi
if [[ "${VCVARSBASH_DEBUG:-}" == 1 ]]; then
printf 'debug: vswhere path: %s\n' "$vswhere" >&2
printf 'debug: vswhere args:\n' >&2
printf 'debug: [ %s ]\n' "${vswhereargs[@]}" >&2
fi
vsinstalldir=$("$vswhere" "${vswhereargs[@]}" </dev/null | fix_crlf)
if [[ -z "$vsinstalldir" ]]; then
printf 'error: vswhere returned an empty installation path\n' >&2
return 1
fi
fi
printf '%s\n' "$(winpath "$vsinstalldir")\\VC\\Auxiliary\\Build\\vcvarsall.bat"
}
# Split command arguments into an array, with a simple logic
# - Arguments are separated by whitespace (space, tab, newline, carriage return)
# - To include whitespace in an argument, it must be enclosed in double quotes (")
# - When inside a double-quoted argument, quotes can be escaped by doubling them ("")
# Inputs:
# $1: The string to parse
# Outputs:
# result: An array of parsed arguments
function parse_args_simple() {
declare -g result=()
local str="$1 " # add a trailing space to simplify the last argument handling
local args=()
local current_type=none # none = waiting for next arg, normal = inside normal arg, quoted = inside quoted arg
local current_value=''
local i=0 len=${#str}
while ((i < len)); do
local c=${str:i:1}
case "$current_type" in
none)
case "$c" in
' ' | $'\t' | $'\n' | $'\r')
# Ignore whitespace
;;
'"')
# Start a quoted argument
current_type=quoted
current_value=''
;;
*)
# Start a normal argument
current_type=normal
current_value="$c"
;;
esac
;;
normal)
case "$c" in
' ' | $'\t' | $'\n' | $'\r')
# End of normal argument, add it to the list
args+=("$current_value")
current_type=none
current_value=''
;;
*)
# Continue building the normal argument
current_value+="$c"
;;
esac
;;
quoted)
case "$c" in
'"')
if [[ "${str:i+1:1}" != '"' ]]; then
# End of quoted argument, add it to the list
args+=("$current_value")
current_type=none
current_value=''
else
# Escaped quote, add a single quote to the current value
current_value+='"'
((++i)) # Skip the next quote
fi
;;
*)
# Continue building the quoted argument
current_value+="$c"
;;
esac
;;
esac
((++i))
done
if [[ "$current_type" != none ]]; then
printf 'error: Unfinished %s argument: %s\n' "$current_type" "$current_value" >&2
return 1
fi
declare -g result=("${args[@]}")
}
# Run a command with cmd.exe
# Inputs:
# $@: The command string to run (use cmdesc to escape arguments when needed)
# Outputs:
# stdout: The cmd.exe standard output
# stderr: The cmd.exe error output
function cmd() {
# This seems to work fine on all supported platforms
# (even with all the weird path and argument conversions on MSYS-like)
MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' cmd.exe /s /c " ; $* "
}
# Escape a cmd.exe command argument
# Inputs:
# $1: The argument to escape
# Outputs:
# stdout: The escaped argument
function cmdesc() {
# shellcheck disable=SC2001
sed 's/[^0-9A-Za-z]/^\0/g' <<<"$1"
}
# Convert path to an absolute unix-style path
# Inputs:
# $1: The path to convert
# Outputs:
# stdout: The converted path
function unixpath() {
local path=$1
case "$bash_platform" in
win_wsl)
case "$path" in
[a-zA-Z]:\\* | [a-zA-Z]:/* | \\\\* | //*)
# Convert windows path using wslpath (unix mode, absolute path)
wslpath -u -a -- "$path"
;;
*)
# Convert unix path using realpath
realpath -m -- "$path"
;;
esac
;;
*)
cygpath -u -a -- "$path"
;;
esac
}
# Convert path to an absolute windows-style path
# Inputs:
# $1: The path to convert
# Outputs:
# stdout: The converted path
function winpath() {
local path=$1
case "$bash_platform" in
win_wsl)
case "$path" in
[a-zA-Z]:\\* | [a-zA-Z]:/* | \\\\* | //*)
# Already a windows path
printf '%s' "$path"
;;
*)
# Convert using wslpath (windows mode, absolute path)
wslpath -w -a -- "$path"
;;
esac
;;
*)
# Convert using cygpath (windows mode, absolute path, long form)
cygpath -w -a -l -- "$path"
;;
esac
}
# Convert a windows-style path list to a unix-style path list
# Inputs:
# $1: The windows-style path list to convert
# Outputs:
# stdout: The converted unix-style path list
function pathlist_win_to_unix() {
local win_paths=$1
local path_dir first=true
while IFS= read -r -d';' path_dir; do
if [[ -z "$path_dir" ]]; then continue; fi
if [[ "$first" == 'true' ]]; then first=false; else printf ':'; fi
printf '%s' "$(unixpath "$path_dir")"
done <<<"${win_paths};"
}
# Normalize a unix-style path list, removing duplicates and empty entries
# Inputs:
# $1: The list to normalize
# Outputs:
# stdout: The normalized path list
function pathlist_normalize() {
local unix_paths=$1
declare -A seen_paths
local path_dir first=true
while IFS= read -r -d ':' path_dir; do
if [[ -z "$path_dir" ]]; then continue; fi
if [[ -n "${seen_paths[$path_dir]:-}" ]]; then continue; fi
seen_paths[$path_dir]=1
if [[ "$first" == 'true' ]]; then first=false; else printf ':'; fi
printf '%s' "$path_dir"
done <<<"${unix_paths}:"
}
# Convert CRLF to LF
# Inputs:
# stdin: The input to convert
# Outputs:
# stdout: The converted input
function fix_crlf() {
sed 's/\r$//'
}
eval 'main "$@";exit "$?"'