Compare commits

..

20 Commits

Author SHA1 Message Date
PavelBARABANOV 1d1bb8af7e [android] move GENSHIN_SPOOF flag to genshinSpoof flavor 2026-05-04 17:04:29 +03: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
lizzie 8765b49512 [video_core] fix H264 and jthread() causing spurious errors (#3907)
fixes regression by #3878

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3907
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-04-29 19:23:20 +02:00
lizzie a587b7dc3a [hle/nvdrv] drop redundant shared_ptr<> in internal nvhost_as_gpu mappings (#3858)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3858
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-04-29 16:44:28 +02:00
lizzie 90515bc6a2 [host1x] fix ffmpeg not having va-api on freebsd, inline nvenc (#3878)
- fix va-api not being used on freebsd

small thingies dont affect a lot:
- removes some pointer indirection (why save pointer to GMMU if its accesible via host1x)
- use std::variant<> for decoder
- miscelly vp9/v8/h264 opts
Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3878
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-04-29 16:41:25 +02:00
crueter 676b1aabfc [frontend] Built-in auto updater (#3845)
Checks latest release and opens a dialog containing the changelog, and
allow the user to select a specific build to download. After
downloading, it prompts the user to open it.

On Windows, this just opens up the zip in File Explorer. In the future setup files will be available. On macOS this opens up the DMG in Finder so the user can drag it to the Applications folder. Android retains the auto-update functionality from before, but updated to the new scheme. Body/View on Forgejo are not implemented, that should be in a future PR.

Additionally, moved some common httplib incantations to `Common::Net`. This will serve as the common network accessor and JSON parser from here on out.

TODO:
- [x] android :(
- [x] Search for builds based on keywords, with weights towards certain builds (e.g. macOS will search for dmg then tar.gz, windows msvc then mingw/exe then zip, etc.)
- [x] remove linux leftovers
- [x] don't allow asset selection on platforms w/o assets
- [x] nightly changelog should be in the real

FUTURE IMPLEMENTATION:
- [ ] Body/View on Forgejo for Android
- [ ] Setup files for Windows (Eden/nightly are separate) -- maybe portable/setup selector?
- [ ] Something else I'm forgetting

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3845
2026-04-28 20:42:23 +02:00
lizzie 77decca678 [video_core/engines/maxwell3d] memory inline DrawState to reduce indirection on hot paths (#3758)
usual indirection removal
helps very slightly to codegen

the idea is basically to reduce the amount of pointer deference overall in the code, and use idiomatic std::variant<>-isms to not rely on vtables/unique_ptr overhead
this should allow the compiler to emit better code
of course it's a tiny optimisation and only CPU side, but allows us to reduce indirection which is almost always a good thing

"but youre passing more parameters to the function!!!" its literally memoized into a register my friend

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3758
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-04-28 17:44:51 +02:00
Eden CI ed225f8a8b [dist, android] Update translations from Transifex for Apr 28 (#3902)
Automatic translation update for Apr 28

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3902
2026-04-28 16:11:44 +02:00
lizzie d69bd86183 [memory] coalesce redundant remappings of MultiPageLevel (#3857)
there is no need to call mmap() over the mapped region as the OS will automatically map it via lazy paging

basically the mmap() and virtualAlloc on a region already allocated is a no-op (FOR THIS SPECIFIC USECASE)

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3857
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-04-28 01:17:56 +02:00
lizzie c172abfb53 [hle] reuse previous pagetable when initializing new processes on the same KProcessPageTable (#3891)
VirtualBuffer<> would be recreated each time due to the `operator=()` from the unique_ptr<> when initializing a new process, this change makes it so said thing doesn't happen (instead it resizes the existing buffer)

this means that consecutive launches of the same process that happen to have the same process page table (or reuse it) will no longer incur a ctor/dtor path for VirtualBuffer and instead just resize the existing one

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3891
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-04-28 01:15:21 +02:00
lizzie d33dc16820 [dynarmic] set BL terminal as FastLinkBlock (#3811)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3811
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-04-28 01:12:35 +02:00
lizzie 8cdaf19a83 [video_core] simplify InvalidationAccumulator (#3890)
various redundant fields aren't required, so just redo it for good measure

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3890
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-04-28 00:24:54 +02:00
lizzie f088f5bd45 [loader] change ASLR algo to be more uniform (#3869)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3869
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-04-27 05:09:55 +02:00
lizzie cbeea5b954 [hle] handle NPad shared_memory being null on certain updates and cases (#3860)
Does NOT fix root issue with shared_memory of NPad yet, that is something probably separate
I'd like to suspect it's because every NPad by default it's expected to have shared memory, even when not explicitly initialized, but seems unlikely

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3860
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-04-27 04:01:49 +02:00
Duncan Ogilvie 1590e7c061 [core] GDB improvements (#3848)
The commands to reproduce the issues are in the commit messages. I tested on Super Mario Odyssey using [nx2elf](https://github.com/shuffle2/nx2elf) converted binaries and gdb-multiarch:

```
(gdb) monitor info
Process:     0x51 (Application)
Program Id:  0x0100000000010000
Layout:
  Alias: 0x1103400000 - 0x21033fffff
  Heap:  0x2103400000 - 0x23033fffff
  Aslr:  0x0008000000 - 0x7fffffffff
  Stack: 0x1083400000 - 0x11033fffff
Modules:
  0x0080b3d000 - 0x0080b40fff nnrtld
  0x0080b41000 - 0x0081ff1fff RedStar.nss
  0x0081ff2000 - 0x008270efff multimedia
  0x008270f000 - 0x00833e2fff nnSdk
(gdb) monitor mappings
Mappings:
  0x0000000000 - 0x0080b3cfff     Free ----- [0, 0]
  0x0080b3d000 - 0x0080b3efff r-x Code ----- [0, 0]
  0x0080b3f000 - 0x0080b3ffff r-- Code ----- [0, 0]
  0x0080b40000 - 0x0080b40fff rw- CodeData ----- [0, 0]
  0x0080b41000 - 0x008156bfff r-x Code ----- [0, 0]
  0x008156c000 - 0x0081cdafff r-- Code ----- [0, 0]
  0x0081cdb000 - 0x0081ff1fff rw- CodeData ----- [0, 0]
  0x0081ff2000 - 0x0082365fff r-x Code ----- [0, 0]
  0x0082366000 - 0x00825c0fff r-- Code ----- [0, 0]
  0x00825c1000 - 0x008270efff rw- CodeData ----- [0, 0]
  0x008270f000 - 0x0082c3cfff r-x Code ----- [0, 0]
  0x0082c3d000 - 0x00832bffff r-- Code ----- [0, 0]
  0x00832c0000 - 0x00833e2fff rw- CodeData ----- [0, 0]
  0x00833e3000 - 0x0083403fff     Free ----- [0, 0]
  0x0083404000 - 0x0083404fff rw- ThreadLocal ----- [0, 0]
  0x0083405000 - 0x1083403fff     Free ----- [0, 0]
  0x1083404000 - 0x1083503fff rw- Stack ----- [0, 0]
  0x1083504000 - 0x7fffffffff     Free ----- [0, 0]
(gdb) set sysroot
(gdb) set solib-search-path /Users/duncan/Downloads/smo-program
Reading symbols from /Users/duncan/Downloads/smo-program/rtld.elf...
(No debugging symbols found in /Users/duncan/Downloads/smo-program/rtld.elf)
Reading symbols from /Users/duncan/Downloads/smo-program/main.elf...
(No debugging symbols found in /Users/duncan/Downloads/smo-program/main.elf)
Reading symbols from /Users/duncan/Downloads/smo-program/subsdk0.elf...
(No debugging symbols found in /Users/duncan/Downloads/smo-program/subsdk0.elf)
Reading symbols from /Users/duncan/Downloads/smo-program/sdk.elf...
(No debugging symbols found in /Users/duncan/Downloads/smo-program/sdk.elf)
(gdb) info shared
From                To                  Syms Read   Shared Object Library
0x0000000080b3d000  0x0000000080b41000  Yes (*)     /Users/duncan/Downloads/smo-program/rtld.elf
0x0000000080b41000  0x0000000081ff2000  Yes (*)     /Users/duncan/Downloads/smo-program/main.elf
0x0000000081ff2000  0x000000008270f000  Yes (*)     /Users/duncan/Downloads/smo-program/subsdk0.elf
0x000000008270f000  0x00000000833e3000  Yes (*)     /Users/duncan/Downloads/smo-program/sdk.elf
(*): Shared library is missing debugging information.
(gdb) info functions nnMain
All functions matching regular expression "nnMain":

Non-debugging symbols:
0x0000000081024250  nnMain
0x0000000082c2de40  nnMain@plt
(gdb) b *nnMain
Breakpoint 1 at 0x81024250
(gdb) c
Continuing.

Breakpoint 1, 0x0000000081024250 in nnMain () from /Users/duncan/Downloads/smo-program/main.elf
(gdb) x/10i $pc
=> 0x81024250 <nnMain>:	stp	x22, x21, [sp, #-48]!
   0x81024254 <nnMain+4>:	stp	x20, x19, [sp, #16]
   0x81024258 <nnMain+8>:	stp	x29, x30, [sp, #32]
   0x8102425c <nnMain+12>:	add	x29, sp, #0x20
   0x81024260 <nnMain+16>:	bl	0x81569aa0
   0x81024264 <nnMain+20>:	mov	w19, w0
   0x81024268 <nnMain+24>:	bl	0x81569ab0
   0x8102426c <nnMain+28>:	mov	x20, x0
   0x81024270 <nnMain+32>:	mov	w0, w19
   0x81024274 <nnMain+36>:	mov	x1, x20
```

Symlinked like this:

```
ls -l /Users/duncan/Downloads/smo-program
total 687472
-rw-r--r--@ 1 duncan  staff   20385356 Apr  9 18:12 main.elf
lrwxr-xr-x@ 1 duncan  staff         11 Apr  9 15:26 multimedia -> subsdk0.elf
lrwxr-xr-x@ 1 duncan  staff          8 Apr  9 15:28 nnrtld -> rtld.elf
lrwxr-xr-x@ 1 duncan  staff          7 Apr  9 15:26 nnSdk -> sdk.elf
lrwxr-xr-x@ 1 duncan  staff          8 Apr  9 15:26 RedStar.nss -> main.elf
-rw-r--r--@ 1 duncan  staff      12440 Apr  9 18:12 rtld.elf
-rw-r--r--@ 1 duncan  staff   12662960 Apr  9 18:12 sdk.elf
-rw-r--r--@ 1 duncan  staff    6294336 Apr  9 18:12 subsdk0.elf
```

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3848
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-04-27 03:39:34 +02:00
lizzie c95f8df8a5 [docs] update deps for illumos, instructions for OmniOS (#3892)
omniOS is different enough from openIndiana that they warrant their own section

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3892
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-04-26 21:17:40 +02:00
crueter e81f5111de [android] Fix auto updater by using https scheme (#3895)
Seems like some phone vendors require https for URLs? Weird behavior,
works on some phones but not others

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3895
2026-04-26 21:15:07 +02:00
177 changed files with 17920 additions and 17876 deletions
+4 -9
View File
@@ -268,11 +268,6 @@ if (NOT EXISTS ${PROJECT_BINARY_DIR}/${compat_json})
file(WRITE ${PROJECT_BINARY_DIR}/${compat_json} "")
endif()
if (YUZU_LEGACY)
message(WARNING "Making legacy build. Performance may suffer.")
add_compile_definitions(YUZU_LEGACY)
endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
set(HAS_NCE 1)
add_compile_definitions(HAS_NCE=1)
@@ -424,10 +419,10 @@ if (zstd_ADDED)
add_library(zstd::libzstd ALIAS libzstd_static)
endif()
if (NOT YUZU_STATIC_ROOM)
# nlohmann
AddJsonPackage(nlohmann)
# nlohmann
AddJsonPackage(nlohmann)
if (NOT YUZU_STATIC_ROOM)
# zlib
AddJsonPackage(zlib)
@@ -485,7 +480,7 @@ endfunction()
# =============================================
if (APPLE)
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia)
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security)
find_library(${fw}_LIBRARY ${fw} REQUIRED)
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
endforeach()
+4 -2
View File
@@ -33,9 +33,11 @@ endif()
set(GIT_DESC ${BUILD_VERSION})
# Generate cpp with Git revision from template
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
# Auto-updater metadata! Must somewhat mirror GitHub/Forgejo API endpoint
# TODO(crueter): Stable releases feed.
set(BUILD_AUTO_UPDATE_STABLE_REPO "eden-emu/eden")
set(BUILD_AUTO_UPDATE_STABLE_API "git.eden-emu.dev")
set(BUILD_AUTO_UPDATE_STABLE_API_PATH "/api/v1/repos/")
set(BUILD_AUTO_UPDATE_API_PATH "/latest/release.json")
if (NIGHTLY_BUILD)
+566 -498
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+568 -500
View File
File diff suppressed because it is too large Load Diff
+639 -567
View File
File diff suppressed because it is too large Load Diff
+566 -499
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -499
View File
File diff suppressed because it is too large Load Diff
+570 -502
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+564 -497
View File
File diff suppressed because it is too large Load Diff
+566 -499
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+572 -503
View File
File diff suppressed because it is too large Load Diff
+565 -498
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -498
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+566 -496
View File
File diff suppressed because it is too large Load Diff
+574 -503
View File
File diff suppressed because it is too large Load Diff
+566 -498
View File
File diff suppressed because it is too large Load Diff
+30 -4
View File
@@ -4,7 +4,8 @@
- [Arch Linux](#arch-linux)
- [Gentoo Linux](#gentoo-linux)
- [macOS](#macos)
- [Solaris](#solaris)
- [OpenIndiana](#openindiana)
- [OmniOS](#omnios)
- [HaikuOS](#haikuos)
- [OpenBSD](#openbsd)
- [FreeBSD](#freebsd)
@@ -31,14 +32,14 @@ If you're having issues with building, always consult that ebuild.
macOS is largely untested. Expect crashes, significant Vulkan issues, and other fun stuff.
## Solaris
## OpenIndiana
Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability.
Run the usual update + install of essential toolings: `sudo pkg update && sudo pkg install git cmake`.
- **gcc**: `sudo pkg install developer/gcc-14`.
- **clang**: Version 20 is broken, use `sudo pkg install developer/clang-19`.
- **gcc**: Install either `developer/gcc-14`.
- **clang**: Version 20 is broken, install `developer/clang-19`.
Qt Widgets appears to be broken. For now, add `-DENABLE_QT=OFF` to your configure command. In the meantime, a Qt Quick frontend is in the works--check back later!
@@ -67,6 +68,31 @@ export LIBGL_ALWAYS_SOFTWARE=1
- If using OpenIndiana, due to a bug in SDL2's CMake configuration, audio driver defaults to SunOS `<sys/audioio.h>`, which does not exist on OpenIndiana. Using external or bundled SDL2 may solve this.
- System OpenSSL generally does not work. Instead, use `-DYUZU_USE_BUNDLED_OPENSSL=ON` to use a bundled static OpenSSL, or build a system dependency from source.
## OmniOS
Install `developer/gcc14` on OmniOS using pkgsrc.
Since so many dependencies are missing on `OmniOS`, you may wish to use `-DCPMUTIL_FORCE_BUNDLED=ON -DYUZU_USE_EXTERNAL_SDL2=ON`
For OmniOS you are required to build glslang yourself:
```sh
sudo pkg install python-313
git clone --depth=1 https://github.com/KhronosGroup/glslang.git
cd glslang
python3.13 ./update_glslang_sources.py
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -- -j `nproc`
cmake --install build
```
It may be tempting to specify `-t glslang`, but this will cause installation to fail. So don't.
Using `--parallel` on CMake incorrectly passes `dmake ... -jn` instead of `dmake ... -j n`, this is a bug with OmniOS's CMake, and as such it's recommended to not use this option until it's fixed.
You may also need to install `gmake` in order to properly build FFmpeg, this is provided by the `build-essential` package.
If it wasn't obvious already, you require a X11 server to properly run the emulator within OmniOS, [this guide](https://web.archive.org/web/20260424200928/https://geekblood.wordpress.com/2017/10/26/installing-x11-and-a-desktop-environment-on-omnios/) is a great starting point for that, the links to pkgsrc are outdated so follow [this exemplar](https://pkgsrc.smartos.org/install-on-illumos/) as well:
## HaikuOS
It's recommended to do a `pkgman full-sync` before installing. See [HaikuOS: Installing applications](https://www.haiku-os.org/guides/daily-tasks/install-applications/). Sometimes the process may be interrupted by an error like "Interrupted syscall". Simply firing the command again fixes the issue. By default `g++` is included on the default installation.
+13 -3
View File
@@ -291,13 +291,23 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak
</details>
<details>
<summary>Solaris / OpenIndiana</summary>
<summary>OpenIndiana</summary>
```sh
sudo pkg install qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
```
[Caveats](./Caveats.md#solaris).
[Caveats](./Caveats.md#openindiana).
</details>
<details>
<summary>OmniOS</summary>
```sh
sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
```
[Caveats](./Caveats.md#omnios).
</details>
<details>
+10
View File
@@ -25,6 +25,16 @@ if (NIGHTLY_BUILD)
add_compile_definitions(NIGHTLY_BUILD)
endif()
if (YUZU_LEGACY)
message(WARNING "Making legacy build. Performance may suffer.")
add_compile_definitions(YUZU_LEGACY)
endif()
if (GENSHIN_SPOOF)
message(WARNING "Making Genshin spoof build")
add_compile_definitions(GENSHIN_SPOOF)
endif()
# Set compilation flags
if (MSVC AND NOT CXX_CLANG)
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
+6
View File
@@ -203,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,19 @@ 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 = "",
var body: String = "",
var url: String = "",
var assets: MutableList<String> = mutableListOf()
) {
fun addAsset(asset: String) {
assets.add(asset)
}
}
@JvmField
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
@@ -240,17 +253,7 @@ object NativeLibrary {
/**
* Checks for available updates.
*/
external fun checkForUpdate(): Array<String>?
/**
* Return the URL to the release page
*/
external fun getUpdateUrl(version: String): String
/**
* Return the URL to download the APK for the given version
*/
external fun getUpdateApkUrl(tag: String, artifact: String, packageId: String): String
external fun checkForUpdate(): UpdateResult?
/**
* Returns whether the update checker is enabled through CMAKE options.
@@ -175,25 +175,25 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val latestVersion = NativeLibrary.checkForUpdate()
if (latestVersion != null) {
runOnUiThread {
val tag: String = latestVersion[0]
val name: String = latestVersion[1]
showUpdateDialog(tag, name)
showUpdateDialog(latestVersion)
}
}
}.start()
}
private fun showUpdateDialog(tag: String, name: String) {
// TODO(crueter): body, "View on Forgejo" button
private fun showUpdateDialog(release: NativeLibrary.UpdateResult) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.update_available)
.setMessage(getString(R.string.update_available_description, name))
.setMessage(getString(R.string.update_available_description, release.title))
.setPositiveButton(android.R.string.ok) { _, _ ->
var artifact = tag
// Nightly builds have a slightly different format
if (NativeLibrary.isNightlyBuild()) {
artifact = tag.substringAfter('.', tag)
val assets = release.assets
if (assets.isEmpty()) {
openLink(release.url)
} else {
downloadAndInstallUpdate(release)
}
downloadAndInstallUpdate(tag, artifact)
}
.setNeutralButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
@@ -206,17 +206,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
.show()
}
private fun downloadAndInstallUpdate(version: String, artifact: String) {
private fun openLink(link: String) {
val intent = Intent(Intent.ACTION_VIEW, link.toUri())
startActivity(intent)
}
private fun downloadAndInstallUpdate(release: NativeLibrary.UpdateResult) {
CoroutineScope(Dispatchers.IO).launch {
val packageId = applicationContext.packageName
val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
val asset = release.assets[0]
val artifact = asset.split("/").last()
val apkFile = File(cacheDir, "update-$artifact.apk")
withContext(Dispatchers.Main) {
showDownloadProgressDialog()
}
val downloader = APKDownloader(apkUrl, apkFile)
val downloader = APKDownloader(asset, apkFile)
downloader.download(
onProgress = { progress ->
runOnUiThread {
@@ -248,7 +254,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} else {
Toast.makeText(
this@MainActivity,
getString(R.string.update_download_failed) + "\n\nURL: $apkUrl",
getString(R.string.update_download_failed) + "\n\nURL: $asset",
Toast.LENGTH_LONG
).show()
}
@@ -277,7 +283,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private fun updateDownloadProgress(progress: Int) {
progressBar?.progress = progress
progressMessage?.text = "$progress%"
progressMessage?.text = getString(R.string.percent, progress)
}
private fun dismissDownloadProgressDialog() {
+58 -58
View File
@@ -1699,76 +1699,76 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isNightlyBuild(
#ifdef ENABLE_UPDATE_CHECKER
JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
JNIEXPORT jobject JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
JNIEnv* env,
jobject obj) {
std::optional<UpdateChecker::Update> release = UpdateChecker::GetUpdate();
std::optional<Common::Net::Release> release = UpdateChecker::GetUpdate();
if (!release) return nullptr;
const std::string tag = release->tag;
const std::string name = release->name;
const std::string title = release->title;
const std::string body = release->body;
const std::string url = release->html_url;
jobjectArray result = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr);
// Android *should* only ever define a single asset.
// If not, something has gone wrong, but the Kotlin side can handle it.
const auto assets = release->GetPlatformAssets();
const jstring jtag = env->NewStringUTF(tag.c_str());
const jstring jname = env->NewStringUTF(name.c_str());
env->SetObjectArrayElement(result, 0, jtag);
env->SetObjectArrayElement(result, 1, jname);
env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jname);
return result;
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
JNIEnv* env,
jobject obj,
jstring version) {
const char* version_str = env->GetStringUTFChars(version, nullptr);
const std::string url = fmt::format("{}/{}",
std::string{Common::g_build_auto_update_api},
version_str);
env->ReleaseStringUTFChars(version, version_str);
return env->NewStringUTF(url.c_str());
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
JNIEnv* env,
jobject obj,
jstring tag,
jstring artifact,
jstring packageId) {
const char* version_str = env->GetStringUTFChars(tag, nullptr);
const char* artifact_str = env->GetStringUTFChars(artifact, nullptr);
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
std::string variant;
std::string package_id(package_id_str);
if (package_id.find("dev.legacy.eden_emulator") != std::string::npos) {
variant = "legacy";
} else if (package_id.find("com.miHoYo.Yuanshen") != std::string::npos) {
variant = "optimized";
} else {
#ifdef ARCHITECTURE_arm64
variant = "standard";
#else
variant = "chromeos";
#endif
jclass updateResultClass = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary$UpdateResult");
if (!updateResultClass) {
LOG_ERROR(Frontend, "Could not find UpdateResult class");
return nullptr;
}
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", artifact_str, variant);
jmethodID updateResultCtor = env->GetMethodID(updateResultClass, "<init>", "()V");
const std::string url = fmt::format("{}/{}/{}",
std::string{Common::g_build_auto_update_api},
version_str, apk_filename);
if (!updateResultCtor) {
LOG_ERROR(Frontend, "Could not find UpdateResult ctor");
env->DeleteLocalRef(updateResultClass);
return nullptr;
}
env->ReleaseStringUTFChars(tag, version_str);
env->ReleaseStringUTFChars(artifact, artifact_str);
env->ReleaseStringUTFChars(packageId, package_id_str);
return env->NewStringUTF(url.c_str());
jmethodID setTag = env->GetMethodID(updateResultClass, "setTag", "(Ljava/lang/String;)V");
jmethodID setTitle = env->GetMethodID(updateResultClass, "setTitle", "(Ljava/lang/String;)V");
jmethodID setBody = env->GetMethodID(updateResultClass, "setBody", "(Ljava/lang/String;)V");
jmethodID setUrl = env->GetMethodID(updateResultClass, "setUrl", "(Ljava/lang/String;)V");
jmethodID addAsset = env->GetMethodID(updateResultClass, "addAsset", "(Ljava/lang/String;)V");
jobject updateResult = env->NewObject(updateResultClass, updateResultCtor);
LOG_DEBUG(Frontend, "Tag: {}", tag);
LOG_DEBUG(Frontend, "Title: {}", title);
LOG_DEBUG(Frontend, "Body: {}", body);
LOG_DEBUG(Frontend, "Url: {}", url);
const auto jtag = env->NewStringUTF(tag.c_str());
const auto jtitle = env->NewStringUTF(title.c_str());
const auto jbody = env->NewStringUTF(body.c_str());
const auto jurl = env->NewStringUTF(url.c_str());
env->CallVoidMethod(updateResult, setTag, jtag);
env->CallVoidMethod(updateResult, setTitle, jtitle);
env->CallVoidMethod(updateResult, setBody, jbody);
env->CallVoidMethod(updateResult, setUrl, jurl);
// TODO(crueter): Handling for multiple assets?
// Maybe another data class x(
for (const Common::Net::Asset &a : assets) {
const auto jaurl = env->NewStringUTF(a.path.c_str());
env->CallVoidMethod(updateResult, addAsset, jaurl);
env->DeleteLocalRef(jaurl);
}
env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jtitle);
env->DeleteLocalRef(jbody);
env->DeleteLocalRef(jurl);
env->DeleteLocalRef(updateResultClass);
return updateResult;
}
#endif
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getBuildVersion(
@@ -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>
@@ -767,7 +767,7 @@
<string name="version">Versión</string>
<string name="copy_details">Copiar detalles</string>
<string name="add_ons">Complementos</string>
<string name="add_ons_description">Activa/desactiva mods, actualizaciones y contenidos descargables</string>
<string name="add_ons_description">Alternar mods, actualizaciones y contenido descargable</string>
<string name="playtime">Tiempo jugado:</string>
<string name="reset_playtime">Borrar tiempo de juego</string>
<string name="reset_playtime_description">Restablecer el tiempo de juego actual a 0 segundos</string>
@@ -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>
@@ -1150,5 +1150,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>
@@ -1783,5 +1783,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<string name="external_content">External Content</string>
<string name="add_folders">Add Folder</string>
<string name="percent">%1$d%%</string>
</resources>
+3 -2
View File
@@ -147,7 +147,8 @@ add_library(
zstd_compression.h
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
fs/symlink.h fs/symlink.cpp
httplib.h)
httplib.h
net/net.h net/net.cpp)
if(WIN32)
target_sources(common PRIVATE windows/timer_resolution.cpp
@@ -245,7 +246,7 @@ else()
target_link_libraries(common PUBLIC Boost::headers)
endif()
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib)
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json)
if (lz4_ADDED)
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
+16 -27
View File
@@ -13,13 +13,13 @@
namespace Common {
template <typename BaseAddr>
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
std::size_t first_level_bits_,
std::size_t page_bits_)
: address_space_bits{address_space_bits_},
first_level_bits{first_level_bits_}, page_bits{page_bits_} {
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_, std::size_t first_level_bits_, std::size_t page_bits_)
: address_space_bits{address_space_bits_}
, first_level_bits{first_level_bits_}
, page_bits{page_bits_}
{
if (page_bits == 0) {
return;
return;
}
first_level_shift = address_space_bits - first_level_bits;
first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
@@ -30,12 +30,9 @@ MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bit
void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
#else
void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
if (base == MAP_FAILED) {
if (base == MAP_FAILED)
base = nullptr;
}
#endif
ASSERT(base);
base_ptr = reinterpret_cast<BaseAddr*>(base);
}
@@ -56,29 +53,21 @@ template <typename BaseAddr>
void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
const u64 new_start = start >> first_level_shift;
const u64 new_end = (start + size) >> first_level_shift;
for (u64 i = new_start; i <= new_end; i++) {
if (!first_level_map[i]) {
for (u64 i = new_start; i <= new_end; i++)
if (!first_level_map[i])
AllocateLevel(i);
}
}
}
template <typename BaseAddr>
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 index) {
void* ptr = reinterpret_cast<char *>(base_ptr) + index * first_level_chunk_size;
#ifdef _WIN32
void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
#else
void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
if (base == MAP_FAILED) {
base = nullptr;
}
#endif
void* base = VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE);
ASSERT(base);
first_level_map[level] = base;
#else
void* base = ptr;
#endif
first_level_map[index] = base;
}
} // namespace Common
+287
View File
@@ -0,0 +1,287 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <optional>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <fmt/format.h>
#include "common/scm_rev.h"
#include "net.h"
#include "common/logging.h"
#include "common/httplib.h"
#ifdef YUZU_BUNDLED_OPENSSL
#include <openssl/cert.h>
#endif
#define QT_TR_NOOP(x) x
namespace Common::Net {
std::vector<Asset> Release::GetPlatformAssets() const {
// TODO(crueter): Need better handling for this as a whole.
#ifdef NIGHTLY_BUILD
std::vector<std::string> result;
boost::algorithm::split(result, tag, boost::is_any_of("."));
if (result.size() != 2)
return {};
const auto ref = result.at(1);
#else
const auto ref = tag;
#endif
std::vector<Asset> found_assets;
// FIXME: This is mildly inefficient.
// Finds assets based on a hierarchy of regex search strings.
const auto find_asset = [&found_assets, ref, this](const std::string& name,
const std::vector<std::string>& suffixes) {
for (const std::string& asset : assets) {
for (const auto& suffix : suffixes) {
if (asset.ends_with(suffix)) {
const std::string_view asset_sv = asset;
const size_t pos = asset_sv.find_last_of('/');
const std::string_view filename =
(pos != std::string_view::npos) ? asset_sv.substr(pos + 1) : asset_sv;
found_assets.emplace_back(Asset{
.name = name,
.url = host,
.path = asset,
.filename = std::string{filename},
});
return;
}
}
}
};
#ifdef _WIN32
#ifdef ARCHITECTURE_x86_64
find_asset("Standard", {"amd64-msvc-standard.exe", "amd64-msvc-standard.zip", "mingw-amd64-gcc-standard.exe", "mingw-amd64-gcc-standard.zip"});
find_asset("PGO", {"mingw-amd64-clang-pgo.exe", "mingw-amd64-clang-pgo.zip"});
#elif defined(ARCHITECTURE_arm64)
find_asset("Standard", {"mingw-arm64-clang-standard.exe", "mingw-arm64-clang-standard.zip"});
find_asset("PGO", {"mingw-arm64-clang-pgo.exe", "mingw-arm64-clang-pgo.zip"});
#endif
#elif defined(__APPLE__)
#ifdef ARCHITECTURE_arm64
find_asset("Standard", {".dmg", ".tar.gz"});
#endif
#elif defined(__ANDROID__)
#ifdef ARCHITECTURE_x86_64
find_asset("Standard", {"chromeos.apk"});
#elif defined(ARCHITECTURE_arm64)
#ifdef YUZU_LEGACY
find_asset("Standard", {"legacy.apk"});
#elif defined(GENSHIN_SPOOF)
find_asset("Standard", {"optimized.apk"});
#else
find_asset("Standard", {"standard.apk"});
#endif
#endif
#endif
return found_assets;
}
static inline u64 ParseIsoTimestamp(const std::string& iso) {
if (iso.empty())
return 0;
std::string buf = iso;
if (buf.back() == 'Z')
buf.pop_back();
std::tm tm{};
std::istringstream ss(buf);
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
if (ss.fail())
return 0;
#ifdef _WIN32
return static_cast<u64>(_mkgmtime(&tm));
#else
return static_cast<u64>(timegm(&tm));
#endif
}
std::optional<Release> Release::FromJson(const nlohmann::json& json, const std::string& host,
const std::string& repo) {
Release rel;
if (!json.is_object())
return std::nullopt;
rel.tag = json.value("tag_name", std::string{});
if (rel.tag.empty())
return std::nullopt;
rel.title = json.value("name", rel.tag);
rel.id = json.value("id", std::hash<std::string>{}(rel.title));
rel.published = ParseIsoTimestamp(json.value("published_at", std::string{}));
rel.prerelease = json.value("prerelease", false);
auto body = json.value("body", rel.title);
boost::replace_all(body, "\\r", "");
boost::replace_all(body, "\\n", "\n");
rel.body = body;
rel.host = host;
const auto release_base =
fmt::format("{}/{}/releases", Common::g_build_auto_update_website, repo);
const auto fallback_html = fmt::format("{}/tag/{}", release_base, rel.tag);
rel.html_url = json.value("html_url", fallback_html);
// This is our own "fake" API.
if (json.contains("base")) {
const auto base = json.value("base", fmt::format("https://{}", Common::g_build_auto_update_api));
rel.base_download_url = fmt::format("{}/{}", base, rel.tag);
// Assets are easy :)
rel.assets = json.value("assets", std::vector<std::string>{});
} else {
const auto base_download_url = fmt::format("/{}/releases/download/{}", repo, rel.tag);
rel.base_download_url = base_download_url;
// assets are a bit more complex here. :(
std::vector<std::string> assets;
const nlohmann::json& arr = json["assets"];
for (const auto &obj : arr) {
const auto url = obj.value("browser_download_url", std::string{});
assets.emplace_back(url);
}
rel.assets = assets;
}
return rel;
}
std::optional<Release> Release::FromJson(const std::string_view& json, const std::string& host,
const std::string& repo) {
try {
return FromJson(nlohmann::json::parse(json), host, repo);
} catch (std::exception& e) {
LOG_WARNING(Common, "Failed to parse JSON: {}", e.what());
}
return {};
}
std::vector<Release> Release::ListFromJson(const nlohmann::json& json, const std::string& host,
const std::string& repo) {
if (!json.is_array())
return {};
std::vector<Release> releases;
for (const auto& obj : json) {
auto rel = Release::FromJson(obj, host, repo);
if (rel)
releases.emplace_back(rel.value());
}
return releases;
}
std::vector<Release> Release::ListFromJson(const std::string_view& json, const std::string& host,
const std::string& repo) {
try {
return ListFromJson(nlohmann::json::parse(json), host, repo);
} catch (std::exception& e) {
LOG_WARNING(Common, "Failed to parse JSON: {}", e.what());
}
return {};
}
std::optional<std::string> MakeRequest(const std::string& url, const std::string& path) {
try {
constexpr std::size_t timeout_seconds = 15;
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(url);
client->set_connection_timeout(timeout_seconds);
client->set_read_timeout(timeout_seconds);
client->set_write_timeout(timeout_seconds);
#ifdef YUZU_BUNDLED_OPENSSL
client->load_ca_cert_store(kCert, sizeof(kCert));
#endif
if (client == nullptr) {
LOG_ERROR(Common, "Invalid URL {}{}", url, path);
return {};
}
httplib::Request request{
.method = "GET",
.path = path,
};
client->set_follow_location(true);
httplib::Result result = client->send(request);
if (!result) {
LOG_ERROR(Common, "GET to {}{} returned null", url, path);
return {};
}
const auto& response = result.value();
if (response.status >= 400) {
LOG_ERROR(Common, "GET to {}{} returned error status code: {}", url, path,
response.status);
return {};
}
if (!response.headers.contains("content-type")) {
LOG_ERROR(Common, "GET to {}{} returned no content", url, path);
return {};
}
return response.body;
} catch (std::exception& e) {
LOG_ERROR(Common, "GET to {}{} failed during update check: {}", url, path, e.what());
return std::nullopt;
}
}
std::vector<Release> GetReleases() {
const auto body = GetReleasesBody();
if (!body) {
LOG_WARNING(Common, "Failed to get stable releases");
return {};
}
const std::string_view body_str = body.value();
const auto url = fmt::format("https://{}", Common::g_build_auto_update_stable_api);
return Release::ListFromJson(body_str, url, Common::g_build_auto_update_stable_repo);
}
std::optional<Release> GetLatestRelease() {
const auto releases_path = Common::g_build_auto_update_api_path;
const auto url = fmt::format("https://{}", Common::g_build_auto_update_api);
const auto body = MakeRequest(url, releases_path);
if (!body) {
LOG_WARNING(Common, "Failed to get latest release");
return std::nullopt;
}
const std::string_view body_str = body.value();
return Release::FromJson(body_str, url, Common::g_build_auto_update_repo);
}
std::optional<std::string> GetReleasesBody() {
const auto releases_path =
fmt::format("/{}/{}/releases", Common::g_build_auto_update_stable_api_path,
Common::g_build_auto_update_stable_repo);
const auto url = fmt::format("https://{}", Common::g_build_auto_update_stable_api);
return MakeRequest(url, releases_path);
}
} // namespace Common::Net
+56
View File
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <optional>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "common/common_types.h"
namespace Common::Net {
typedef struct {
std::string name;
std::string url;
std::string path;
std::string filename;
} Asset;
typedef struct Release {
std::string title;
std::string body;
std::string tag;
std::string base_download_url;
std::string html_url;
std::string host;
std::vector<std::string> assets;
u64 id;
u64 published;
bool prerelease;
// Get the relevant list of assets for the current platform.
std::vector<Asset> GetPlatformAssets() const;
static std::optional<Release> FromJson(const nlohmann::json& json, const std::string &host, const std::string& repo);
static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo);
static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo);
static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo);
} Release;
// Make a request via httplib, and return the response body if applicable.
std::optional<std::string> MakeRequest(const std::string &url, const std::string &path);
// Get all of the latest stable releases.
std::vector<Release> GetReleases();
// Get all of the latest stable releases as text.
std::optional<std::string> GetReleasesBody();
// Get the latest release of the current channel.
std::optional<Release> GetLatestRelease();
}
+2 -4
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 2019 yuzu Emulator Project
@@ -142,10 +142,8 @@ struct PageTable {
VirtualBuffer<PageEntryData> entries;
static_assert(sizeof(PageEntryData) == 32);
std::size_t current_address_space_width_in_bits{};
u8* fastmem_arena{};
std::size_t current_address_space_width_in_bits{};
std::size_t page_size{};
};
+6
View File
@@ -22,6 +22,9 @@
#define BUILD_AUTO_UPDATE_API "@BUILD_AUTO_UPDATE_API@"
#define BUILD_AUTO_UPDATE_API_PATH "@BUILD_AUTO_UPDATE_API_PATH@"
#define BUILD_AUTO_UPDATE_REPO "@BUILD_AUTO_UPDATE_REPO@"
#define BUILD_AUTO_UPDATE_STABLE_API "@BUILD_AUTO_UPDATE_STABLE_API@"
#define BUILD_AUTO_UPDATE_STABLE_API_PATH "@BUILD_AUTO_UPDATE_STABLE_API_PATH@"
#define BUILD_AUTO_UPDATE_STABLE_REPO "@BUILD_AUTO_UPDATE_STABLE_REPO@"
#define IS_NIGHTLY_BUILD @IS_NIGHTLY_BUILD@
namespace Common {
@@ -45,5 +48,8 @@ constexpr const char g_build_auto_update_website[] = BUILD_AUTO_UPDATE_WEBSITE;
constexpr const char g_build_auto_update_api[] = BUILD_AUTO_UPDATE_API;
constexpr const char g_build_auto_update_api_path[] = BUILD_AUTO_UPDATE_API_PATH;
constexpr const char g_build_auto_update_repo[] = BUILD_AUTO_UPDATE_REPO;
constexpr const char g_build_auto_update_stable_api[] = BUILD_AUTO_UPDATE_STABLE_API;
constexpr const char g_build_auto_update_stable_api_path[] = BUILD_AUTO_UPDATE_STABLE_API_PATH;
constexpr const char g_build_auto_update_stable_repo[] = BUILD_AUTO_UPDATE_STABLE_REPO;
} // namespace Common
+3
View File
@@ -28,5 +28,8 @@ extern const char g_build_auto_update_website[];
extern const char g_build_auto_update_api[];
extern const char g_build_auto_update_api_path[];
extern const char g_build_auto_update_repo[];
extern const char g_build_auto_update_stable_api[];
extern const char g_build_auto_update_stable_api_path[];
extern const char g_build_auto_update_stable_repo[];
} // namespace Common
+7 -1
View File
@@ -600,7 +600,13 @@ struct Values {
Category::RendererExtensions,
Specialization::Scalar};
SwitchableSetting<bool> vertex_input_dynamic_state{linkage, false, "vertex_input_dynamic_state", Category::RendererExtensions};
SwitchableSetting<bool> vertex_input_dynamic_state{linkage,
#if defined (ANDROID)
false,
#else
true,
#endif
"vertex_input_dynamic_state", Category::RendererExtensions};
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
-11
View File
@@ -130,17 +130,6 @@ public:
ResetStorageBit(id.index);
}
[[nodiscard]] bool Contains(SlotId id) const noexcept {
if (!id) {
return false;
}
const size_t word = id.index / 64;
if (word >= stored_bitset.size()) {
return false;
}
return ((stored_bitset[word] >> (id.index % 64)) & 1) != 0;
}
[[nodiscard]] Iterator begin() noexcept {
const auto it = std::ranges::find_if(stored_bitset, [](u64 value) { return value != 0; });
if (it == stored_bitset.end()) {
+18 -17
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -21,7 +24,9 @@ public:
// "with the current allocator");
constexpr VirtualBuffer() = default;
explicit VirtualBuffer(std::size_t count) : alloc_size{count * sizeof(T)} {
explicit VirtualBuffer(std::size_t count) noexcept
: alloc_size{count * sizeof(T)}
{
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
}
@@ -33,8 +38,8 @@ public:
VirtualBuffer& operator=(const VirtualBuffer&) = delete;
VirtualBuffer(VirtualBuffer&& other) noexcept
: alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr),
nullptr} {}
: alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr), nullptr}
{}
VirtualBuffer& operator=(VirtualBuffer&& other) noexcept {
alloc_size = std::exchange(other.alloc_size, 0);
@@ -42,35 +47,31 @@ public:
return *this;
}
void resize(std::size_t count) {
const auto new_size = count * sizeof(T);
if (new_size == alloc_size) {
return;
void resize(std::size_t count) noexcept {
if (auto const new_size = count * sizeof(T); new_size != alloc_size) {
FreeMemoryPages(base_ptr, alloc_size);
alloc_size = new_size;
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
}
FreeMemoryPages(base_ptr, alloc_size);
alloc_size = new_size;
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
}
[[nodiscard]] constexpr const T& operator[](std::size_t index) const {
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept {
return base_ptr[index];
}
[[nodiscard]] constexpr T& operator[](std::size_t index) {
[[nodiscard]] constexpr T& operator[](std::size_t index) noexcept {
return base_ptr[index];
}
[[nodiscard]] constexpr T* data() {
[[nodiscard]] constexpr T* data() noexcept {
return base_ptr;
}
[[nodiscard]] constexpr const T* data() const {
[[nodiscard]] constexpr const T* data() const noexcept {
return base_ptr;
}
[[nodiscard]] constexpr std::size_t size() const {
[[nodiscard]] constexpr std::size_t size() const noexcept {
return alloc_size / sizeof(T);
}
+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 2022 yuzu Emulator Project
@@ -283,7 +283,7 @@ std::string_view GDBStubA32::GetTargetXML() const {
<reg name="r11" bitsize="32" type="uint32"/>
<reg name="r12" bitsize="32" type="uint32"/>
<reg name="sp" bitsize="32" type="data_ptr"/>
<reg name="lr" bitsize="32" type="code_ptr"/>
<reg name="lr" bitsize="32"/>
<reg name="pc" bitsize="32" type="code_ptr"/>
<!-- The CPSR is register 25, rather than register 16, because
the FPA registers historically were placed between the PC
@@ -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));
+16 -32
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
@@ -191,8 +191,7 @@ Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start
m_cached_physical_heap_region = nullptr;
// Initialize our implementation.
m_impl = std::make_unique<Common::PageTable>();
m_impl->Resize(m_address_space_width, PageBits);
m_impl.Resize(m_address_space_width, PageBits);
// Set the tracking memory.
m_memory = std::addressof(memory);
@@ -202,13 +201,7 @@ Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start
m_memory_block_slab_manager));
}
Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr,
bool enable_das_merge, bool from_back,
KMemoryManager::Pool pool, KProcessAddress code_address,
size_t code_size, KSystemResource* system_resource,
KResourceLimit* resource_limit,
Core::Memory::Memory& memory,
KProcessAddress aslr_space_start) {
Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr, bool enable_das_merge, bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address, size_t code_size, KSystemResource* system_resource, KResourceLimit* resource_limit, Core::Memory::Memory& memory, KProcessAddress aslr_space_start) {
// Calculate region extents.
const size_t as_width = GetAddressSpaceWidth(as_type);
const KProcessAddress start = 0;
@@ -319,14 +312,10 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
// Determine random placements for each region.
size_t alias_rnd = 0, heap_rnd = 0, stack_rnd = 0, kmap_rnd = 0;
if (enable_aslr) {
alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
RegionAlignment;
heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
RegionAlignment;
stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
RegionAlignment;
kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
RegionAlignment;
alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
}
// Setup heap and alias regions.
@@ -445,15 +434,13 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
ASSERT(heap_last < kmap_start || kmap_last < heap_start);
// Initialize our implementation.
m_impl = std::make_unique<Common::PageTable>();
m_impl->Resize(m_address_space_width, PageBits);
m_impl.Resize(m_address_space_width, PageBits);
// Set the tracking memory.
m_memory = std::addressof(memory);
// Initialize our memory block manager.
R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end,
m_memory_block_slab_manager));
R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, m_memory_block_slab_manager));
}
Result KPageTableBase::FinalizeProcess() {
@@ -476,7 +463,7 @@ void KPageTableBase::Finalize() {
this->FinalizeProcess();
auto BlockCallback = [&](KProcessAddress addr, u64 size) {
if (m_impl->fastmem_arena) {
if (m_impl.fastmem_arena) {
m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
}
@@ -514,9 +501,6 @@ void KPageTableBase::Finalize() {
m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
m_mapped_ipc_server_memory);
}
// Close the backing page table, as the destructor is not called for guest objects.
m_impl.reset();
}
KProcessAddress KPageTableBase::GetRegionAddress(Svc::MemoryState state) const {
@@ -2349,7 +2333,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
TraversalContext context;
TraversalEntry next_entry;
bool traverse_valid =
m_impl->BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr);
m_impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr);
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
// Set tracking variables.
@@ -2360,7 +2344,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
while (true) {
// Continue the traversal.
traverse_valid =
m_impl->ContinueTraversal(std::addressof(next_entry), std::addressof(context));
m_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
if (!traverse_valid) {
break;
}
@@ -5730,14 +5714,14 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
this->MakePageGroup(pages_to_close, virt_addr, num_pages);
// Unmap.
m_memory->UnmapRegion(*m_impl, virt_addr, num_pages * PageSize, separate_heap);
m_memory->UnmapRegion(m_impl, virt_addr, num_pages * PageSize, separate_heap);
R_SUCCEED();
}
case OperationType::Map: {
ASSERT(virt_addr != 0);
ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr,
m_memory->MapMemoryRegion(m_impl, virt_addr, num_pages * PageSize, phys_addr,
ConvertToMemoryPermission(properties.perm), false);
// Open references to pages, if we should.
@@ -5754,7 +5738,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
case OperationType::ChangePermissions:
case OperationType::ChangePermissionsAndRefresh:
case OperationType::ChangePermissionsAndRefreshAndFlush: {
m_memory->ProtectRegion(*m_impl, virt_addr, num_pages * PageSize,
m_memory->ProtectRegion(m_impl, virt_addr, num_pages * PageSize,
ConvertToMemoryPermission(properties.perm));
R_SUCCEED();
}
@@ -5788,7 +5772,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
const size_t size{node.GetNumPages() * PageSize};
// Map the pages.
m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress(),
m_memory->MapMemoryRegion(m_impl, virt_addr, size, node.GetAddress(),
ConvertToMemoryPermission(properties.perm), separate_heap);
virt_addr += size;
+7 -22
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
@@ -215,7 +215,7 @@ private:
mutable KLightLock m_general_lock;
mutable KLightLock m_map_physical_memory_lock;
KLightLock m_device_map_lock;
std::unique_ptr<Common::PageTable> m_impl{};
Common::PageTable m_impl{};
Core::Memory::Memory* m_memory{};
KMemoryBlockManager m_memory_block_manager{};
u32 m_allocate_option{};
@@ -300,26 +300,11 @@ public:
}
public:
Core::Memory::Memory& GetMemory() {
return *m_memory;
}
Core::Memory::Memory& GetMemory() const {
return *m_memory;
}
Common::PageTable& GetImpl() {
return *m_impl;
}
Common::PageTable& GetImpl() const {
return *m_impl;
}
size_t GetNumGuardPages() const {
return this->IsKernel() ? 1 : 4;
}
[[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return *m_memory; }
[[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return *m_memory; }
[[nodiscard]] Common::PageTable& GetImpl() noexcept { return m_impl; }
[[nodiscard]] Common::PageTable const& GetImpl() const noexcept { return m_impl; }
[[nodiscard]] size_t GetNumGuardPages() const noexcept { return this->IsKernel() ? 1 : 4; }
protected:
// NOTE: These three functions (Operate, Operate, FinalizeUpdate) are virtual functions
// in Nintendo's kernel. We devirtualize them, since KPageTable is the only derived
+5 -16
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
@@ -33,21 +33,10 @@ public:
m_page_table.Finalize();
}
Core::Memory::Memory& GetMemory() {
return m_page_table.GetMemory();
}
Core::Memory::Memory& GetMemory() const {
return m_page_table.GetMemory();
}
Common::PageTable& GetImpl() {
return m_page_table.GetImpl();
}
Common::PageTable& GetImpl() const {
return m_page_table.GetImpl();
}
[[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(); }
[[nodiscard]] Common::PageTable const& GetImpl() const noexcept { return m_page_table.GetImpl(); }
size_t GetNumGuardPages() const {
return m_page_table.GetNumGuardPages();
+23 -5
View File
@@ -50,6 +50,14 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
};
const auto ExitContext = [&]() {
// Save the JIT context back to the thread so the debugger can
// read the current register state. Debug halt paths (step,
// breakpoint, watchpoint) return from RunThread without going
// through the scheduler's Unload/SaveContext.
if (system.DebuggerEnabled()) {
interface->GetContext(thread->GetContext());
}
// Unlock the thread.
interface->UnlockThread(thread);
@@ -97,11 +105,16 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
}
// Determine why we stopped.
const bool supervisor_call = True(hr & Core::HaltReason::SupervisorCall);
const bool prefetch_abort = True(hr & Core::HaltReason::PrefetchAbort);
const bool breakpoint = True(hr & Core::HaltReason::InstructionBreakpoint);
const bool data_abort = True(hr & Core::HaltReason::DataAbort);
const bool interrupt = True(hr & Core::HaltReason::BreakLoop);
// If a step completed successfully, skip other halt reason handlers —
// the step takes priority (e.g. step may also set InstructionBreakpoint
// if the next instruction happens to be a breakpoint).
const bool step_completed = True(hr & Core::HaltReason::StepThread)
&& thread->GetStepState() == StepState::StepPerformed;
const bool supervisor_call = !step_completed && True(hr & Core::HaltReason::SupervisorCall);
const bool prefetch_abort = !step_completed && True(hr & Core::HaltReason::PrefetchAbort);
const bool breakpoint = !step_completed && True(hr & Core::HaltReason::InstructionBreakpoint);
const bool data_abort = !step_completed && True(hr & Core::HaltReason::DataAbort);
const bool interrupt = !step_completed && True(hr & Core::HaltReason::BreakLoop);
// Since scheduling may occur here, we cannot use any cached
// state after returning from calls we make.
@@ -111,6 +124,11 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
if (breakpoint || prefetch_abort) {
if (breakpoint) {
interface->RewindBreakpointInstruction();
// RewindBreakpointInstruction sets the JIT state to the
// saved breakpoint context. Update the thread context to
// match, since ExitContext already saved the post-execution
// state.
interface->GetContext(thread->GetContext());
}
if (system.DebuggerEnabled()) {
system.GetDebugger().NotifyThreadStopped(thread);
+31 -128
View File
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "common/net/net.h"
#include "common/scm_rev.h"
#include "core/hle/service/bcat/news/builtin_news.h"
#include "core/hle/service/bcat/news/msgpack.h"
#include "core/hle/service/bcat/news/news_storage.h"
@@ -22,10 +24,8 @@
#include <filesystem>
#include <fstream>
#include <future>
#include <iomanip>
#include <mutex>
#include <optional>
#include <sstream>
#include <thread>
#ifdef YUZU_BUNDLED_OPENSSL
@@ -35,9 +35,6 @@
namespace Service::News {
namespace {
// TODO(crueter): COMPILE DEFINITION
constexpr const char* GitHubAPI_EdenReleases = "/api/v1/repos/eden-emu/eden/releases";
// Cached logo data
std::vector<u8> default_logo_small;
std::vector<u8> default_logo_large;
@@ -66,24 +63,6 @@ u32 HashToNewsId(std::string_view key) {
return static_cast<u32>(std::hash<std::string_view>{}(key) & 0x7FFFFFFF);
}
u64 ParseIsoTimestamp(const std::string& iso) {
if (iso.empty()) return 0;
std::string buf = iso;
if (buf.back() == 'Z') buf.pop_back();
std::tm tm{};
std::istringstream ss(buf);
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
if (ss.fail()) return 0;
#ifdef _WIN32
return static_cast<u64>(_mkgmtime(&tm));
#else
return static_cast<u64>(timegm(&tm));
#endif
}
std::vector<u8> TryLoadFromDisk(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) return {};
@@ -100,8 +79,9 @@ std::vector<u8> TryLoadFromDisk(const std::filesystem::path& path) {
return data;
}
// TODO(crueter): Migrate to use Common::Net
std::vector<u8> DownloadImage(const std::string& url_path, const std::filesystem::path& cache_path) {
LOG_INFO(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path);
LOG_DEBUG(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path);
try {
httplib::Client cli("https://eden-emu.dev");
cli.set_follow_location(true);
@@ -226,67 +206,6 @@ void WriteCachedJson(std::string_view json) {
(void)Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, json);
}
std::optional<std::string> DownloadReleasesJson() {
try {
#ifdef YUZU_BUNDLED_OPENSSL
const auto url = "https://git.eden-emu.dev";
#else
const auto url = "git.eden-emu.dev";
#endif
// TODO(crueter): This is duplicated between frontend and here.
constexpr auto path = GitHubAPI_EdenReleases;
constexpr std::size_t timeout_seconds = 15;
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(url);
client->set_connection_timeout(timeout_seconds);
client->set_read_timeout(timeout_seconds);
client->set_write_timeout(timeout_seconds);
#ifdef YUZU_BUNDLED_OPENSSL
client->load_ca_cert_store(kCert, sizeof(kCert));
#endif
if (client == nullptr) {
LOG_ERROR(Service_BCAT, "Invalid URL {}{}", url, path);
return {};
}
httplib::Request request{
.method = "GET",
.path = path,
};
client->set_follow_location(true);
httplib::Result result = client->send(request);
if (!result) {
LOG_ERROR(Service_BCAT, "GET to {}{} returned null", url, path);
return {};
} else if (result->status < 400) {
return result->body;
}
if (result->status >= 400) {
LOG_ERROR(Service_BCAT,
"GET to {}{} returned error status code: {}",
url,
path,
result->status);
return {};
}
if (!result->headers.contains("content-type")) {
LOG_ERROR(Service_BCAT, "GET to {}{} returned no content", url, path);
return {};
}
} catch (...) {
LOG_WARNING(Service_BCAT, " failed to download releases");
}
return std::nullopt;
}
// idk but News App does not render Markdown or HTML, so remove some formatting.
std::string SanitizeMarkdown(std::string_view markdown) {
std::string result;
@@ -342,9 +261,7 @@ std::string SanitizeMarkdown(std::string_view markdown) {
return text;
}
std::string FormatBody(const nlohmann::json& release, std::string_view title) {
std::string body = release.value("body", std::string{});
std::string FormatBody(std::string body, const std::string_view &title) {
if (body.empty()) {
return std::string(title);
}
@@ -375,52 +292,32 @@ std::string FormatBody(const nlohmann::json& release, std::string_view title) {
return body;
}
void ImportReleases(std::string_view json_text) {
nlohmann::json root;
try {
root = nlohmann::json::parse(json_text);
} catch (...) {
LOG_WARNING(Service_BCAT, "failed to parse JSON");
return;
}
if (!root.is_array()) return;
void ImportReleases(const std::vector<Common::Net::Release> &releases) {
std::vector<u32> news_ids;
for (const auto& rel : root) {
if (!rel.is_object()) continue;
std::string title = rel.value("name", rel.value("tag_name", std::string{}));
if (title.empty()) continue;
const u64 release_id = rel.value("id", 0);
const u32 news_id = release_id ? static_cast<u32>(release_id & 0x7FFFFFFF) : HashToNewsId(title);
for (const auto& rel : releases) {
const u32 news_id = u32(rel.id & 0x7FFFFFFF);
news_ids.push_back(news_id);
}
PreloadNewsImages(news_ids);
for (const auto& rel : root) {
if (!rel.is_object()) continue;
for (const auto& rel : releases) {
const std::string title = rel.title;
const std::string body = rel.body;
const std::string html_url = rel.html_url;
std::string title = rel.value("name", rel.value("tag_name", std::string{}));
if (title.empty()) continue;
const u64 release_id = rel.value("id", 0);
const u32 news_id = release_id ? static_cast<u32>(release_id & 0x7FFFFFFF) : HashToNewsId(title);
const u64 published = ParseIsoTimestamp(rel.value("published_at", std::string{}));
const u32 news_id = u32(rel.id & 0x7FFFFFFF);
const u64 published = rel.published;
const u64 pickup_limit = published + 600000000;
const u32 priority = rel.value("prerelease", false) ? 1500 : 2500;
const u32 priority = rel.prerelease ? 1500 : 2500;
std::string author = "eden";
if (rel.contains("author") && rel["author"].is_object()) {
author = rel["author"].value("login", "eden");
}
std::string author = "Eden";
auto payload = BuildMsgpack(title, FormatBody(rel, title), title, published,
auto payload = BuildMsgpack(title, FormatBody(body, title), title, published,
pickup_limit, priority, {"en"}, author, {},
rel.value("html_url", std::string{}), news_id);
html_url, news_id);
const std::string news_id_str = fmt::format("LA{:020}", news_id);
const std::string news_id_str = fmt::format("LA{:020}", rel.id);
GithubNewsMeta meta{
.news_id = news_id_str,
@@ -565,15 +462,21 @@ void EnsureBuiltinNewsLoaded() {
LoadDefaultLogos();
if (const auto cached = ReadCachedJson()) {
ImportReleases(*cached);
LOG_DEBUG(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size());
const std::string_view body = cached.value();
const auto releases = Common::Net::Release::ListFromJson(body, Common::g_build_auto_update_stable_api, Common::g_build_auto_update_stable_repo);
ImportReleases(releases);
LOG_INFO(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size());
}
std::thread([] {
if (const auto fresh = DownloadReleasesJson()) {
WriteCachedJson(*fresh);
ImportReleases(*fresh);
LOG_DEBUG(Service_BCAT, "news: {} entries updated from Forgejo", NewsStorage::Instance().ListAll().size());
if (const auto fresh = Common::Net::GetReleasesBody()) {
const std::string_view body = fresh.value();
WriteCachedJson(body);
const auto releases = Common::Net::Release::ListFromJson(body, Common::g_build_auto_update_stable_api, Common::g_build_auto_update_stable_repo);
ImportReleases(releases);
LOG_INFO(Service_BCAT, "news: {} entries updated from Forgejo", NewsStorage::Instance().ListAll().size());
}
}).detach();
});
+15 -56
View File
@@ -6,7 +6,6 @@
#include <algorithm>
#include <array>
#include <cstring>
#include <sstream>
#include <boost/range/algorithm_ext/erase.hpp>
@@ -191,7 +190,7 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
buffer_w_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
}
buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
const auto buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
if (!command_header->IsTipc()) {
// Padding to align to 16 bytes
@@ -298,15 +297,7 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer() {
}
// Write the domain objects to the command buffer, these go after the raw untranslated data.
if (buffer_c_offset != 0 && !buffer_c_descriptors.empty()) {
constexpr u32 WORDS_PER_DESCRIPTOR = sizeof(IPC::BufferDescriptorC) / sizeof(u32);
u32 descriptor_offset = buffer_c_offset;
for (const auto& descriptor : buffer_c_descriptors) {
std::memcpy(&cmd_buf[descriptor_offset], &descriptor, sizeof(descriptor));
descriptor_offset += WORDS_PER_DESCRIPTOR;
}
}
// TODO(Subv): This completely ignores C buffers.
if (GetManager()->IsDomain()) {
current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size());
@@ -405,14 +396,10 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
BufferDescriptorB()[buffer_index].Size()};
const std::size_t buffer_size{GetWriteBufferSize(buffer_index)};
if (buffer_size == 0) {
LOG_WARNING(Core, "WriteBuffer target index {} has zero capacity", buffer_index);
return 0;
}
if (size > buffer_size) {
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
size, buffer_size);
size = buffer_size;
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
buffer_size);
size = buffer_size; // TODO(bunnei): This needs to be HW tested
}
if (is_buffer_b) {
@@ -434,25 +421,15 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size,
std::size_t buffer_index) const {
if (buffer_index >= BufferDescriptorB().size()) {
LOG_WARNING(Core, "WriteBufferB invalid buffer index {}", buffer_index);
return 0;
}
if (size == 0) {
LOG_WARNING(Core, "skip empty buffer write (B)");
if (buffer_index >= BufferDescriptorB().size() || size == 0) {
return 0;
}
const auto buffer_size{BufferDescriptorB()[buffer_index].Size()};
if (buffer_size == 0) {
LOG_WARNING(Core, "WriteBufferB target index {} has zero capacity", buffer_index);
return 0;
}
if (size > buffer_size) {
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
size, buffer_size);
size = buffer_size;
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
buffer_size);
size = buffer_size; // TODO(bunnei): This needs to be HW tested
}
memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
@@ -461,25 +438,15 @@ std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size
std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size,
std::size_t buffer_index) const {
if (buffer_index >= BufferDescriptorC().size()) {
LOG_WARNING(Core, "WriteBufferC invalid buffer index {}", buffer_index);
return 0;
}
if (size == 0) {
LOG_WARNING(Core, "skip empty buffer write (C)");
if (buffer_index >= BufferDescriptorC().size() || size == 0) {
return 0;
}
const auto buffer_size{BufferDescriptorC()[buffer_index].Size()};
if (buffer_size == 0) {
LOG_WARNING(Core, "WriteBufferC target index {} has zero capacity", buffer_index);
return 0;
}
if (size > buffer_size) {
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
size, buffer_size);
size = buffer_size;
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
buffer_size);
size = buffer_size; // TODO(bunnei): This needs to be HW tested
}
memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
@@ -509,20 +476,12 @@ std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) cons
ASSERT_OR_EXECUTE_MSG(
BufferDescriptorB().size() > buffer_index, { return 0; },
"BufferDescriptorB invalid buffer_index {}", buffer_index);
const auto size = BufferDescriptorB()[buffer_index].Size();
if (size == 0) {
LOG_WARNING(Core, "BufferDescriptorB index {} has zero size", buffer_index);
}
return size;
return BufferDescriptorB()[buffer_index].Size();
} else {
ASSERT_OR_EXECUTE_MSG(
BufferDescriptorC().size() > buffer_index, { return 0; },
"BufferDescriptorC invalid buffer_index {}", buffer_index);
const auto size = BufferDescriptorC()[buffer_index].Size();
if (size == 0) {
LOG_WARNING(Core, "BufferDescriptorC index {} has zero size", buffer_index);
}
return size;
return BufferDescriptorC()[buffer_index].Size();
}
return 0;
}
-4
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@@ -425,7 +422,6 @@ private:
u32 data_payload_offset{};
u32 handles_offset{};
u32 domain_offset{};
u32 buffer_c_offset{};
std::weak_ptr<SessionRequestManager> manager{};
bool is_deferred{false};
+3 -7
View File
@@ -89,7 +89,7 @@ void NvMap::UnmapHandle(Handle& handle_description) {
// Free and unmap the handle from Host1x GMMU
if (handle_description.pin_virt_address) {
host1x.GMMU().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
host1x.gmmu_manager.Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
handle_description.aligned_size);
host1x.Allocator().Free(handle_description.pin_virt_address,
static_cast<u32>(handle_description.aligned_size));
@@ -169,12 +169,8 @@ DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
std::scoped_lock lock(handle_description->mutex);
const auto map_low_area = [&] {
if (handle_description->pin_virt_address == 0) {
auto& gmmu_allocator = host1x.Allocator();
auto& gmmu = host1x.GMMU();
u32 address =
gmmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size));
gmmu.Map(static_cast<GPUVAddr>(address), handle_description->d_address,
handle_description->aligned_size);
u32 address = host1x.Allocator().Allocate(u32(handle_description->aligned_size));
host1x.gmmu_manager.Map(GPUVAddr(address), handle_description->d_address, handle_description->aligned_size);
handle_description->pin_virt_address = address;
}
};
@@ -10,6 +10,7 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging.h"
#include "core/core.h"
#include "core/hle/service/nvdrv/core/container.h"
@@ -130,12 +131,12 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
vm.small_page_allocator.emplace(start_pages, end_pages);
const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
const auto end_big_pages{
static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
vm.big_page_allocator.emplace(start_big_pages, end_big_pages);
gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split,
vm.big_page_size_bits, VM::PAGE_SIZE_BITS);
@@ -188,8 +189,8 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
}
allocation_map[params.offset] = {
.size = size,
.mappings{},
.size = size,
.page_size = params.page_size,
.sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
.big_pages = params.page_size != VM::YUZU_PAGESIZE,
@@ -199,71 +200,52 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
}
void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
auto mapping{mapping_map.at(offset)};
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(static_cast<u32>(mapping->offset >> page_size_bits),
static_cast<u32>(aligned_size >> page_size_bits));
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));
}
nvmap.UnpinHandle(mapping->handle);
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);
if (mapping.sparse_alloc) {
gmmu->MapSparse(offset, mapping.size, mapping.big_page);
} else {
gmmu->Unmap(offset, mapping->size);
gmmu->Unmap(offset, mapping.size);
}
mapping_map.erase(offset);
mapping_map.erase(it);
}
NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) {
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
params.pages, params.page_size);
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset, params.pages, params.page_size);
std::scoped_lock lock(mutex);
if (!vm.initialised) {
return NvResult::BadValue;
}
try {
auto allocation{allocation_map[params.offset]};
if (allocation.page_size != params.page_size ||
allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
if (auto const it = allocation_map.find(params.offset); it != allocation_map.end()) {
auto const allocation = it->second;
if (allocation.page_size != params.page_size || allocation.size != (u64(params.pages) * params.page_size))
return NvResult::BadValue;
}
for (const auto& mapping : allocation.mappings) {
FreeMappingLocked(mapping->offset);
}
for (const auto mapping_offset : allocation.mappings)
FreeMappingLocked(mapping_offset);
// Unset sparse flag if required
if (allocation.sparse) {
if (allocation.sparse)
gmmu->Unmap(params.offset, allocation.size);
}
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
: *vm.big_page_allocator};
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
: vm.big_page_size_bits};
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator : *vm.big_page_allocator};
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS : vm.big_page_size_bits};
allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
static_cast<u32>(allocation.size >> page_size_bits));
allocator.Free(u32(params.offset >> page_size_bits), u32(allocation.size >> page_size_bits));
allocation_map.erase(params.offset);
} catch (const std::out_of_range&) {
return NvResult::BadValue;
return NvResult::Success;
}
return NvResult::Success;
return NvResult::BadValue;
}
NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) {
@@ -327,26 +309,18 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
// Remaps a subregion of an existing mapping to a different PA
if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
try {
auto mapping{mapping_map.at(params.offset)};
if (mapping->size < params.mapping_size) {
LOG_WARNING(Service_NVDRV,
"Cannot remap a partially mapped GPU address space region: {:#X}",
params.offset);
if (auto const it = mapping_map.find(params.offset); it != mapping_map.end()) {
auto const mapping = it->second;
if (mapping.size < params.mapping_size) {
LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#X}", params.offset);
return NvResult::BadValue;
}
u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
VAddr device_address{mapping->ptr + params.buffer_offset};
gmmu->Map(gpu_address, device_address, params.mapping_size,
static_cast<Tegra::PTEKind>(params.kind), mapping->big_page);
u64 gpu_address = u64(params.offset + params.buffer_offset);
VAddr device_address{mapping.ptr + params.buffer_offset};
gmmu->Map(gpu_address, device_address, params.mapping_size, Tegra::PTEKind(params.kind), mapping.big_page);
return NvResult::Success;
} catch (const std::out_of_range&) {
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}",
params.offset);
} else {
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}", params.offset);
return NvResult::BadValue;
}
}
@@ -356,8 +330,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
return NvResult::BadValue;
}
DAddr device_address{
static_cast<DAddr>(nvmap.PinHandle(params.handle, false) + params.buffer_offset)};
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset);
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
bool big_page{[&]() {
@@ -381,32 +354,22 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
}
const bool use_big_pages = alloc->second.big_pages && big_page;
gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind),
use_big_pages);
gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind), use_big_pages);
auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
true, use_big_pages, alloc->second.sparse)};
alloc->second.mappings.push_back(mapping);
mapping_map[params.offset] = mapping;
alloc->second.mappings.push_back(params.offset);
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, true, use_big_pages, alloc->second.sparse));
} else {
auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
params.offset = static_cast<u64>(allocator.Allocate(
static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
<< page_size_bits;
params.offset = u64(allocator.Allocate(u32(Common::AlignUp(size, page_size) >> page_size_bits))) << page_size_bits;
if (!params.offset) {
ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
return NvResult::InsufficientMemory;
}
gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size),
static_cast<Tegra::PTEKind>(params.kind), big_page);
auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
false, big_page, false)};
mapping_map[params.offset] = mapping;
gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size), Tegra::PTEKind(params.kind), big_page);
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false));
}
map_buffer_offsets.insert(params.offset);
@@ -415,37 +378,32 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
}
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
if (map_buffer_offsets.find(params.offset) != map_buffer_offsets.end()) {
std::scoped_lock lock(mutex);
if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) {
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
std::scoped_lock lock(mutex);
if (!vm.initialised) {
return NvResult::BadValue;
}
auto mapping{mapping_map.at(params.offset)};
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};
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
static_cast<u32>(mapping->size >> page_size_bits));
auto const it = mapping_map.find(params.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};
allocator.Free(u32(mapping.offset >> page_size_bits), u32(mapping.size >> page_size_bits));
}
// 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(params.offset, mapping->size, mapping->big_page);
if (mapping.sparse_alloc) {
gmmu->MapSparse(params.offset, mapping.size, mapping.big_page);
} else {
gmmu->Unmap(params.offset, mapping->size);
gmmu->Unmap(params.offset, mapping.size);
}
nvmap.UnpinHandle(mapping->handle);
mapping_map.erase(params.offset);
map_buffer_offsets.erase(params.offset);
nvmap.UnpinHandle(mapping.handle);
mapping_map.erase(it);
map_buffer_offsets.erase(offset_it);
}
return NvResult::Success;
}
@@ -478,8 +436,7 @@ void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
}
NvResult nvhost_as_gpu::GetVARegions1(IoctlGetVaRegions& params) {
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
params.buf_size);
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr, params.buf_size);
std::scoped_lock lock(mutex);
@@ -165,35 +165,36 @@ private:
NvCore::NvMap& nvmap;
struct Mapping {
NvCore::NvMap::Handle::Id handle;
DAddr ptr;
u64 offset;
u64 size;
bool fixed;
bool big_page; // Only valid if fixed == false
bool sparse_alloc;
NvCore::NvMap::Handle::Id handle;
bool fixed : 1;
bool big_page : 1; // Only valid if fixed == false
bool sparse_alloc : 1;
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_,
bool big_page_, bool sparse_alloc_)
: handle(handle_), ptr(ptr_), offset(offset_), size(size_), fixed(fixed_),
big_page(big_page_), sparse_alloc(sparse_alloc_) {}
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
: ptr(ptr_), offset(offset_), size(size_), handle(handle_)
, fixed(fixed_), big_page(big_page_), sparse_alloc(sparse_alloc_)
{}
};
struct Allocation {
std::vector<u64> mappings;
u64 size;
std::list<std::shared_ptr<Mapping>> mappings;
u32 page_size;
bool sparse;
bool big_pages;
};
std::map<u64, std::shared_ptr<Mapping>>
mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
//!< mapping type, this is needed as what was originally a single buffer may
//!< have been split into multiple GPU side buffers with the remap flag.
std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
//!< which fixed buffers can be mapped into
std::mutex mutex; //!< Locks all AS operations
//!< This maps the base addresses of mapped buffers to their total sizes and
//!< mapping type, this is needed as what was originally a single buffer may
//!< have been split into multiple GPU side buffers with the remap flag.
std::map<u64, Mapping> mapping_map;
//!< Holds allocations created by AllocSpace from
//!< which fixed buffers can be mapped into
std::map<u64, Allocation> allocation_map;
std::mutex mutex; //!< Locks all AS operations
struct VM {
static constexpr u32 YUZU_PAGESIZE{0x1000};
@@ -213,9 +214,8 @@ private:
using Allocator = Common::FlatAllocator<u32, 0, 32>;
std::unique_ptr<Allocator> big_page_allocator;
std::shared_ptr<Allocator>
small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
std::optional<Allocator> big_page_allocator;
std::optional<Allocator> small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
bool initialised{};
} vm;
@@ -228,9 +228,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
code_size += patch_ctx.GetTotalPatchSize();
// TODO: this is bad form of ASLR, it sucks
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
// Setup the process code layout
if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) {
+2 -3
View File
@@ -89,9 +89,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
codeset.DataSegment().size += kip->GetBSSSize();
// TODO: this is bad form of ASLR, it sucks
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
// Setup the process code layout
if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) {
+2 -3
View File
@@ -242,9 +242,8 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
}();
// TODO: this is bad form of ASLR, it sucks
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
// Setup the process code layout
if (process
@@ -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<>
@@ -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
/* This file is part of the dynarmic project.
@@ -39,7 +39,7 @@ bool TranslatorVisitor::BL(Imm<26> imm26) {
ir.PushRSB(ir.current_location->AdvancePC(4));
const u64 target = ir.PC() + offset;
ir.SetTerm(IR::Term::LinkBlock{ir.current_location->SetPC(target)});
ir.SetTerm(IR::Term::LinkBlockFast{ir.current_location->SetPC(target)});
return false;
}
File diff suppressed because one or more lines are too long
+23 -124
View File
@@ -5,146 +5,45 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#ifdef NIGHTLY_BUILD
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#endif
#include <fmt/format.h>
#include "common/logging.h"
#include "common/net/net.h"
#include "common/scm_rev.h"
#include "update_checker.h"
#include "common/httplib.h"
#include "common/logging.h"
#ifdef YUZU_BUNDLED_OPENSSL
#include <openssl/cert.h>
#endif
std::optional<Common::Net::Release> UpdateChecker::GetUpdate() {
const auto latest = Common::Net::GetLatestRelease();
if (!latest) return std::nullopt;
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
LOG_INFO(Frontend, "Received update {}", latest->title);
std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::string path)
{
try {
constexpr std::size_t timeout_seconds = 15;
#ifdef NIGHTLY_BUILD
std::vector<std::string> result;
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(url);
client->set_connection_timeout(timeout_seconds);
client->set_read_timeout(timeout_seconds);
client->set_write_timeout(timeout_seconds);
#ifdef YUZU_BUNDLED_OPENSSL
client->load_ca_cert_store(kCert, sizeof(kCert));
#endif
if (client == nullptr) {
LOG_ERROR(Frontend, "Invalid URL {}{}", url, path);
return {};
}
httplib::Request request{
.method = "GET",
.path = path,
};
client->set_follow_location(true);
httplib::Result result = client->send(request);
if (!result) {
LOG_ERROR(Frontend, "GET to {}{} returned null", url, path);
return {};
}
const auto &response = result.value();
if (response.status >= 400) {
LOG_ERROR(Frontend,
"GET to {}{} returned error status code: {}",
url,
path,
response.status);
return {};
}
if (!response.headers.contains("content-type")) {
LOG_ERROR(Frontend, "GET to {}{} returned no content", url, path);
return {};
}
return response.body;
} catch (std::exception &e) {
LOG_ERROR(Frontend,
"GET to {}{} failed during update check: {}",
url,
path,
e.what());
boost::split(result, latest->tag, boost::is_any_of("."));
if (result.size() != 2)
return std::nullopt;
}
}
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease() {
#ifdef YUZU_BUNDLED_OPENSSL
const auto update_check_url = fmt::format("https://{}", Common::g_build_auto_update_api);
const std::string tag = result[1];
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
if (result.empty())
return std::nullopt;
const std::string build = result[0];
#else
const auto update_check_url = std::string{Common::g_build_auto_update_api};
const std::string tag = latest->tag;
const std::string build = Common::g_build_version;
#endif
auto update_check_path = std::string{Common::g_build_auto_update_api_path};
try {
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
if (tag != build)
return latest;
if (!response)
return {};
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
return Update{latest_tag, latest_name};
} catch (nlohmann::detail::out_of_range&) {
LOG_ERROR(Frontend,
"Parsing JSON response from {}{} failed during update check: "
"nlohmann::detail::out_of_range",
update_check_url,
update_check_path);
return {};
} catch (nlohmann::detail::type_error&) {
LOG_ERROR(Frontend,
"Parsing JSON response from {}{} failed during update check: "
"nlohmann::detail::type_error",
update_check_url,
update_check_path);
return {};
}
}
std::optional<UpdateChecker::Update> UpdateChecker::GetUpdate() {
const std::optional<UpdateChecker::Update> latest_release_tag =
UpdateChecker::GetLatestRelease();
if (!latest_release_tag)
goto empty;
{
std::string tag, build;
if (Common::g_is_nightly_build) {
std::vector<std::string> result;
boost::split(result, latest_release_tag->tag, boost::is_any_of("."));
if (result.size() != 2)
goto empty;
tag = result[1];
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
if (result.empty())
goto empty;
build = result[0];
} else {
tag = latest_release_tag->tag;
build = Common::g_build_version;
}
if (tag != build)
return latest_release_tag.value();
}
empty:
return std::nullopt;
}
+2 -8
View File
@@ -8,16 +8,10 @@
#pragma once
#include <optional>
#include <string>
#include "common/net/net.h"
namespace UpdateChecker {
typedef struct {
std::string tag;
std::string name;
} Update;
std::optional<Common::Net::Release> GetUpdate();
std::optional<std::string> GetResponse(std::string url, std::string path);
std::optional<Update> GetLatestRelease();
std::optional<Update> GetUpdate();
} // namespace UpdateChecker
+15 -9
View File
@@ -157,11 +157,14 @@ void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t c
if (!controller.device->IsConnected()) {
return;
}
auto* shared_memory = controller.shared_memory;
const auto& battery_level = controller.device->GetBattery();
shared_memory->battery_level_dual = battery_level.dual.battery_level;
shared_memory->battery_level_left = battery_level.left.battery_level;
shared_memory->battery_level_right = battery_level.right.battery_level;
if (auto* shared_memory = controller.shared_memory; shared_memory) {
const auto& battery_level = controller.device->GetBattery();
shared_memory->battery_level_dual = battery_level.dual.battery_level;
shared_memory->battery_level_left = battery_level.left.battery_level;
shared_memory->battery_level_right = battery_level.right.battery_level;
} else {
LOG_WARNING(Service_HID, "shared_memory is null {}", controller_idx);
}
break;
}
default:
@@ -180,6 +183,10 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) {
const auto& body_colors = controller.device->GetColors();
const auto& battery_level = controller.device->GetBattery();
auto* shared_memory = controller.shared_memory;
if (!shared_memory) {
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id);
return;
}
if (controller_type == Core::HID::NpadStyleIndex::None) {
npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id);
return;
@@ -801,7 +808,7 @@ Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) {
auto* shared_memory = controller.shared_memory;
if (!shared_memory) {
LOG_WARNING(Service_HID, "DisconnectNpad: shared_memory is null for npad_id={}", npad_id);
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id);
return ResultSuccess;
}
// Don't reset shared_memory->assignment_mode this value is persistent
@@ -1195,11 +1202,10 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) {
const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid();
const auto& shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory;
const auto* shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory;
return {
.ui_variant = 0,
.footer = shared_memory->applet_footer_type,
.footer = shared_memory ? shared_memory->applet_footer_type : AppletFooterUiType::None,
};
}
@@ -10,7 +10,6 @@
#include <utility>
#include <vector>
#include "common/logging/log.h"
#include "common/settings.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
@@ -431,23 +430,15 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
ctx.AddExtension("SPV_KHR_shader_draw_parameters");
ctx.AddCapability(spv::Capability::DrawParameters);
}
const bool stage_supports_warp = profile.SupportsWarpIntrinsics(ctx.stage);
const bool needs_warp_intrinsics = info.uses_subgroup_vote ||
info.uses_subgroup_invocation_id ||
info.uses_subgroup_shuffles;
if (needs_warp_intrinsics && profile.support_vote && stage_supports_warp) {
if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
info.uses_subgroup_shuffles) &&
profile.support_vote) {
ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
if (!profile.warp_size_potentially_larger_than_guest) {
// vote ops are only used when not taking the long path
ctx.AddCapability(spv::Capability::GroupNonUniformVote);
}
} else if (needs_warp_intrinsics && !stage_supports_warp) {
LOG_WARNING(Shader,
"Warp intrinsics requested in stage {} but the device does not report subgroup "
"support; falling back to scalar approximations",
static_cast<u32>(ctx.stage));
}
if (info.uses_int64_bit_atomics && profile.support_int64_atomics) {
ctx.AddCapability(spv::Capability::Int64Atomics);
@@ -483,21 +483,6 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
}
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
const AttributeType output_type{ctx.runtime_info.color_output_types[index]};
Id pointer_type{ctx.output_f32};
Id store_value{value};
switch (output_type) {
case AttributeType::SignedInt:
pointer_type = ctx.output_s32;
store_value = ctx.OpBitcast(ctx.S32[1], value);
break;
case AttributeType::UnsignedInt:
pointer_type = ctx.output_u32;
store_value = ctx.OpBitcast(ctx.U32[1], value);
break;
default:
break;
}
const Id component_id{ctx.Const(component)};
const AttributeType type{ctx.runtime_info.color_output_types[index]};
if (type == AttributeType::Float) {
@@ -40,10 +40,7 @@ public:
explicit ImageOperands(EmitContext& ctx, const IR::Value& offset, const IR::Value& offset2) {
if (offset2.IsEmpty()) {
if (offset.IsEmpty()) {
return;
}
Add(spv::ImageOperandsMask::Offset, ctx.Def(offset));
AddOffset(ctx, offset, ImageGatherOffsetAllowed);
return;
}
const std::array values{offset.InstRecursive(), offset2.InstRecursive()};
@@ -199,41 +196,6 @@ Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR
}
}
Id TextureColorResultType(EmitContext& ctx, const TextureDefinition& def) {
switch (def.component_type) {
case SamplerComponentType::Float:
case SamplerComponentType::Depth:
return ctx.F32[4];
case SamplerComponentType::Sint:
return ctx.S32[4];
case SamplerComponentType::Stencil:
return ctx.U32[4];
case SamplerComponentType::Uint:
return ctx.U32[4];
}
throw InvalidArgument("Invalid sampler component type {}", def.component_type);
}
Id TextureSampleResultToFloat(EmitContext& ctx, const TextureDefinition& def, Id color) {
switch (def.component_type) {
case SamplerComponentType::Float:
case SamplerComponentType::Depth:
return color;
case SamplerComponentType::Sint:
return ctx.OpConvertSToF(ctx.F32[4], color);
case SamplerComponentType::Stencil:
{
const Id converted{ctx.OpConvertUToF(ctx.F32[4], color)};
const Id inv255{ctx.Const(1.0f / 255.0f)};
const Id scale{ctx.ConstantComposite(ctx.F32[4], inv255, inv255, inv255, inv255)};
return ctx.OpFMul(ctx.F32[4], converted, scale);
}
case SamplerComponentType::Uint:
return ctx.OpConvertUToF(ctx.F32[4], color);
}
throw InvalidArgument("Invalid sampler component type {}", def.component_type);
}
Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& index) {
if (info.type == TextureType::Buffer) {
const TextureBufferDefinition& def{ctx.texture_buffers.at(info.descriptor_index)};
@@ -386,8 +348,8 @@ void AddOffsetToCoordinates(EmitContext& ctx, const IR::TextureInstInfo& info, I
break;
}
case TextureType::ColorArray2D:
offset = ctx.OpCompositeConstruct(ctx.U32[3], ctx.OpCompositeExtract(ctx.U32[1], coords, 0),
ctx.OpCompositeExtract(ctx.U32[1], coords, 1),
offset = ctx.OpCompositeConstruct(ctx.U32[3], ctx.OpCompositeExtract(ctx.U32[1], offset, 0),
ctx.OpCompositeExtract(ctx.U32[1], offset, 1),
ctx.u32_zero_value);
[[fallthrough]];
case TextureType::Color3D: {
@@ -501,39 +463,38 @@ Id EmitBoundImageWrite(EmitContext&) {
Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id bias_lc, const IR::Value& offset) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
const Id color_type{TextureColorResultType(ctx, def)};
const Id texture{Texture(ctx, info, index)};
Id color{};
if (ctx.stage == Stage::Fragment) {
const ImageOperands operands(ctx, info.has_bias != 0, false, info.has_lod_clamp != 0,
bias_lc, offset);
color = Emit(&EmitContext::OpImageSparseSampleImplicitLod,
&EmitContext::OpImageSampleImplicitLod, ctx, inst, color_type, texture,
coords, operands.MaskOptional(), operands.Span());
return Emit(&EmitContext::OpImageSparseSampleImplicitLod,
&EmitContext::OpImageSampleImplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
} else {
// We can't use implicit lods on non-fragment stages on SPIR-V. Maxwell hardware behaves as
// if the lod was explicitly zero. This may change on Turing with implicit compute
// derivatives
const Id lod{ctx.Const(0.0f)};
const ImageOperands operands(ctx, false, true, info.has_lod_clamp != 0, lod, offset);
color = Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type, texture,
coords, operands.Mask(), operands.Span());
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
}
return TextureSampleResultToFloat(ctx, def, color);
}
Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id lod, const IR::Value& offset) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
const Id color_type{TextureColorResultType(ctx, def)};
const ImageOperands operands(ctx, false, true, false, lod, offset);
const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type,
Texture(ctx, info, index), coords, operands.Mask(), operands.Span())};
return TextureSampleResultToFloat(ctx, def, color);
Id result = Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
#ifdef ANDROID
if (Settings::values.fix_bloom_effects.GetValue()) {
result = ctx.OpVectorTimesScalar(ctx.F32[4], result, ctx.Const(0.98f));
}
#endif
return result;
}
Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
@@ -569,18 +530,13 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Va
Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
const IR::Value& offset, const IR::Value& offset2) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
const Id color_type{TextureColorResultType(ctx, def)};
const ImageOperands operands(ctx, offset, offset2);
const Id texture{Texture(ctx, info, index)};
if (ctx.profile.need_gather_subpixel_offset) {
coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords);
}
const Id color{
Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, color_type,
texture, coords, ctx.Const(info.gather_component), operands.MaskOptional(),
operands.Span())};
return TextureSampleResultToFloat(ctx, def, color);
return Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst,
ctx.F32[4], Texture(ctx, info, index), coords, ctx.Const(info.gather_component),
operands.MaskOptional(), operands.Span());
}
Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
@@ -598,9 +554,6 @@ Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset,
Id lod, Id ms) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition* def =
info.type == TextureType::Buffer ? nullptr : &ctx.textures.at(info.descriptor_index);
const Id result_type{def ? TextureColorResultType(ctx, *def) : ctx.F32[4]};
AddOffsetToCoordinates(ctx, info, coords, offset);
if (info.type == TextureType::Buffer) {
lod = Id{};
@@ -610,13 +563,8 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
lod = Id{};
}
const ImageOperands operands(lod, ms);
Id color{Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst,
result_type, TextureImage(ctx, info, index), coords, operands.MaskOptional(),
operands.Span())};
if (def) {
color = TextureSampleResultToFloat(ctx, *def, color);
}
return color;
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
}
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
@@ -661,17 +609,14 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id derivatives, const IR::Value& offset, Id lod_clamp) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
const Id color_type{TextureColorResultType(ctx, def)};
const auto operands = info.num_derivatives == 3
? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
ctx.Def(offset), {}, lod_clamp)
: ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
info.num_derivatives, offset, lod_clamp);
const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type,
Texture(ctx, info, index), coords, operands.Mask(), operands.Span())};
return TextureSampleResultToFloat(ctx, def, color);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
}
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) {
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@@ -81,25 +78,9 @@ Id AddPartitionBase(EmitContext& ctx, Id thread_id) {
const Id partition_base{ctx.OpShiftLeftLogical(ctx.U32[1], partition_idx, ctx.Const(5u))};
return ctx.OpIAdd(ctx.U32[1], thread_id, partition_base);
}
bool SupportsWarpIntrinsics(const EmitContext& ctx) {
return ctx.profile.SupportsWarpIntrinsics(ctx.stage);
}
void SetAlwaysInBounds(EmitContext& ctx, IR::Inst* inst) {
SetInBoundsFlag(inst, ctx.true_value);
}
Id FallbackBallotMask(EmitContext& ctx, Id pred) {
const Id full_mask{ctx.Const(0xFFFFFFFFu)};
return ctx.OpSelect(ctx.U32[1], pred, full_mask, ctx.u32_zero_value);
}
} // Anonymous namespace
Id EmitLaneId(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
const Id id{GetThreadId(ctx)};
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return id;
@@ -108,9 +89,6 @@ Id EmitLaneId(EmitContext& ctx) {
}
Id EmitVoteAll(EmitContext& ctx, Id pred) {
if (!SupportsWarpIntrinsics(ctx)) {
return pred;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -124,9 +102,6 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) {
}
Id EmitVoteAny(EmitContext& ctx, Id pred) {
if (!SupportsWarpIntrinsics(ctx)) {
return pred;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -140,9 +115,6 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) {
}
Id EmitVoteEqual(EmitContext& ctx, Id pred) {
if (!SupportsWarpIntrinsics(ctx)) {
return pred;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -157,9 +129,6 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) {
}
Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
if (!SupportsWarpIntrinsics(ctx)) {
return FallbackBallotMask(ctx, pred);
}
const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)};
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U);
@@ -168,46 +137,27 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
}
Id EmitSubgroupEqMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_eq);
}
Id EmitSubgroupLtMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_lt);
}
Id EmitSubgroupLeMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_le);
}
Id EmitSubgroupGtMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_gt);
}
Id EmitSubgroupGeMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_ge);
}
Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!SupportsWarpIntrinsics(ctx)) {
SetAlwaysInBounds(ctx, inst);
return value;
}
const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)};
const Id thread_id{EmitLaneId(ctx)};
const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)};
@@ -227,10 +177,6 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla
Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!SupportsWarpIntrinsics(ctx)) {
SetAlwaysInBounds(ctx, inst);
return value;
}
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)};
@@ -246,10 +192,6 @@ Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!SupportsWarpIntrinsics(ctx)) {
SetAlwaysInBounds(ctx, inst);
return value;
}
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)};
@@ -265,10 +207,6 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!SupportsWarpIntrinsics(ctx)) {
SetAlwaysInBounds(ctx, inst);
return value;
}
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)};
@@ -28,41 +28,27 @@ enum class Operation {
FPMax,
};
Id ComponentScalarType(EmitContext& ctx, SamplerComponentType component_type) {
switch (component_type) {
case SamplerComponentType::Float:
case SamplerComponentType::Depth:
return ctx.F32[1];
case SamplerComponentType::Sint:
return ctx.S32[1];
case SamplerComponentType::Stencil:
return ctx.U32[1];
case SamplerComponentType::Uint:
return ctx.U32[1];
}
throw InvalidArgument("Invalid sampler component type {}", component_type);
}
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc, Id sampled_type) {
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
const spv::ImageFormat format{spv::ImageFormat::Unknown};
const Id type{ctx.F32[1]};
const bool depth{desc.is_depth};
const bool ms{desc.is_multisample};
switch (desc.type) {
case TextureType::Color1D:
return ctx.TypeImage(sampled_type, spv::Dim::Dim1D, depth, false, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, false, false, 1, format);
case TextureType::ColorArray1D:
return ctx.TypeImage(sampled_type, spv::Dim::Dim1D, depth, true, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format);
case TextureType::Color2D:
case TextureType::Color2DRect:
return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, depth, false, ms, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, ms, 1, format);
case TextureType::ColorArray2D:
return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, depth, true, ms, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, ms, 1, format);
case TextureType::Color3D:
return ctx.TypeImage(sampled_type, spv::Dim::Dim3D, depth, false, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim3D, depth, false, false, 1, format);
case TextureType::ColorCube:
return ctx.TypeImage(sampled_type, spv::Dim::Cube, depth, false, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Cube, depth, false, false, 1, format);
case TextureType::ColorArrayCube:
return ctx.TypeImage(sampled_type, spv::Dim::Cube, depth, true, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Cube, depth, true, false, 1, format);
case TextureType::Buffer:
break;
}
@@ -563,7 +549,6 @@ void EmitContext::DefineCommonTypes(const Info& info) {
output_f32 = Name(TypePointer(spv::StorageClass::Output, F32[1]), "output_f32");
output_u32 = Name(TypePointer(spv::StorageClass::Output, U32[1]), "output_u32");
output_s32 = Name(TypePointer(spv::StorageClass::Output, S32[1]), "output_s32");
if (info.uses_int8 && profile.support_int8) {
AddCapability(spv::Capability::Int8);
@@ -1375,8 +1360,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_index) {
textures.reserve(info.texture_descriptors.size());
for (const TextureDescriptor& desc : info.texture_descriptors) {
const Id result_type{ComponentScalarType(*this, desc.component_type)};
const Id image_type{ImageType(*this, desc, result_type)};
const Id image_type{ImageType(*this, desc)};
const Id sampled_type{TypeSampledImage(image_type)};
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, sampled_type)};
const Id desc_type{DescType(*this, sampled_type, pointer_type, desc.count)};
@@ -1389,10 +1373,8 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in
.sampled_type = sampled_type,
.pointer_type = pointer_type,
.image_type = image_type,
.result_type = result_type,
.count = desc.count,
.is_multisample = desc.is_multisample,
.component_type = desc.component_type,
});
if (profile.supported_spirv >= 0x00010400) {
interfaces.push_back(id);
@@ -1433,7 +1415,6 @@ void EmitContext::DefineImages(const Info& info, u32& binding, u32& scaling_inde
void EmitContext::DefineInputs(const IR::Program& program) {
const Info& info{program.info};
const VaryingState loads{info.loads.mask | info.passthrough.mask};
const bool stage_supports_warp = profile.SupportsWarpIntrinsics(stage);
if (info.uses_workgroup_id) {
workgroup_id = DefineInput(*this, U32[3], false, spv::BuiltIn::WorkgroupId);
@@ -1457,7 +1438,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
if (info.uses_is_helper_invocation) {
is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation);
}
if (info.uses_subgroup_mask && stage_supports_warp) {
if (info.uses_subgroup_mask) {
subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR);
subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR);
subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR);
@@ -1471,10 +1452,9 @@ void EmitContext::DefineInputs(const IR::Program& program) {
Decorate(subgroup_mask_ge, spv::Decoration::Flat);
}
}
if (stage_supports_warp &&
(info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
(profile.warp_size_potentially_larger_than_guest &&
(info.uses_subgroup_vote || info.uses_subgroup_mask)))) {
if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
(profile.warp_size_potentially_larger_than_guest &&
(info.uses_subgroup_vote || info.uses_subgroup_mask))) {
AddCapability(spv::Capability::GroupNonUniform);
subgroup_local_invocation_id =
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
@@ -1709,22 +1689,6 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
Decorate(frag_color[index], spv::Decoration::Location, index);
Name(frag_color[index], fmt::format("frag_color{}", index));
}
#if 0
const AttributeType output_type{runtime_info.color_output_types[index]};
const Id vec_type = [&, output_type]() -> Id {
switch (output_type) {
case AttributeType::SignedInt:
return S32[4];
case AttributeType::UnsignedInt:
return U32[4];
default:
return F32[4];
}
}();
frag_color[index] = DefineOutput(*this, vec_type, std::nullopt);
Decorate(frag_color[index], spv::Decoration::Location, index);
Name(frag_color[index], fmt::format("frag_color{}", index));
#endif
}
if (info.stores_frag_depth) {
frag_depth = DefineOutput(*this, F32[1], std::nullopt);
@@ -39,10 +39,8 @@ struct TextureDefinition {
Id sampled_type;
Id pointer_type;
Id image_type;
Id result_type;
u32 count;
bool is_multisample;
SamplerComponentType component_type;
};
struct TextureBufferDefinition {
@@ -251,7 +249,6 @@ public:
Id output_f32{};
Id output_u32{};
Id output_s32{};
Id image_buffer_type{};
Id image_u32{};
-2
View File
@@ -77,8 +77,6 @@ public:
[[nodiscard]] virtual TextureType ReadTextureType(u32 raw_handle) = 0;
[[nodiscard]] virtual SamplerComponentType ReadTextureComponentType(u32 raw_handle) = 0;
[[nodiscard]] virtual TexturePixelFormat ReadTexturePixelFormat(u32 raw_handle) = 0;
[[nodiscard]] virtual bool IsTexturePixelFormatInteger(u32 raw_handle) = 0;
@@ -418,10 +418,6 @@ u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) {
return env.IsTexturePixelFormatInteger(GetTextureHandle(env, cbuf));
}
SamplerComponentType ReadTextureComponentType(Environment& env, const ConstBufferAddr& cbuf) {
return env.ReadTextureComponentType(GetTextureHandle(env, cbuf));
}
class Descriptors {
public:
explicit Descriptors(TextureBufferDescriptors& texture_buffer_descriptors_,
@@ -459,9 +455,7 @@ public:
u32 Add(const TextureDescriptor& desc) {
const u32 index{Add(texture_descriptors, desc, [&desc](const auto& existing) {
return desc.type == existing.type &&
desc.component_type == existing.component_type &&
desc.is_depth == existing.is_depth &&
return desc.type == existing.type && desc.is_depth == existing.is_depth &&
desc.has_secondary == existing.has_secondary &&
desc.cbuf_index == existing.cbuf_index &&
desc.cbuf_offset == existing.cbuf_offset &&
@@ -696,12 +690,10 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.secondary_shift_left = cbuf.secondary_shift_left,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
.component_type = ReadTextureComponentType(env, cbuf),
});
} else {
index = descriptors.Add(TextureDescriptor{
.type = flags.type,
.component_type = ReadTextureComponentType(env, cbuf),
.is_depth = flags.is_depth != 0,
.is_multisample = is_multisample,
.has_secondary = cbuf.has_secondary,
-13
View File
@@ -1,15 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 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
#pragma once
#include <limits>
#include "common/common_types.h"
#include "shader_recompiler/stage.h"
namespace Shader {
@@ -52,8 +46,6 @@ struct Profile {
bool support_multi_viewport{};
bool support_geometry_streams{};
u32 warp_stage_support_mask{std::numeric_limits<u32>::max()};
bool warp_size_potentially_larger_than_guest{};
bool lower_left_origin_mode{};
@@ -98,11 +90,6 @@ struct Profile {
u64 min_ssbo_alignment{};
u32 max_user_clip_distances{};
bool SupportsWarpIntrinsics(Stage stage) const {
const u32 bit = 1u << static_cast<u32>(stage);
return (warp_stage_support_mask & bit) != 0;
}
};
} // namespace Shader
-1
View File
@@ -84,7 +84,6 @@ struct TransformFeedbackVarying {
struct RuntimeInfo {
std::array<AttributeType, 32> generic_input_types{};
std::array<AttributeType, 8> color_output_types{};
VaryingState previous_stage_stores;
std::map<IR::Attribute, IR::Attribute> previous_stage_legacy_stores_mapping;
-10
View File
@@ -154,14 +154,6 @@ enum class ImageFormat : u32 {
R32G32B32A32_UINT,
};
enum class SamplerComponentType : u8 {
Float,
Sint,
Uint,
Depth,
Stencil,
};
enum class Interpolation {
Smooth,
Flat,
@@ -194,7 +186,6 @@ struct TextureBufferDescriptor {
u32 secondary_shift_left;
u32 count;
u32 size_shift;
SamplerComponentType component_type;
auto operator<=>(const TextureBufferDescriptor&) const = default;
};
@@ -216,7 +207,6 @@ using ImageBufferDescriptors = boost::container::small_vector<ImageBufferDescrip
struct TextureDescriptor {
TextureType type;
SamplerComponentType component_type;
bool is_depth;
bool is_multisample;
bool has_secondary;
+4 -5
View File
@@ -8,7 +8,7 @@ add_subdirectory(host_shaders)
add_subdirectory(gpu_logging)
if(LIBVA_FOUND)
set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
set_source_files_properties(host1x/ffmpeg.cpp
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
endif()
@@ -44,7 +44,6 @@ add_library(video_core STATIC
engines/sw_blitter/converter.h
engines/const_buffer_info.h
engines/draw_manager.cpp
engines/draw_manager.h
engines/engine_interface.h
engines/engine_upload.cpp
engines/engine_upload.h
@@ -72,9 +71,9 @@ add_library(video_core STATIC
host1x/codecs/vp8.h
host1x/codecs/vp9.cpp
host1x/codecs/vp9.h
host1x/codecs/vp9_types.h
host1x/ffmpeg/ffmpeg.cpp
host1x/ffmpeg/ffmpeg.h
host1x/codec_types.h
host1x/ffmpeg.cpp
host1x/ffmpeg.h
host1x/control.cpp
host1x/control.h
host1x/gpu_device_memory_manager.cpp
+13 -19
View File
@@ -356,7 +356,7 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
if (is_indexed) {
BindHostIndexBuffer();
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
const auto& draw_state = maxwell3d->draw_manager.draw_state;
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
@@ -740,30 +740,25 @@ void BufferCache<P>::BindHostIndexBuffer() {
TouchBuffer(buffer, channel_state->index_buffer.buffer_id);
const u32 offset = buffer.Offset(channel_state->index_buffer.device_addr);
const u32 size = channel_state->index_buffer.size;
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
const auto& draw_state = maxwell3d->draw_manager.draw_state;
if (draw_state.inline_index_draw_indexes.empty()) {
SynchronizeBuffer(buffer, channel_state->index_buffer.device_addr, size);
} else {
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
auto upload_staging = runtime.UploadStagingBuffer(size);
std::array<BufferCopy, 1> copies{
{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
std::memcpy(upload_staging.mapped_span.data(),
draw_state.inline_index_draw_indexes.data(), size);
std::array<BufferCopy, 1> copies{{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
std::memcpy(upload_staging.mapped_span.data(), draw_state.inline_index_draw_indexes.data(), size);
runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true);
} else {
buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes);
}
} else {
SynchronizeBuffer(buffer, channel_state->index_buffer.device_addr, size);
}
if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
const u32 new_offset =
offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
const u32 new_offset = offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
runtime.BindIndexBuffer(buffer, new_offset, size);
} else {
buffer.MarkUsage(offset, size);
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
draw_state.index_buffer.first, draw_state.index_buffer.count,
buffer, offset, size);
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format, draw_state.index_buffer.first, draw_state.index_buffer.count, buffer, offset, size);
}
}
@@ -945,10 +940,9 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
return alignment > 1 && (offset % alignment) != 0;
}
}();
const bool use_fast_buffer = needs_alignment_stream ||
(has_host_buffer &&
size <= channel_state->uniform_buffer_skip_cache_size &&
!memory_tracker.IsRegionGpuModified(device_addr, size));
const bool use_fast_buffer = needs_alignment_stream
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size
&& !memory_tracker.IsRegionGpuModified(device_addr, size));
if (use_fast_buffer) {
if constexpr (IS_OPENGL) {
if (runtime.HasFastBufferSubData()) {
@@ -1229,7 +1223,7 @@ template <class P>
void BufferCache<P>::UpdateIndexBuffer() {
// We have to check for the dirty flags and index count
// The index count is currently changed without updating the dirty flags
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
const auto& draw_state = maxwell3d->draw_manager.draw_state;
const auto& index_buffer_ref = draw_state.index_buffer;
auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::IndexBuffer]) {
@@ -32,7 +32,7 @@
#include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
@@ -305,7 +305,7 @@ public:
[[nodiscard]] bool IsRegionCpuModified(DAddr addr, size_t size);
void SetDrawIndirect(
const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect_) {
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect_) {
current_draw_indirect = current_draw_indirect_;
}
@@ -480,7 +480,7 @@ private:
#endif
DelayedDestructionRing<Buffer, TICKS_TO_DESTROY> delayed_destruction_ring;
const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect{};
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
u32 last_index_count = 0;
+78 -86
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: Ryujinx Team and Contributors
@@ -20,109 +20,101 @@
namespace Tegra {
CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id)
: host1x{host1x_}, memory_manager{host1x.GMMU()},
host_processor{std::make_unique<Host1x::Control>(host1x_)}, current_class{
static_cast<ChClassId>(id)} {
thread = std::jthread([this](std::stop_token stop_token) { ProcessEntries(stop_token); });
: host_processor(host1x_)
, host1x{host1x_}
, current_class{ChClassId(id)}
{
thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
ChCommandHeaderList command_list{host1x.System().ApplicationMemory(), 0, 0};
u32 count{};
u32 method_offset{};
u32 mask{};
bool incrementing{};
while (!stop_token.stop_requested()) {
{
std::unique_lock l{command_mutex};
command_cv.wait(l, stop_token, [this]() { return command_lists.size() > 0; });
if (stop_token.stop_requested()) {
return;
}
command_list = std::move(command_lists.front());
command_lists.pop_front();
}
size_t i = 0;
for (const auto value : command_list) {
i++;
if (mask != 0) {
const auto lbs = static_cast<u32>(std::countr_zero(mask));
mask &= ~(1U << lbs);
ExecuteCommand(method_offset + lbs, value.raw);
continue;
} else if (count != 0) {
--count;
ExecuteCommand(method_offset, value.raw);
if (incrementing) {
++method_offset;
}
continue;
}
const auto mode = value.submission_mode.Value();
switch (mode) {
case ChSubmissionMode::SetClass: {
mask = value.value & 0x3f;
method_offset = value.method_offset;
current_class = ChClassId((value.value >> 6) & 0x3ff);
break;
}
case ChSubmissionMode::Incrementing:
case ChSubmissionMode::NonIncrementing:
count = value.value;
method_offset = value.method_offset;
incrementing = mode == ChSubmissionMode::Incrementing;
break;
case ChSubmissionMode::Mask:
mask = value.value;
method_offset = value.method_offset;
break;
case ChSubmissionMode::Immediate: {
const u32 data = value.value & 0xfff;
method_offset = value.method_offset;
ExecuteCommand(method_offset, data);
break;
}
default:
LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1, (i - 1) * sizeof(u32), command_list.size());
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", u32(mode));
break;
}
}
}
});
}
CDmaPusher::~CDmaPusher() = default;
void CDmaPusher::ProcessEntries(std::stop_token stop_token) {
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
ChCommandHeaderList command_list{host1x.System().ApplicationMemory(), 0, 0};
u32 count{};
u32 method_offset{};
u32 mask{};
bool incrementing{};
while (!stop_token.stop_requested()) {
{
std::unique_lock l{command_mutex};
command_cv.wait(l, stop_token,
[this]() { return command_lists.size() > 0; });
if (stop_token.stop_requested()) {
return;
}
command_list = std::move(command_lists.front());
command_lists.pop_front();
}
size_t i = 0;
for (const auto value : command_list) {
i++;
if (mask != 0) {
const auto lbs = static_cast<u32>(std::countr_zero(mask));
mask &= ~(1U << lbs);
ExecuteCommand(method_offset + lbs, value.raw);
continue;
} else if (count != 0) {
--count;
ExecuteCommand(method_offset, value.raw);
if (incrementing) {
++method_offset;
}
continue;
}
const auto mode = value.submission_mode.Value();
switch (mode) {
case ChSubmissionMode::SetClass: {
mask = value.value & 0x3f;
method_offset = value.method_offset;
current_class = static_cast<ChClassId>((value.value >> 6) & 0x3ff);
break;
}
case ChSubmissionMode::Incrementing:
case ChSubmissionMode::NonIncrementing:
count = value.value;
method_offset = value.method_offset;
incrementing = mode == ChSubmissionMode::Incrementing;
break;
case ChSubmissionMode::Mask:
mask = value.value;
method_offset = value.method_offset;
break;
case ChSubmissionMode::Immediate: {
const u32 data = value.value & 0xfff;
method_offset = value.method_offset;
ExecuteCommand(method_offset, data);
break;
}
default:
LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1,
(i - 1) * sizeof(u32), command_list.size());
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!",
static_cast<u32>(mode));
break;
}
}
}
}
void CDmaPusher::ExecuteCommand(u32 method, u32 arg) {
switch (current_class) {
case ChClassId::Control:
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}",
static_cast<u32>(current_class), method, arg);
host_processor->ProcessMethod(static_cast<Host1x::Control::Method>(method), arg);
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), method, arg);
host_processor.ProcessMethod(Host1x::Control::Method(method), arg);
break;
default:
thi_regs.reg_array[method] = arg;
switch (static_cast<ThiMethod>(method)) {
case ThiMethod::IncSyncpt: {
const auto syncpoint_id = static_cast<u32>(arg & 0xFF);
[[maybe_unused]] const auto cond = static_cast<u32>((arg >> 8) & 0xFF);
LOG_TRACE(Service_NVDRV, "Class {} IncSyncpt Method, syncpt {} cond {}",
static_cast<u32>(current_class), syncpoint_id, cond);
const auto syncpoint_id = u32(arg & 0xFF);
[[maybe_unused]] const auto cond = u32((arg >> 8) & 0xFF);
LOG_TRACE(Service_NVDRV, "Class {} IncSyncpt Method, syncpt {} cond {}", u32(current_class), syncpoint_id, cond);
auto& syncpoint_manager = host1x.GetSyncpointManager();
syncpoint_manager.IncrementGuest(syncpoint_id);
syncpoint_manager.IncrementHost(syncpoint_id);
break;
}
case ThiMethod::SetMethod1:
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}",
static_cast<u32>(current_class), static_cast<u32>(thi_regs.method_0), arg);
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), u32(thi_regs.method_0), arg);
ProcessMethod(thi_regs.method_0, arg);
break;
default:
+11 -14
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,11 +18,11 @@
#include "common/common_types.h"
#include "common/polyfill_thread.h"
#include "core/memory.h"
#include "video_core/host1x/control.h"
namespace Tegra {
namespace Host1x {
class Control;
class Host1x;
class Nvdec;
class SyncptIncrManager;
@@ -117,25 +120,19 @@ protected:
virtual void ProcessMethod(u32 method, u32 arg) = 0;
Host1x::Host1x& host1x;
Tegra::MemoryManager& memory_manager;
private:
/// Process the command entry
void ProcessEntries(std::stop_token stop_token);
/// Invoke command class devices to execute the command based on the current state
void ExecuteCommand(u32 state_offset, u32 data);
std::unique_ptr<Host1x::Control> host_processor;
std::mutex command_mutex;
std::condition_variable_any command_cv;
std::deque<ChCommandHeaderList> command_lists;
std::jthread thread;
protected:
ThiRegisters thi_regs{};
std::deque<ChCommandHeaderList> command_lists;
std::condition_variable_any command_cv;
Host1x::Control host_processor;
std::mutex command_mutex;
Host1x::Host1x& host1x;
ChClassId current_class;
std::jthread thread;
};
} // namespace Tegra
-6
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -238,9 +235,6 @@ constexpr Table MakeViewTable() {
EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_12x10_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA);
Enable(view, PixelFormat::D24_UNORM_S8_UINT, PixelFormat::S8_UINT);
Enable(view, PixelFormat::S8_UINT_D24_UNORM, PixelFormat::S8_UINT);
Enable(view, PixelFormat::D32_FLOAT_S8_UINT, PixelFormat::S8_UINT);
return view;
}
+103 -139
View File
@@ -1,23 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/rasterizer_interface.h"
namespace Tegra::Engines {
DrawManager::DrawManager(Maxwell3D* maxwell3d_) : maxwell3d(maxwell3d_) {}
void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
const auto& regs{maxwell3d->regs};
void Maxwell3D::DrawManager::ProcessMethodCall(Maxwell3D& maxwell3d, u32 method, u32 argument) {
switch (method) {
case MAXWELL3D_REG_INDEX(clear_surface):
return Clear(1);
return Clear(maxwell3d, 1);
case MAXWELL3D_REG_INDEX(draw.begin):
return DrawBegin();
return DrawBegin(maxwell3d);
case MAXWELL3D_REG_INDEX(draw.end):
return DrawEnd();
return DrawEnd(maxwell3d);
case MAXWELL3D_REG_INDEX(vertex_buffer.first):
case MAXWELL3D_REG_INDEX(vertex_buffer.count):
case MAXWELL3D_REG_INDEX(index_buffer.first):
@@ -33,33 +34,29 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
case MAXWELL3D_REG_INDEX(index_buffer32_first):
case MAXWELL3D_REG_INDEX(index_buffer16_first):
case MAXWELL3D_REG_INDEX(index_buffer8_first):
return DrawIndexSmall(argument);
return DrawIndexSmall(maxwell3d, argument);
case MAXWELL3D_REG_INDEX(draw_inline_index):
SetInlineIndexBuffer(argument);
SetInlineIndexBuffer(maxwell3d, argument);
break;
case MAXWELL3D_REG_INDEX(inline_index_2x16.even):
SetInlineIndexBuffer(regs.inline_index_2x16.even);
SetInlineIndexBuffer(regs.inline_index_2x16.odd);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_2x16.even);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_2x16.odd);
break;
case MAXWELL3D_REG_INDEX(inline_index_4x8.index0):
SetInlineIndexBuffer(regs.inline_index_4x8.index0);
SetInlineIndexBuffer(regs.inline_index_4x8.index1);
SetInlineIndexBuffer(regs.inline_index_4x8.index2);
SetInlineIndexBuffer(regs.inline_index_4x8.index3);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index0);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index1);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index2);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index3);
break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
regs.vertex_array_instance_first.start.Value(),
regs.vertex_array_instance_first.count.Value(), false);
DrawArrayInstanced(maxwell3d, maxwell3d.regs.vertex_array_instance_first.topology.Value(), maxwell3d.regs.vertex_array_instance_first.start.Value(), maxwell3d.regs.vertex_array_instance_first.count.Value(), false);
break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
regs.vertex_array_instance_subsequent.start.Value(),
regs.vertex_array_instance_subsequent.count.Value(), true);
DrawArrayInstanced(maxwell3d, maxwell3d.regs.vertex_array_instance_subsequent.topology.Value(), maxwell3d.regs.vertex_array_instance_subsequent.start.Value(), maxwell3d.regs.vertex_array_instance_subsequent.count.Value(), true);
break;
}
case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
DrawTexture();
DrawTexture(maxwell3d);
break;
}
default:
@@ -67,101 +64,87 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
}
}
void DrawManager::Clear(u32 layer_count) {
if (maxwell3d->ShouldExecute()) {
maxwell3d->rasterizer->Clear(layer_count);
void Maxwell3D::DrawManager::Clear(Maxwell3D& maxwell3d, u32 layer_count) {
if (maxwell3d.ShouldExecute()) {
maxwell3d.rasterizer->Clear(layer_count);
}
}
void DrawManager::DrawDeferred() {
void Maxwell3D::DrawManager::DrawDeferred(Maxwell3D& maxwell3d) {
if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0) {
return;
}
DrawEnd(draw_state.instance_count + 1, true);
DrawEnd(maxwell3d, draw_state.instance_count + 1, true);
draw_state.instance_count = 0;
}
void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
u32 base_instance, u32 num_instances) {
void Maxwell3D::DrawManager::DrawArray(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, u32 base_instance, u32 num_instances) {
draw_state.topology = topology;
draw_state.vertex_buffer.first = vertex_first;
draw_state.vertex_buffer.count = vertex_count;
draw_state.base_instance = base_instance;
ProcessDraw(false, num_instances);
ProcessDraw(maxwell3d, false, num_instances);
}
void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
bool subsequent) {
void Maxwell3D::DrawManager::DrawArrayInstanced(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, bool subsequent) {
draw_state.topology = topology;
draw_state.vertex_buffer.first = vertex_first;
draw_state.vertex_buffer.count = vertex_count;
if (!subsequent) {
draw_state.instance_count = 1;
}
draw_state.base_instance = draw_state.instance_count - 1;
draw_state.draw_mode = DrawMode::Instance;
draw_state.instance_count++;
ProcessDraw(false, 1);
ProcessDraw(maxwell3d, false, 1);
}
void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
u32 base_index, u32 base_instance, u32 num_instances) {
const auto& regs{maxwell3d->regs};
void Maxwell3D::DrawManager::DrawIndex(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, u32 base_instance, u32 num_instances) {
draw_state.topology = topology;
draw_state.index_buffer = regs.index_buffer;
draw_state.index_buffer = maxwell3d.regs.index_buffer;
draw_state.index_buffer.first = index_first;
draw_state.index_buffer.count = index_count;
draw_state.base_index = base_index;
draw_state.base_instance = base_instance;
ProcessDraw(true, num_instances);
ProcessDraw(maxwell3d, true, num_instances);
}
void DrawManager::DrawArrayIndirect(PrimitiveTopology topology) {
void Maxwell3D::DrawManager::DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology) {
draw_state.topology = topology;
ProcessDrawIndirect();
ProcessDrawIndirect(maxwell3d);
}
void DrawManager::DrawIndexedIndirect(PrimitiveTopology topology, u32 index_first,
u32 index_count) {
const auto& regs{maxwell3d->regs};
void Maxwell3D::DrawManager::DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count) {
draw_state.topology = topology;
draw_state.index_buffer = regs.index_buffer;
draw_state.index_buffer = maxwell3d.regs.index_buffer;
draw_state.index_buffer.first = index_first;
draw_state.index_buffer.count = index_count;
ProcessDrawIndirect();
ProcessDrawIndirect(maxwell3d);
}
void DrawManager::SetInlineIndexBuffer(u32 index) {
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>(index & 0x000000ff));
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x0000ff00) >> 8));
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x00ff0000) >> 16));
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0xff000000) >> 24));
void Maxwell3D::DrawManager::SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index) {
draw_state.inline_index_draw_indexes.push_back(u8(index & 0x000000ff));
draw_state.inline_index_draw_indexes.push_back(u8((index & 0x0000ff00) >> 8));
draw_state.inline_index_draw_indexes.push_back(u8((index & 0x00ff0000) >> 16));
draw_state.inline_index_draw_indexes.push_back(u8((index & 0xff000000) >> 24));
draw_state.draw_mode = DrawMode::InlineIndex;
}
void DrawManager::DrawBegin() {
const auto& regs{maxwell3d->regs};
auto reset_instance_count = regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First;
auto increment_instance_count =
regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent;
void Maxwell3D::DrawManager::DrawBegin(Maxwell3D& maxwell3d) {
auto reset_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First;
auto increment_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent;
if (reset_instance_count) {
DrawDeferred();
DrawDeferred(maxwell3d);
draw_state.instance_count = 0;
draw_state.draw_mode = DrawMode::General;
} else if (increment_instance_count) {
draw_state.instance_count++;
draw_state.draw_mode = DrawMode::Instance;
}
draw_state.topology = regs.draw.topology;
draw_state.topology = maxwell3d.regs.draw.topology;
}
void DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
const auto& regs{maxwell3d->regs};
void Maxwell3D::DrawManager::DrawEnd(Maxwell3D& maxwell3d, u32 instance_count, bool force_draw) {
switch (draw_state.draw_mode) {
case DrawMode::Instance:
if (!force_draw) {
@@ -169,119 +152,100 @@ void DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
}
[[fallthrough]];
case DrawMode::General:
draw_state.base_instance = regs.global_base_instance_index;
draw_state.base_index = regs.global_base_vertex_index;
draw_state.base_instance = maxwell3d.regs.global_base_instance_index;
draw_state.base_index = maxwell3d.regs.global_base_vertex_index;
if (draw_state.draw_indexed) {
draw_state.index_buffer = regs.index_buffer;
ProcessDraw(true, instance_count);
draw_state.index_buffer = maxwell3d.regs.index_buffer;
ProcessDraw(maxwell3d, true, instance_count);
} else {
draw_state.vertex_buffer = regs.vertex_buffer;
ProcessDraw(false, instance_count);
draw_state.vertex_buffer = maxwell3d.regs.vertex_buffer;
ProcessDraw(maxwell3d, false, instance_count);
}
draw_state.draw_indexed = false;
break;
case DrawMode::InlineIndex:
draw_state.base_instance = regs.global_base_instance_index;
draw_state.base_index = regs.global_base_vertex_index;
draw_state.index_buffer = regs.index_buffer;
draw_state.index_buffer.count =
static_cast<u32>(draw_state.inline_index_draw_indexes.size() / 4);
draw_state.base_instance = maxwell3d.regs.global_base_instance_index;
draw_state.base_index = maxwell3d.regs.global_base_vertex_index;
draw_state.index_buffer = maxwell3d.regs.index_buffer;
draw_state.index_buffer.count = u32(draw_state.inline_index_draw_indexes.size() / 4);
draw_state.index_buffer.format = Maxwell3D::Regs::IndexFormat::UnsignedInt;
maxwell3d->dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
ProcessDraw(true, instance_count);
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
ProcessDraw(maxwell3d, true, instance_count);
draw_state.inline_index_draw_indexes.clear();
break;
}
}
void DrawManager::DrawIndexSmall(u32 argument) {
const auto& regs{maxwell3d->regs};
IndexBufferSmall index_small_params{argument};
draw_state.base_instance = regs.global_base_instance_index;
draw_state.base_index = regs.global_base_vertex_index;
draw_state.index_buffer = regs.index_buffer;
void Maxwell3D::DrawManager::DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument) {
Maxwell3D::Regs::IndexBufferSmall index_small_params{argument};
draw_state.base_instance = maxwell3d.regs.global_base_instance_index;
draw_state.base_index = maxwell3d.regs.global_base_vertex_index;
draw_state.index_buffer = maxwell3d.regs.index_buffer;
draw_state.index_buffer.first = index_small_params.first;
draw_state.index_buffer.count = index_small_params.count;
draw_state.topology = index_small_params.topology;
maxwell3d->dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
ProcessDraw(true, 1);
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
ProcessDraw(maxwell3d, true, 1);
}
void DrawManager::DrawTexture() {
const auto& regs{maxwell3d->regs};
draw_texture_state.dst_x0 = static_cast<float>(regs.draw_texture.dst_x0) / 4096.f;
draw_texture_state.dst_y0 = static_cast<float>(regs.draw_texture.dst_y0) / 4096.f;
const auto dst_width = static_cast<float>(regs.draw_texture.dst_width) / 4096.f;
const auto dst_height = static_cast<float>(regs.draw_texture.dst_height) / 4096.f;
const bool lower_left{regs.window_origin.mode !=
Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft};
void Maxwell3D::DrawManager::DrawTexture(Maxwell3D& maxwell3d) {
draw_texture_state.dst_x0 = f32(maxwell3d.regs.draw_texture.dst_x0) / 4096.f;
draw_texture_state.dst_y0 = f32(maxwell3d.regs.draw_texture.dst_y0) / 4096.f;
const auto dst_width = f32(maxwell3d.regs.draw_texture.dst_width) / 4096.f;
const auto dst_height = f32(maxwell3d.regs.draw_texture.dst_height) / 4096.f;
const bool lower_left{maxwell3d.regs.window_origin.mode != Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft};
if (lower_left) {
draw_texture_state.dst_y0 =
static_cast<f32>(regs.surface_clip.height) - draw_texture_state.dst_y0;
draw_texture_state.dst_y0 = f32(maxwell3d.regs.surface_clip.height) - draw_texture_state.dst_y0;
}
draw_texture_state.dst_x1 = draw_texture_state.dst_x0 + dst_width;
draw_texture_state.dst_y1 = draw_texture_state.dst_y0 + dst_height;
draw_texture_state.src_x0 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f;
draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f;
draw_texture_state.src_x1 =
(static_cast<float>(regs.draw_texture.dx_du) / 4294967296.f) * dst_width +
draw_texture_state.src_x0;
draw_texture_state.src_y1 =
(static_cast<float>(regs.draw_texture.dy_dv) / 4294967296.f) * dst_height +
draw_texture_state.src_y0;
draw_texture_state.src_sampler = regs.draw_texture.src_sampler;
draw_texture_state.src_texture = regs.draw_texture.src_texture;
maxwell3d->rasterizer->DrawTexture();
draw_texture_state.src_x0 = f32(maxwell3d.regs.draw_texture.src_x0) / 4096.f;
draw_texture_state.src_y0 = f32(maxwell3d.regs.draw_texture.src_y0) / 4096.f;
draw_texture_state.src_x1 = (f32(maxwell3d.regs.draw_texture.dx_du) / 4294967296.f) * dst_width + draw_texture_state.src_x0;
draw_texture_state.src_y1 = (f32(maxwell3d.regs.draw_texture.dy_dv) / 4294967296.f) * dst_height + draw_texture_state.src_y0;
draw_texture_state.src_sampler = maxwell3d.regs.draw_texture.src_sampler;
draw_texture_state.src_texture = maxwell3d.regs.draw_texture.src_texture;
maxwell3d.rasterizer->DrawTexture();
}
void DrawManager::UpdateTopology() {
const auto& regs{maxwell3d->regs};
switch (regs.primitive_topology_control) {
case PrimitiveTopologyControl::UseInBeginMethods:
void Maxwell3D::DrawManager::UpdateTopology(Maxwell3D& maxwell3d) {
switch (maxwell3d.regs.primitive_topology_control) {
case Maxwell3D::Regs::PrimitiveTopologyControl::UseInBeginMethods:
break;
case PrimitiveTopologyControl::UseSeparateState:
switch (regs.topology_override) {
case PrimitiveTopologyOverride::None:
case Maxwell3D::Regs::PrimitiveTopologyControl::UseSeparateState:
switch (maxwell3d.regs.topology_override) {
case Maxwell3D::Regs::PrimitiveTopologyOverride::None:
break;
case PrimitiveTopologyOverride::Points:
draw_state.topology = PrimitiveTopology::Points;
case Maxwell3D::Regs::PrimitiveTopologyOverride::Points:
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Points;
break;
case PrimitiveTopologyOverride::Lines:
draw_state.topology = PrimitiveTopology::Lines;
case Maxwell3D::Regs::PrimitiveTopologyOverride::Lines:
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Lines;
break;
case PrimitiveTopologyOverride::LineStrip:
draw_state.topology = PrimitiveTopology::LineStrip;
case Maxwell3D::Regs::PrimitiveTopologyOverride::LineStrip:
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::LineStrip;
break;
default:
draw_state.topology = static_cast<PrimitiveTopology>(regs.topology_override);
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology(maxwell3d.regs.topology_override);
break;
}
break;
}
}
void DrawManager::ProcessDraw(bool draw_indexed, u32 instance_count) {
LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology,
draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
UpdateTopology();
if (maxwell3d->ShouldExecute()) {
maxwell3d->rasterizer->Draw(draw_indexed, instance_count);
void Maxwell3D::DrawManager::ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed, u32 instance_count) {
LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology, draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
UpdateTopology(maxwell3d);
if (maxwell3d.ShouldExecute()) {
maxwell3d.rasterizer->Draw(draw_indexed, instance_count);
}
}
void DrawManager::ProcessDrawIndirect() {
LOG_TRACE(
HW_GPU,
"called, topology={}, is_indexed={}, includes_count={}, buffer_size={}, max_draw_count={}",
draw_state.topology, indirect_state.is_indexed, indirect_state.include_count,
indirect_state.buffer_size, indirect_state.max_draw_counts);
UpdateTopology();
if (maxwell3d->ShouldExecute()) {
maxwell3d->rasterizer->DrawIndirect();
void Maxwell3D::DrawManager::ProcessDrawIndirect(Maxwell3D& maxwell3d) {
LOG_TRACE(HW_GPU, "called, topology={}, is_indexed={}, includes_count={}, buffer_size={}, max_draw_count={}", draw_state.topology, indirect_state.is_indexed, indirect_state.include_count, indirect_state.buffer_size, indirect_state.max_draw_counts);
UpdateTopology(maxwell3d);
if (maxwell3d.ShouldExecute()) {
maxwell3d.rasterizer->DrawIndirect();
}
}
} // namespace Tegra::Engines
-117
View File
@@ -1,117 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
namespace VideoCore {
class RasterizerInterface;
}
namespace Tegra::Engines {
using PrimitiveTopologyControl = Maxwell3D::Regs::PrimitiveTopologyControl;
using PrimitiveTopology = Maxwell3D::Regs::PrimitiveTopology;
using PrimitiveTopologyOverride = Maxwell3D::Regs::PrimitiveTopologyOverride;
using IndexBuffer = Maxwell3D::Regs::IndexBuffer;
using VertexBuffer = Maxwell3D::Regs::VertexBuffer;
using IndexBufferSmall = Maxwell3D::Regs::IndexBufferSmall;
class DrawManager {
public:
enum class DrawMode : u32 { General = 0, Instance, InlineIndex };
struct State {
PrimitiveTopology topology{};
DrawMode draw_mode{};
bool draw_indexed{};
u32 base_index{};
VertexBuffer vertex_buffer;
IndexBuffer index_buffer;
u32 base_instance{};
u32 instance_count{};
std::vector<u8> inline_index_draw_indexes;
};
struct DrawTextureState {
f32 dst_x0;
f32 dst_y0;
f32 dst_x1;
f32 dst_y1;
f32 src_x0;
f32 src_y0;
f32 src_x1;
f32 src_y1;
u32 src_sampler;
u32 src_texture;
};
struct IndirectParams {
bool is_byte_count;
bool is_indexed;
bool include_count;
GPUVAddr count_start_address;
GPUVAddr indirect_start_address;
size_t buffer_size;
size_t max_draw_counts;
size_t stride;
};
explicit DrawManager(Maxwell3D* maxwell_3d);
void ProcessMethodCall(u32 method, u32 argument);
void Clear(u32 layer_count);
void DrawDeferred();
void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
u32 base_instance, u32 num_instances);
void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
bool subsequent);
void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
u32 base_instance, u32 num_instances);
void DrawArrayIndirect(PrimitiveTopology topology);
void DrawIndexedIndirect(PrimitiveTopology topology, u32 index_first, u32 index_count);
const State& GetDrawState() const {
return draw_state;
}
const DrawTextureState& GetDrawTextureState() const {
return draw_texture_state;
}
IndirectParams& GetIndirectParams() {
return indirect_state;
}
const IndirectParams& GetIndirectParams() const {
return indirect_state;
}
private:
void SetInlineIndexBuffer(u32 index);
void DrawBegin();
void DrawEnd(u32 instance_count = 1, bool force_draw = false);
void DrawIndexSmall(u32 argument);
void DrawTexture();
void UpdateTopology();
void ProcessDraw(bool draw_indexed, u32 instance_count);
void ProcessDrawIndirect();
Maxwell3D* maxwell3d{};
State draw_state{};
DrawTextureState draw_texture_state{};
IndirectParams indirect_state{};
};
} // namespace Tegra::Engines
+6 -7
View File
@@ -13,7 +13,7 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
@@ -26,7 +26,8 @@ namespace Tegra::Engines {
constexpr u32 MacroRegistersStart = 0xE00;
Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
: draw_manager{std::make_unique<DrawManager>(this)}, system{system_}
: draw_manager()
, system{system_}
, memory_manager{memory_manager_}
#ifdef ARCHITECTURE_x86_64
, macro_engine(bool(Settings::values.disable_macro_jit))
@@ -373,8 +374,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
case MAXWELL3D_REG_INDEX(sync_info):
return ProcessSyncPoint();
case MAXWELL3D_REG_INDEX(launch_dma):
return upload_state.ProcessExec(regs.launch_dma.memory_layout.Value() ==
Regs::LaunchDMA::Layout::Pitch);
return upload_state.ProcessExec(regs.launch_dma.memory_layout.Value() == Regs::LaunchDMA::Layout::Pitch);
case MAXWELL3D_REG_INDEX(inline_data):
upload_state.ProcessData(argument, is_last_call);
return;
@@ -386,7 +386,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
return rasterizer->TiledCacheBarrier();
default:
draw_manager->ProcessMethodCall(method, argument);
draw_manager.ProcessMethodCall(*this, method, argument);
break;
}
}
@@ -401,8 +401,7 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
// Execute the current macro.
macro_engine.Execute(*this, macro_positions[entry], parameters);
draw_manager->DrawDeferred();
draw_manager.DrawDeferred(*this);
}
void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
+63 -10
View File
@@ -25,6 +25,7 @@
#include "video_core/gpu.h"
#include "video_core/macro.h"
#include "video_core/textures/texture.h"
#include "video_core/engines/maxwell_3d.h"
namespace Core {
class System;
@@ -40,8 +41,6 @@ class RasterizerInterface;
namespace Tegra::Engines {
class DrawManager;
/**
* This Engine is known as GF100_3D. Documentation can be found in:
* https://github.com/NVIDIA/open-gpu-doc/blob/master/classes/3d/clb197.h
@@ -543,7 +542,7 @@ public:
}
GPUVAddr StorageLimitAddress() const {
return (GPUVAddr{storage_limit_address_high} << 32) |
GPUVAddr{storage_limit_address_low};
GPUVAddr{storage_limit_address_low};
}
};
@@ -819,7 +818,7 @@ public:
u32 Map(std::size_t index) const {
const std::array<u32, NumRenderTargets> maps{target0, target1, target2, target3,
target4, target5, target6, target7};
target4, target5, target6, target7};
ASSERT(index < maps.size());
return maps[index];
}
@@ -1831,7 +1830,7 @@ public:
bool AnyEnabled() const {
return output0_enable || output1_enable || output2_enable || output3_enable ||
output4_enable || output5_enable || output6_enable || output7_enable;
output4_enable || output5_enable || output6_enable || output7_enable;
}
};
@@ -1870,7 +1869,7 @@ public:
bool AnyEnabled() const {
return plane0 || plane1 || plane2 || plane3 || plane4 || plane5 || plane6 ||
plane7;
plane7;
}
};
@@ -3023,8 +3022,7 @@ public:
u32 bindless_texture_const_buffer_slot; ///< 0x2608
u32 trap_handler; ///< 0x260C
INSERT_PADDING_BYTES_NOINIT(0x1F0);
std::array<std::array<StreamOutLayout, 32>, NumTransformFeedbackBuffers>
stream_out_layout; ///< 0x2800
std::array<std::array<StreamOutLayout, 32>, NumTransformFeedbackBuffers> stream_out_layout; ///< 0x2800
INSERT_PADDING_BYTES_NOINIT(0x93C);
ShaderPerformance shader_performance; ///< 0x333C
INSERT_PADDING_BYTES_NOINIT(0x18);
@@ -3035,6 +3033,62 @@ public:
};
// clang-format on
struct DrawManager {
enum class DrawMode : u32 { General = 0, Instance, InlineIndex };
struct State {
Maxwell3D::Regs::PrimitiveTopology topology{};
DrawMode draw_mode{};
bool draw_indexed{};
u32 base_index{};
Maxwell3D::Regs::VertexBuffer vertex_buffer;
Maxwell3D::Regs::IndexBuffer index_buffer;
u32 base_instance{};
u32 instance_count{};
std::vector<u8> inline_index_draw_indexes;
};
struct DrawTextureState {
f32 dst_x0;
f32 dst_y0;
f32 dst_x1;
f32 dst_y1;
f32 src_x0;
f32 src_y0;
f32 src_x1;
f32 src_y1;
u32 src_sampler;
u32 src_texture;
};
struct IndirectParams {
bool is_byte_count;
bool is_indexed;
bool include_count;
GPUVAddr count_start_address;
GPUVAddr indirect_start_address;
size_t buffer_size;
size_t max_draw_counts;
size_t stride;
};
void ProcessMethodCall(Maxwell3D& maxwell3d, u32 method, u32 argument);
void Clear(Maxwell3D& maxwell3d, u32 layer_count);
void DrawDeferred(Maxwell3D& maxwell3d);
void DrawArray(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, u32 base_instance, u32 num_instances);
void DrawArrayInstanced(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, bool subsequent);
void DrawIndex(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, u32 base_instance, u32 num_instances);
void DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology);
void DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count);
void SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index);
void DrawBegin(Maxwell3D& maxwell3d);
void DrawEnd(Maxwell3D& maxwell3d, u32 instance_count = 1, bool force_draw = false);
void DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument);
void DrawTexture(Maxwell3D& maxwell3d);
void UpdateTopology(Maxwell3D& maxwell3d);
void ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed, u32 instance_count);
void ProcessDrawIndirect(Maxwell3D& maxwell3d);
State draw_state{};
DrawTextureState draw_texture_state{};
IndirectParams indirect_state{};
};
Regs regs{};
/// Store temporary hw register values, used by some calls to restore state after a operation
@@ -3102,8 +3156,7 @@ public:
Tables tables{};
} dirty;
std::unique_ptr<DrawManager> draw_manager;
friend class DrawManager;
DrawManager draw_manager;
GPUVAddr GetMacroAddress(size_t index) const {
return macro_addresses[index];

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