Compare commits

...

18 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
120 changed files with 17378 additions and 15299 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
+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("https://{}/{}/{}",
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
+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();
});
+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,
};
}
+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
+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];
+1 -1
View File
@@ -89,7 +89,7 @@ struct GPU::Impl {
renderer = std::move(renderer_);
rasterizer = renderer->ReadRasterizer();
host1x.MemoryManager().BindInterface(rasterizer);
host1x.GMMU().BindRasterizer(rasterizer);
host1x.gmmu_manager.BindRasterizer(rasterizer);
}
/// Flush all current written commands into the host GPU for execution.
+33 -44
View File
@@ -19,56 +19,45 @@
namespace VideoCommon::GPUThread {
/// Runs the GPU thread
static void RunThread(std::stop_token stop_token, Core::System& system,
VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
Tegra::Control::Scheduler& scheduler, SynchState& state) {
Common::SetCurrentThreadName("GPU");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
system.RegisterHostThread();
auto current_context = context.Acquire();
VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
CommandDataContainer next;
while (!stop_token.stop_requested()) {
state.queue.PopWait(next, stop_token);
if (stop_token.stop_requested()) {
break;
}
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
scheduler.Push(submit_list->channel, std::move(submit_list->entries));
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
system.GPU().TickWork();
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
rasterizer->FlushRegion(flush->addr, flush->size);
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
rasterizer->OnCacheInvalidation(invalidate->addr, invalidate->size);
} else {
ASSERT(false);
}
state.signaled_fence.store(next.fence);
if (next.block) {
// We have to lock the write_lock to ensure that the condition_variable wait not get a
// race between the check and the lock itself.
std::scoped_lock lk{state.write_lock};
state.cv.notify_all();
}
}
}
ThreadManager::ThreadManager(Core::System& system_, bool is_async_)
: system{system_}, is_async{is_async_} {}
ThreadManager::~ThreadManager() = default;
void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
Core::Frontend::GraphicsContext& context,
Tegra::Control::Scheduler& scheduler) {
void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, Tegra::Control::Scheduler& scheduler) {
rasterizer = renderer.ReadRasterizer();
thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
std::ref(scheduler), std::ref(state));
thread = std::jthread([&](std::stop_token stop_token) {
Common::SetCurrentThreadName("GPU");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
system.RegisterHostThread();
auto current_context = context.Acquire();
CommandDataContainer next;
while (!stop_token.stop_requested()) {
state.queue.PopWait(next, stop_token);
if (stop_token.stop_requested()) {
break;
}
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
scheduler.Push(submit_list->channel, std::move(submit_list->entries));
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
system.GPU().TickWork();
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
renderer.ReadRasterizer()->FlushRegion(flush->addr, flush->size);
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
renderer.ReadRasterizer()->OnCacheInvalidation(invalidate->addr, invalidate->size);
} else {
ASSERT(false);
}
state.signaled_fence.store(next.fence);
if (next.block) {
// We have to lock the write_lock to ensure that the condition_variable wait not get a
// race between the check and the lock itself.
std::scoped_lock lk{state.write_lock};
state.cv.notify_all();
}
}
});
}
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
@@ -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
@@ -6,12 +9,192 @@
#include <array>
#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Tegra {
namespace Decoders {
struct Offset {
constexpr u32 Address() const noexcept {
return offset << 8;
}
private:
u32 offset;
};
static_assert(std::is_trivial_v<Offset>, "Offset must be trivial");
static_assert(sizeof(Offset) == 0x4, "Offset has the wrong size!");
struct H264ParameterSet {
s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
s32 delta_pic_order_always_zero_flag; ///< 0x04
s32 frame_mbs_only_flag; ///< 0x08
u32 pic_width_in_mbs; ///< 0x0C
u32 frame_height_in_mbs; ///< 0x10
union { ///< 0x14
BitField<0, 2, u32> tile_format;
BitField<2, 3, u32> gob_height;
BitField<5, 27, u32> reserved_surface_format;
};
u32 entropy_coding_mode_flag; ///< 0x18
s32 pic_order_present_flag; ///< 0x1C
s32 num_refidx_l0_default_active; ///< 0x20
s32 num_refidx_l1_default_active; ///< 0x24
s32 deblocking_filter_control_present_flag; ///< 0x28
s32 redundant_pic_cnt_present_flag; ///< 0x2C
u32 transform_8x8_mode_flag; ///< 0x30
u32 pitch_luma; ///< 0x34
u32 pitch_chroma; ///< 0x38
Offset luma_top_offset; ///< 0x3C
Offset luma_bot_offset; ///< 0x40
Offset luma_frame_offset; ///< 0x44
Offset chroma_top_offset; ///< 0x48
Offset chroma_bot_offset; ///< 0x4C
Offset chroma_frame_offset; ///< 0x50
u32 hist_buffer_size; ///< 0x54
union { ///< 0x58
union {
BitField<0, 1, u64> mbaff_frame;
BitField<1, 1, u64> direct_8x8_inference;
BitField<2, 1, u64> weighted_pred;
BitField<3, 1, u64> constrained_intra_pred;
BitField<4, 1, u64> ref_pic;
BitField<5, 1, u64> field_pic;
BitField<6, 1, u64> bottom_field;
BitField<7, 1, u64> second_field;
} flags;
BitField<8, 4, u64> log2_max_frame_num_minus4;
BitField<12, 2, u64> chroma_format_idc;
BitField<14, 2, u64> pic_order_cnt_type;
BitField<16, 6, s64> pic_init_qp_minus26;
BitField<22, 5, s64> chroma_qp_index_offset;
BitField<27, 5, s64> second_chroma_qp_index_offset;
BitField<32, 2, u64> weighted_bipred_idc;
BitField<34, 7, u64> curr_pic_idx;
BitField<41, 5, u64> curr_col_idx;
BitField<46, 16, u64> frame_number;
BitField<62, 1, u64> frame_surfaces;
BitField<63, 1, u64> output_memory_layout;
};
};
static_assert(sizeof(H264ParameterSet) == 0x60, "H264ParameterSet is an invalid size");
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(H264ParameterSet, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(log2_max_pic_order_cnt_lsb_minus4, 0x00);
ASSERT_POSITION(delta_pic_order_always_zero_flag, 0x04);
ASSERT_POSITION(frame_mbs_only_flag, 0x08);
ASSERT_POSITION(pic_width_in_mbs, 0x0C);
ASSERT_POSITION(frame_height_in_mbs, 0x10);
ASSERT_POSITION(tile_format, 0x14);
ASSERT_POSITION(entropy_coding_mode_flag, 0x18);
ASSERT_POSITION(pic_order_present_flag, 0x1C);
ASSERT_POSITION(num_refidx_l0_default_active, 0x20);
ASSERT_POSITION(num_refidx_l1_default_active, 0x24);
ASSERT_POSITION(deblocking_filter_control_present_flag, 0x28);
ASSERT_POSITION(redundant_pic_cnt_present_flag, 0x2C);
ASSERT_POSITION(transform_8x8_mode_flag, 0x30);
ASSERT_POSITION(pitch_luma, 0x34);
ASSERT_POSITION(pitch_chroma, 0x38);
ASSERT_POSITION(luma_top_offset, 0x3C);
ASSERT_POSITION(luma_bot_offset, 0x40);
ASSERT_POSITION(luma_frame_offset, 0x44);
ASSERT_POSITION(chroma_top_offset, 0x48);
ASSERT_POSITION(chroma_bot_offset, 0x4C);
ASSERT_POSITION(chroma_frame_offset, 0x50);
ASSERT_POSITION(hist_buffer_size, 0x54);
ASSERT_POSITION(flags, 0x58);
#undef ASSERT_POSITION
struct DpbEntry {
union {
BitField<0, 7, u32> index;
BitField<7, 5, u32> col_idx;
BitField<12, 2, u32> state;
BitField<14, 1, u32> is_long_term;
BitField<15, 1, u32> non_existing;
BitField<16, 1, u32> is_field;
BitField<17, 4, u32> top_field_marking;
BitField<21, 4, u32> bottom_field_marking;
BitField<25, 1, u32> output_memory_layout;
BitField<26, 6, u32> reserved;
} flags;
std::array<u32, 2> field_order_cnt;
u32 frame_idx;
};
static_assert(sizeof(DpbEntry) == 0x10, "DpbEntry has the wrong size!");
struct DisplayParam {
union {
BitField<0, 1, u32> enable_tf_output;
BitField<1, 1, u32> vc1_map_y_flag;
BitField<2, 3, u32> map_y_value;
BitField<5, 1, u32> vc1_map_uv_flag;
BitField<6, 3, u32> map_uv_value;
BitField<9, 8, u32> out_stride;
BitField<17, 3, u32> tiling_format;
BitField<20, 1, u32> output_structure; // 0=frame, 1=field
BitField<21, 11, u32> reserved0;
};
std::array<s32, 2> output_top;
std::array<s32, 2> output_bottom;
union {
BitField<0, 1, u32> enable_histogram;
BitField<1, 12, u32> histogram_start_x;
BitField<13, 12, u32> histogram_start_y;
BitField<25, 7, u32> reserved1;
};
union {
BitField<0, 12, u32> histogram_end_x;
BitField<12, 12, u32> histogram_end_y;
BitField<24, 8, u32> reserved2;
};
};
static_assert(sizeof(DisplayParam) == 0x1C, "DisplayParam has the wrong size!");
struct H264DecoderContext {
INSERT_PADDING_WORDS_NOINIT(13); ///< 0x0000
std::array<u8, 16> eos; ///< 0x0034
u8 explicit_eos_present_flag; ///< 0x0044
u8 hint_dump_en; ///< 0x0045
INSERT_PADDING_BYTES_NOINIT(2); ///< 0x0046
u32 stream_len; ///< 0x0048
u32 slice_count; ///< 0x004C
u32 mbhist_buffer_size; ///< 0x0050
u32 gptimer_timeout_value; ///< 0x0054
H264ParameterSet h264_parameter_set; ///< 0x0058
std::array<s32, 2> curr_field_order_cnt; ///< 0x00B8
std::array<DpbEntry, 16> dpb; ///< 0x00C0
std::array<u8, 0x60> weight_scale_4x4; ///< 0x01C0
std::array<u8, 0x80> weight_scale_8x8; ///< 0x0220
std::array<u8, 2> num_inter_view_refs_lX; ///< 0x02A0
std::array<u8, 14> reserved2; ///< 0x02A2
std::array<std::array<s8, 16>, 2> inter_view_refidx_lX; ///< 0x02B0
union { ///< 0x02D0
BitField<0, 1, u32> lossless_ipred8x8_filter_enable;
BitField<1, 1, u32> qpprime_y_zero_transform_bypass_flag;
BitField<2, 30, u32> reserved3;
};
DisplayParam display_param; ///< 0x02D4
std::array<u32, 3> reserved4; ///< 0x02F0
};
static_assert(sizeof(H264DecoderContext) == 0x2FC, "H264DecoderContext is an invalid size");
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(H264DecoderContext, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(stream_len, 0x48);
ASSERT_POSITION(h264_parameter_set, 0x58);
ASSERT_POSITION(dpb, 0xC0);
ASSERT_POSITION(weight_scale_4x4, 0x1C0);
#undef ASSERT_POSITION
enum class Vp9SurfaceIndex : u32 {
Last = 0,
Golden = 1,
@@ -287,10 +470,7 @@ struct RefPoolElement {
bool refresh{};
};
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \
"Field " #field_name " has invalid position")
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(Vp9EntropyProbs, field_name) == position)
ASSERT_POSITION(partition_prob, 0x0024);
ASSERT_POSITION(switchable_interp_prob, 0x0724);
ASSERT_POSITION(sign, 0x0772);
@@ -298,10 +478,7 @@ ASSERT_POSITION(class_0_fr, 0x079E);
ASSERT_POSITION(high_precision, 0x07B2);
#undef ASSERT_POSITION
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(PictureInfo, field_name) == position, \
"Field " #field_name " has invalid position")
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(PictureInfo, field_name) == position)
ASSERT_POSITION(bitstream_size, 0x30);
ASSERT_POSITION(last_frame_size, 0x48);
ASSERT_POSITION(first_level, 0x70);
@@ -309,9 +486,7 @@ ASSERT_POSITION(segmentation, 0x80);
ASSERT_POSITION(loop_filter, 0xE4);
#undef ASSERT_POSITION
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(EntropyProbs, field_name) == position, \
"Field " #field_name " has invalid position")
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(EntropyProbs, field_name) == position)
ASSERT_POSITION(inter_mode_prob, 0x400);
ASSERT_POSITION(tx_8x8_prob, 0x470);
@@ -321,5 +496,47 @@ ASSERT_POSITION(class_0_fr, 0x560);
ASSERT_POSITION(coef_probs, 0x5A0);
#undef ASSERT_POSITION
struct VP8PictureInfo {
INSERT_PADDING_WORDS_NOINIT(14);
u16 frame_width; // actual frame width
u16 frame_height; // actual frame height
u8 key_frame;
u8 version;
union {
u8 raw;
BitField<0, 2, u8> tile_format;
BitField<2, 3, u8> gob_height;
BitField<5, 3, u8> reserved_surface_format;
};
u8 error_conceal_on; // 1: error conceal on; 0: off
u32 first_part_size; // the size of first partition(frame header and mb header partition)
u32 hist_buffer_size; // in units of 256
u32 vld_buffer_size; // in units of 1
// Current frame buffers
std::array<u32, 2> frame_stride; // [y_c]
u32 luma_top_offset; // offset of luma top field in units of 256
u32 luma_bot_offset; // offset of luma bottom field in units of 256
u32 luma_frame_offset; // offset of luma frame in units of 256
u32 chroma_top_offset; // offset of chroma top field in units of 256
u32 chroma_bot_offset; // offset of chroma bottom field in units of 256
u32 chroma_frame_offset; // offset of chroma frame in units of 256
INSERT_PADDING_BYTES_NOINIT(0x1c); // NvdecDisplayParams
// Decode picture buffer related
s8 current_output_memory_layout;
// output NV12/NV24 setting. index 0: golden; 1: altref; 2: last
std::array<s8, 3> output_memory_layout;
u8 segmentation_feature_data_update;
INSERT_PADDING_BYTES_NOINIT(3);
// ucode return result
u32 result_value;
std::array<u32, 8> partition_offset;
INSERT_PADDING_WORDS_NOINIT(3);
};
static_assert(sizeof(VP8PictureInfo) == 0xc0, "PictureInfo is an invalid size");
}; // namespace Decoders
}; // namespace Tegra
+17 -19
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
@@ -13,9 +16,12 @@
namespace Tegra {
Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs)
: host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
: host1x(host1x_)
, state{regs}
, h264_decoder(host1x_)
, vp8_decoder(host1x_)
, vp9_decoder(host1x_)
{}
Codec::~Codec() = default;
@@ -32,13 +38,11 @@ void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) {
void Codec::Decode() {
const bool is_first_frame = !initialized;
if (is_first_frame) {
if (is_first_frame)
Initialize();
}
if (!initialized) {
if (!initialized)
return;
}
// Assemble bitstream.
bool vp9_hidden_frame = false;
@@ -46,13 +50,13 @@ void Codec::Decode() {
const auto packet_data = [&]() {
switch (current_codec) {
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
return h264_decoder->ComposeFrame(state, &configuration_size, is_first_frame);
return h264_decoder.ComposeFrame(state, &configuration_size, is_first_frame);
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
return vp8_decoder->ComposeFrame(state);
return vp8_decoder.ComposeFrame(state);
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
vp9_decoder->ComposeFrame(state);
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
return vp9_decoder->GetFrameBytes();
vp9_decoder.ComposeFrame(state);
vp9_hidden_frame = vp9_decoder.WasFrameHidden();
return vp9_decoder.GetFrameBytes();
default:
ASSERT(false);
return std::span<const u8>{};
@@ -81,19 +85,13 @@ void Codec::Decode() {
std::unique_ptr<FFmpeg::Frame> Codec::GetCurrentFrame() {
// Sometimes VIC will request more frames than have been decoded.
// in this case, return a blank frame and don't overwrite previous data.
if (frames.empty()) {
if (frames.empty())
return {};
}
auto frame = std::move(frames.front());
frames.pop();
return frame;
}
Host1x::NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
return current_codec;
}
std::string_view Codec::GetCurrentCodecName() const {
switch (current_codec) {
case Host1x::NvdecCommon::VideoCodec::None:
+10 -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
@@ -8,17 +11,14 @@
#include <string_view>
#include <queue>
#include "common/common_types.h"
#include "video_core/host1x/ffmpeg/ffmpeg.h"
#include "video_core/host1x/codecs/h264.h"
#include "video_core/host1x/codecs/vp8.h"
#include "video_core/host1x/codecs/vp9.h"
#include "video_core/host1x/ffmpeg.h"
#include "video_core/host1x/nvdec_common.h"
namespace Tegra {
namespace Decoder {
class H264;
class VP8;
class VP9;
} // namespace Decoder
namespace Host1x {
class Host1x;
} // namespace Host1x
@@ -40,9 +40,6 @@ public:
/// Returns next decoded frame
[[nodiscard]] std::unique_ptr<FFmpeg::Frame> GetCurrentFrame();
/// Returns the value of current_codec
[[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const;
/// Return name of the current codec
[[nodiscard]] std::string_view GetCurrentCodecName() const;
@@ -53,10 +50,9 @@ private:
Host1x::Host1x& host1x;
const Host1x::NvdecCommon::NvdecRegisters& state;
std::unique_ptr<Decoder::H264> h264_decoder;
std::unique_ptr<Decoder::VP8> vp8_decoder;
std::unique_ptr<Decoder::VP9> vp9_decoder;
Decoders::H264 h264_decoder;
Decoders::VP8 vp8_decoder;
Decoders::VP9 vp9_decoder;
std::queue<std::unique_ptr<FFmpeg::Frame>> frames{};
};
+12 -11
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
@@ -12,10 +12,11 @@
namespace Tegra {
Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::NvdecRegisters& regs_,
Host1x::FrameQueue& frame_queue_)
: host1x(host1x_), memory_manager{host1x.GMMU()}, regs{regs_}, id{id_}, frame_queue{
frame_queue_} {}
Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::NvdecRegisters& regs_)
: host1x(host1x_)
, regs{regs_}
, id{id_}
{}
Decoder::~Decoder() = default;
@@ -53,11 +54,11 @@ void Decoder::Decode() {
}
if (UsingDecodeOrder()) {
frame_queue.PushDecodeOrder(id, luma_top, std::move(frame));
frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy));
host1x.frame_queue.PushDecodeOrder(id, luma_top, std::move(frame));
host1x.frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy));
} else {
frame_queue.PushPresentOrder(id, luma_top, std::move(frame));
frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy));
host1x.frame_queue.PushPresentOrder(id, luma_top, std::move(frame));
host1x.frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy));
}
} else {
auto [luma_offset, chroma_offset] = GetProgressiveOffsets();
@@ -68,9 +69,9 @@ void Decoder::Decode() {
}
if (UsingDecodeOrder()) {
frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame));
host1x.frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame));
} else {
frame_queue.PushPresentOrder(id, luma_offset, std::move(frame));
host1x.frame_queue.PushPresentOrder(id, luma_offset, std::move(frame));
}
}
}
+5 -15
View File
@@ -14,7 +14,7 @@
#include <queue>
#include "common/common_types.h"
#include "video_core/host1x/ffmpeg/ffmpeg.h"
#include "video_core/host1x/ffmpeg.h"
#include "video_core/host1x/nvdec_common.h"
namespace Tegra {
@@ -35,33 +35,23 @@ public:
return decode_api.UsingDecodeOrder();
}
/// Returns the value of current_codec
[[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const {
return codec;
}
/// Return name of the current codec
[[nodiscard]] virtual std::string_view GetCurrentCodecName() const = 0;
protected:
explicit Decoder(Host1x::Host1x& host1x, s32 id,
const Host1x::NvdecCommon::NvdecRegisters& regs,
Host1x::FrameQueue& frame_queue);
explicit Decoder(Host1x::Host1x& host1x, s32 id, const Host1x::NvdecCommon::NvdecRegisters& regs);
virtual std::span<const u8> ComposeFrame() = 0;
virtual std::tuple<u64, u64> GetProgressiveOffsets() = 0;
virtual std::tuple<u64, u64, u64, u64> GetInterlacedOffsets() = 0;
virtual bool IsInterlaced() = 0;
FFmpeg::DecodeApi decode_api;
Host1x::Host1x& host1x;
Tegra::MemoryManager& memory_manager;
const Host1x::NvdecCommon::NvdecRegisters& regs;
s32 id;
Host1x::FrameQueue& frame_queue;
Host1x::NvdecCommon::VideoCodec codec;
FFmpeg::DecodeApi decode_api;
bool initialized{};
bool vp9_hidden_frame{};
bool initialized : 1 = false;
bool vp9_hidden_frame : 1 = false;
};
} // namespace Tegra
+42 -55
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
@@ -14,25 +14,11 @@
#include "video_core/memory_manager.h"
namespace Tegra::Decoders {
namespace {
// ZigZag LUTs from libavcodec.
constexpr std::array<u8, 64> zig_zag_direct{
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
};
constexpr std::array<u8, 16> zig_zag_scan{
0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4,
};
} // Anonymous namespace
H264::H264(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_,
Host1x::FrameQueue& frame_queue_)
: Decoder{host1x_, id_, regs_, frame_queue_} {
codec = Host1x::NvdecCommon::VideoCodec::H264;
initialized = decode_api.Initialize(codec);
H264::H264(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_)
: Decoder{host1x_, id_, regs_}
{
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::H264);
}
H264::~H264() = default;
@@ -65,14 +51,11 @@ bool H264::IsInterlaced() {
}
std::span<const u8> H264::ComposeFrame() {
memory_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context,
sizeof(H264DecoderContext));
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context, sizeof(H264DecoderContext));
const s64 frame_number = current_context.h264_parameter_set.frame_number.Value();
if (!is_first_frame && frame_number != 0) {
frame_scratch.resize_destructive(current_context.stream_len);
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data(),
frame_scratch.size());
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data(), frame_scratch.size());
return frame_scratch;
}
@@ -174,15 +157,13 @@ std::span<const u8> H264::ComposeFrame() {
for (s32 index = 0; index < 6; index++) {
writer.WriteBit(true);
std::span<const u8> matrix{current_context.weight_scale_4x4};
writer.WriteScalingList(scan_scratch, matrix, index * 16, 16);
writer.WriteScalingList(current_context.weight_scale_4x4, index * 16, 16);
}
if (current_context.h264_parameter_set.transform_8x8_mode_flag) {
for (s32 index = 0; index < 2; index++) {
writer.WriteBit(true);
std::span<const u8> matrix{current_context.weight_scale_8x8};
writer.WriteScalingList(scan_scratch, matrix, index * 64, 64);
writer.WriteScalingList(current_context.weight_scale_8x8, index * 64, 64);
}
}
@@ -196,11 +177,7 @@ std::span<const u8> H264::ComposeFrame() {
const auto& encoded_header = writer.GetByteArray();
frame_scratch.resize(encoded_header.size() + current_context.stream_len);
std::memcpy(frame_scratch.data(), encoded_header.data(), encoded_header.size());
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(),
frame_scratch.data() + encoded_header.size(),
current_context.stream_len);
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data() + encoded_header.size(), current_context.stream_len);
return frame_scratch;
}
@@ -229,23 +206,37 @@ void H264BitWriter::WriteBit(bool state) {
WriteBits(state ? 1 : 0, 1);
}
void H264BitWriter::WriteScalingList(Common::ScratchBuffer<u8>& scan, std::span<const u8> list,
s32 start, s32 count) {
scan.resize_destructive(count);
void H264BitWriter::WriteScalingList(std::span<const u8> list, s32 start, s32 count) {
if (count == 16) {
std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());
u8 last_scale = 8;
for (s32 index = 0; index < count; index++) {
// libavcodec has a zig zag LUT, but we dont need it, just use a magic
// constant which is a packing of 4 bits for each component of the table
const u8 value = list[start + ((0xfeb7adc963258410 >> (index * 4)) & 0xf)];
const s32 delta_scale = s32(value - last_scale);
WriteSe(delta_scale);
last_scale = value;
}
} else {
std::memcpy(scan.data(), zig_zag_direct.data(), scan.size());
}
u8 last_scale = 8;
for (s32 index = 0; index < count; index++) {
const u8 value = list[start + scan[index]];
const s32 delta_scale = static_cast<s32>(value - last_scale);
WriteSe(delta_scale);
last_scale = value;
// ZigZag LUTs from libavcodec: this is the famous zigzag pattern found in the ffmpeg logo itself!
static constexpr std::array<u8, 64> scan{
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4,
5, 12, 19, 26, 33, 40, 48,
41, 34, 27, 20, 13, 6, 7,
14, 21, 28, 35, 42, 49, 56,
57, 50, 43, 36, 29, 22, 15,
23, 30, 37, 44, 51, 58, 59,
52, 45, 38, 31, 39, 46, 53,
60, 61, 54, 47, 55, 62, 63,
};
u8 last_scale = 8;
for (s32 index = 0; index < count; index++) {
const u8 value = list[start + scan[index]];
const s32 delta_scale = s32(value - last_scale);
WriteSe(delta_scale);
last_scale = value;
}
}
}
@@ -286,19 +277,15 @@ void H264BitWriter::WriteBits(s32 value, s32 bit_count) {
void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
const s32 sign = value <= 0 ? 0 : 1;
if (value < 0) {
value = -value;
}
value = (value << 1) - sign;
WriteExpGolombCodedUInt(value);
if (!sign) value = -value;
WriteExpGolombCodedUInt((value << 1) - sign);
}
void H264BitWriter::WriteExpGolombCodedUInt(u32 value) {
const s32 size = 32 - std::countl_zero(value + 1);
WriteBits(1, size);
value -= (1U << (size - 1)) - 1;
WriteBits(static_cast<s32>(value), size - 1);
WriteBits(s32(value), size - 1);
}
s32 H264BitWriter::GetFreeBufferBits() {
+9 -187
View File
@@ -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
@@ -6,11 +9,10 @@
#include <span>
#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/scratch_buffer.h"
#include "video_core/host1x/codecs/decoder.h"
#include "video_core/host1x/codec_types.h"
#include "video_core/host1x/nvdec_common.h"
namespace Tegra {
@@ -40,8 +42,7 @@ public:
/// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification
/// Writes the scaling matrices of the sream
void WriteScalingList(Common::ScratchBuffer<u8>& scan, std::span<const u8> list, s32 start,
s32 count);
void WriteScalingList(std::span<const u8> list, s32 start, s32 count);
/// Return the bitstream as a vector.
[[nodiscard]] std::vector<u8>& GetByteArray();
@@ -61,188 +62,9 @@ private:
std::vector<u8> byte_array;
};
struct Offset {
constexpr u32 Address() const noexcept {
return offset << 8;
}
private:
u32 offset;
};
static_assert(std::is_trivial_v<Offset>, "Offset must be trivial");
static_assert(sizeof(Offset) == 0x4, "Offset has the wrong size!");
struct H264ParameterSet {
s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
s32 delta_pic_order_always_zero_flag; ///< 0x04
s32 frame_mbs_only_flag; ///< 0x08
u32 pic_width_in_mbs; ///< 0x0C
u32 frame_height_in_mbs; ///< 0x10
union { ///< 0x14
BitField<0, 2, u32> tile_format;
BitField<2, 3, u32> gob_height;
BitField<5, 27, u32> reserved_surface_format;
};
u32 entropy_coding_mode_flag; ///< 0x18
s32 pic_order_present_flag; ///< 0x1C
s32 num_refidx_l0_default_active; ///< 0x20
s32 num_refidx_l1_default_active; ///< 0x24
s32 deblocking_filter_control_present_flag; ///< 0x28
s32 redundant_pic_cnt_present_flag; ///< 0x2C
u32 transform_8x8_mode_flag; ///< 0x30
u32 pitch_luma; ///< 0x34
u32 pitch_chroma; ///< 0x38
Offset luma_top_offset; ///< 0x3C
Offset luma_bot_offset; ///< 0x40
Offset luma_frame_offset; ///< 0x44
Offset chroma_top_offset; ///< 0x48
Offset chroma_bot_offset; ///< 0x4C
Offset chroma_frame_offset; ///< 0x50
u32 hist_buffer_size; ///< 0x54
union { ///< 0x58
union {
BitField<0, 1, u64> mbaff_frame;
BitField<1, 1, u64> direct_8x8_inference;
BitField<2, 1, u64> weighted_pred;
BitField<3, 1, u64> constrained_intra_pred;
BitField<4, 1, u64> ref_pic;
BitField<5, 1, u64> field_pic;
BitField<6, 1, u64> bottom_field;
BitField<7, 1, u64> second_field;
} flags;
BitField<8, 4, u64> log2_max_frame_num_minus4;
BitField<12, 2, u64> chroma_format_idc;
BitField<14, 2, u64> pic_order_cnt_type;
BitField<16, 6, s64> pic_init_qp_minus26;
BitField<22, 5, s64> chroma_qp_index_offset;
BitField<27, 5, s64> second_chroma_qp_index_offset;
BitField<32, 2, u64> weighted_bipred_idc;
BitField<34, 7, u64> curr_pic_idx;
BitField<41, 5, u64> curr_col_idx;
BitField<46, 16, u64> frame_number;
BitField<62, 1, u64> frame_surfaces;
BitField<63, 1, u64> output_memory_layout;
};
};
static_assert(sizeof(H264ParameterSet) == 0x60, "H264ParameterSet is an invalid size");
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(H264ParameterSet, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(log2_max_pic_order_cnt_lsb_minus4, 0x00);
ASSERT_POSITION(delta_pic_order_always_zero_flag, 0x04);
ASSERT_POSITION(frame_mbs_only_flag, 0x08);
ASSERT_POSITION(pic_width_in_mbs, 0x0C);
ASSERT_POSITION(frame_height_in_mbs, 0x10);
ASSERT_POSITION(tile_format, 0x14);
ASSERT_POSITION(entropy_coding_mode_flag, 0x18);
ASSERT_POSITION(pic_order_present_flag, 0x1C);
ASSERT_POSITION(num_refidx_l0_default_active, 0x20);
ASSERT_POSITION(num_refidx_l1_default_active, 0x24);
ASSERT_POSITION(deblocking_filter_control_present_flag, 0x28);
ASSERT_POSITION(redundant_pic_cnt_present_flag, 0x2C);
ASSERT_POSITION(transform_8x8_mode_flag, 0x30);
ASSERT_POSITION(pitch_luma, 0x34);
ASSERT_POSITION(pitch_chroma, 0x38);
ASSERT_POSITION(luma_top_offset, 0x3C);
ASSERT_POSITION(luma_bot_offset, 0x40);
ASSERT_POSITION(luma_frame_offset, 0x44);
ASSERT_POSITION(chroma_top_offset, 0x48);
ASSERT_POSITION(chroma_bot_offset, 0x4C);
ASSERT_POSITION(chroma_frame_offset, 0x50);
ASSERT_POSITION(hist_buffer_size, 0x54);
ASSERT_POSITION(flags, 0x58);
#undef ASSERT_POSITION
struct DpbEntry {
union {
BitField<0, 7, u32> index;
BitField<7, 5, u32> col_idx;
BitField<12, 2, u32> state;
BitField<14, 1, u32> is_long_term;
BitField<15, 1, u32> non_existing;
BitField<16, 1, u32> is_field;
BitField<17, 4, u32> top_field_marking;
BitField<21, 4, u32> bottom_field_marking;
BitField<25, 1, u32> output_memory_layout;
BitField<26, 6, u32> reserved;
} flags;
std::array<u32, 2> field_order_cnt;
u32 frame_idx;
};
static_assert(sizeof(DpbEntry) == 0x10, "DpbEntry has the wrong size!");
struct DisplayParam {
union {
BitField<0, 1, u32> enable_tf_output;
BitField<1, 1, u32> vc1_map_y_flag;
BitField<2, 3, u32> map_y_value;
BitField<5, 1, u32> vc1_map_uv_flag;
BitField<6, 3, u32> map_uv_value;
BitField<9, 8, u32> out_stride;
BitField<17, 3, u32> tiling_format;
BitField<20, 1, u32> output_structure; // 0=frame, 1=field
BitField<21, 11, u32> reserved0;
};
std::array<s32, 2> output_top;
std::array<s32, 2> output_bottom;
union {
BitField<0, 1, u32> enable_histogram;
BitField<1, 12, u32> histogram_start_x;
BitField<13, 12, u32> histogram_start_y;
BitField<25, 7, u32> reserved1;
};
union {
BitField<0, 12, u32> histogram_end_x;
BitField<12, 12, u32> histogram_end_y;
BitField<24, 8, u32> reserved2;
};
};
static_assert(sizeof(DisplayParam) == 0x1C, "DisplayParam has the wrong size!");
struct H264DecoderContext {
INSERT_PADDING_WORDS_NOINIT(13); ///< 0x0000
std::array<u8, 16> eos; ///< 0x0034
u8 explicit_eos_present_flag; ///< 0x0044
u8 hint_dump_en; ///< 0x0045
INSERT_PADDING_BYTES_NOINIT(2); ///< 0x0046
u32 stream_len; ///< 0x0048
u32 slice_count; ///< 0x004C
u32 mbhist_buffer_size; ///< 0x0050
u32 gptimer_timeout_value; ///< 0x0054
H264ParameterSet h264_parameter_set; ///< 0x0058
std::array<s32, 2> curr_field_order_cnt; ///< 0x00B8
std::array<DpbEntry, 16> dpb; ///< 0x00C0
std::array<u8, 0x60> weight_scale_4x4; ///< 0x01C0
std::array<u8, 0x80> weight_scale_8x8; ///< 0x0220
std::array<u8, 2> num_inter_view_refs_lX; ///< 0x02A0
std::array<u8, 14> reserved2; ///< 0x02A2
std::array<std::array<s8, 16>, 2> inter_view_refidx_lX; ///< 0x02B0
union { ///< 0x02D0
BitField<0, 1, u32> lossless_ipred8x8_filter_enable;
BitField<1, 1, u32> qpprime_y_zero_transform_bypass_flag;
BitField<2, 30, u32> reserved3;
};
DisplayParam display_param; ///< 0x02D4
std::array<u32, 3> reserved4; ///< 0x02F0
};
static_assert(sizeof(H264DecoderContext) == 0x2FC, "H264DecoderContext is an invalid size");
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(H264DecoderContext, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(stream_len, 0x48);
ASSERT_POSITION(h264_parameter_set, 0x58);
ASSERT_POSITION(dpb, 0xC0);
ASSERT_POSITION(weight_scale_4x4, 0x1C0);
#undef ASSERT_POSITION
class H264 final : public Decoder {
public:
explicit H264(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
Host1x::FrameQueue& frame_queue);
explicit H264(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id);
~H264() override;
H264(const H264&) = delete;
@@ -263,10 +85,10 @@ public:
}
private:
bool is_first_frame{true};
Common::ScratchBuffer<u8> frame_scratch;
Common::ScratchBuffer<u8> scan_scratch;
H264DecoderContext current_context{};
std::array<u8, 64> scan_scratch;
Common::ScratchBuffer<u8> frame_scratch;
bool is_first_frame{true};
};
} // namespace Decoders
+23 -26
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -8,11 +11,10 @@
#include "video_core/memory_manager.h"
namespace Tegra::Decoders {
VP8::VP8(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_,
Host1x::FrameQueue& frame_queue_)
: Decoder{host1x_, id_, regs_, frame_queue_} {
codec = Host1x::NvdecCommon::VideoCodec::VP8;
initialized = decode_api.Initialize(codec);
VP8::VP8(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_)
: Decoder{host1x_, id_, regs_}
{
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::VP8);
}
VP8::~VP8() = default;
@@ -25,35 +27,30 @@ std::tuple<u64, u64> VP8::GetProgressiveOffsets() {
std::tuple<u64, u64, u64, u64> VP8::GetInterlacedOffsets() {
auto luma_top{regs.surface_luma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
auto luma_bottom{
regs.surface_luma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
auto chroma_top{
regs.surface_chroma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
auto chroma_bottom{
regs.surface_chroma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
auto luma_bottom = regs.surface_luma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
auto chroma_top = regs.surface_chroma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
auto chroma_bottom = regs.surface_chroma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
return {luma_top, luma_bottom, chroma_top, chroma_bottom};
}
std::span<const u8> VP8::ComposeFrame() {
memory_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context,
sizeof(VP8PictureInfo));
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context, sizeof(VP8PictureInfo));
const bool is_key_frame = current_context.key_frame == 1u;
const auto bitstream_size = static_cast<size_t>(current_context.vld_buffer_size);
const auto bitstream_size = size_t(current_context.vld_buffer_size);
const size_t header_size = is_key_frame ? 10u : 3u;
frame_scratch.resize(header_size + bitstream_size);
// Based on page 30 of the VP8 specification.
// https://datatracker.ietf.org/doc/rfc6386/
frame_scratch[0] = is_key_frame ? 0u : 1u; // 1-bit frame type (0: keyframe, 1: interframes).
frame_scratch[0] |=
static_cast<u8>((current_context.version & 7u) << 1u); // 3-bit version number
frame_scratch[0] |= static_cast<u8>(1u << 4u); // 1-bit show_frame flag
frame_scratch[0] |= u8((current_context.version & 7u) << 1u); // 3-bit version number
frame_scratch[0] |= u8(1u << 4u); // 1-bit show_frame flag
// The next 19-bits are the first partition size
frame_scratch[0] |= static_cast<u8>((current_context.first_part_size & 7u) << 5u);
frame_scratch[1] = static_cast<u8>((current_context.first_part_size & 0x7f8u) >> 3u);
frame_scratch[2] = static_cast<u8>((current_context.first_part_size & 0x7f800u) >> 11u);
frame_scratch[0] |= u8((current_context.first_part_size & 7u) << 5u);
frame_scratch[1] = u8((current_context.first_part_size & 0x7f8u) >> 3u);
frame_scratch[2] = u8((current_context.first_part_size & 0x7f800u) >> 11u);
if (is_key_frame) {
frame_scratch[3] = 0x9du;
@@ -61,15 +58,15 @@ std::span<const u8> VP8::ComposeFrame() {
frame_scratch[5] = 0x2au;
// TODO(ameerj): Horizontal/Vertical Scale
// 16 bits: (2 bits Horizontal Scale << 14) | Width (14 bits)
frame_scratch[6] = static_cast<u8>(current_context.frame_width & 0xff);
frame_scratch[7] = static_cast<u8>(((current_context.frame_width >> 8) & 0x3f));
frame_scratch[6] = u8(current_context.frame_width & 0xff);
frame_scratch[7] = u8(((current_context.frame_width >> 8) & 0x3f));
// 16 bits:(2 bits Vertical Scale << 14) | Height (14 bits)
frame_scratch[8] = static_cast<u8>(current_context.frame_height & 0xff);
frame_scratch[9] = static_cast<u8>(((current_context.frame_height >> 8) & 0x3f));
frame_scratch[8] = u8(current_context.frame_height & 0xff);
frame_scratch[9] = u8(((current_context.frame_height >> 8) & 0x3f));
}
const u64 bitstream_offset = regs.frame_bitstream_offset.Address();
memory_manager.ReadBlock(bitstream_offset, frame_scratch.data() + header_size, bitstream_size);
const u64 bitstream_offset = regs.frame_bitstream_offset.Address();
host1x.gmmu_manager.ReadBlock(bitstream_offset, frame_scratch.data() + header_size, bitstream_size);
return frame_scratch;
}
+6 -46
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -11,6 +14,7 @@
#include "common/scratch_buffer.h"
#include "video_core/host1x/codecs/decoder.h"
#include "video_core/host1x/nvdec_common.h"
#include "video_core/host1x/codec_types.h"
namespace Tegra {
@@ -28,8 +32,7 @@ enum class Vp8SurfaceIndex : u32 {
class VP8 final : public Decoder {
public:
explicit VP8(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
Host1x::FrameQueue& frame_queue);
explicit VP8(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id);
~VP8() override;
VP8(const VP8&) = delete;
@@ -52,51 +55,8 @@ public:
}
private:
Common::ScratchBuffer<u8> frame_scratch;
struct VP8PictureInfo {
INSERT_PADDING_WORDS_NOINIT(14);
u16 frame_width; // actual frame width
u16 frame_height; // actual frame height
u8 key_frame;
u8 version;
union {
u8 raw;
BitField<0, 2, u8> tile_format;
BitField<2, 3, u8> gob_height;
BitField<5, 3, u8> reserved_surface_format;
};
u8 error_conceal_on; // 1: error conceal on; 0: off
u32 first_part_size; // the size of first partition(frame header and mb header partition)
u32 hist_buffer_size; // in units of 256
u32 vld_buffer_size; // in units of 1
// Current frame buffers
std::array<u32, 2> frame_stride; // [y_c]
u32 luma_top_offset; // offset of luma top field in units of 256
u32 luma_bot_offset; // offset of luma bottom field in units of 256
u32 luma_frame_offset; // offset of luma frame in units of 256
u32 chroma_top_offset; // offset of chroma top field in units of 256
u32 chroma_bot_offset; // offset of chroma bottom field in units of 256
u32 chroma_frame_offset; // offset of chroma frame in units of 256
INSERT_PADDING_BYTES_NOINIT(0x1c); // NvdecDisplayParams
// Decode picture buffer related
s8 current_output_memory_layout;
// output NV12/NV24 setting. index 0: golden; 1: altref; 2: last
std::array<s8, 3> output_memory_layout;
u8 segmentation_feature_data_update;
INSERT_PADDING_BYTES_NOINIT(3);
// ucode return result
u32 result_value;
std::array<u32, 8> partition_offset;
INSERT_PADDING_WORDS_NOINIT(3);
};
static_assert(sizeof(VP8PictureInfo) == 0xc0, "PictureInfo is an invalid size");
VP8PictureInfo current_context{};
Common::ScratchBuffer<u8> frame_scratch;
};
} // namespace Decoders
+20 -63
View File
@@ -1,10 +1,11 @@
// 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 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm> // for std::copy
#include <bit>
#include <numeric>
#include "common/alignment.h"
@@ -15,11 +16,11 @@
namespace Tegra::Decoders {
namespace {
constexpr u32 diff_update_probability = 252;
constexpr u32 frame_sync_code = 0x498342;
static constexpr u32 diff_update_probability = 252;
static constexpr u32 frame_sync_code = 0x498342;
// Default compressed header probabilities once frame context resets
constexpr Vp9EntropyProbs default_probs{
static constexpr Vp9EntropyProbs default_probs{
.y_mode_prob{
65, 32, 18, 144, 162, 194, 41, 51, 98, 132, 68, 18, 165, 217, 196, 45, 40, 78,
173, 80, 19, 176, 240, 193, 64, 35, 46, 221, 135, 38, 194, 248, 121, 96, 85, 29,
@@ -158,34 +159,6 @@ constexpr Vp9EntropyProbs default_probs{
.high_precision{128, 128},
};
constexpr std::array<u8, 256> norm_lut{
0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
constexpr std::array<u8, 254> map_lut{
20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 4, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 5, 86, 87, 88, 89,
90, 91, 92, 93, 94, 95, 96, 97, 6, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
108, 109, 7, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 8, 122, 123, 124,
125, 126, 127, 128, 129, 130, 131, 132, 133, 9, 134, 135, 136, 137, 138, 139, 140, 141, 142,
143, 144, 145, 10, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 11, 158, 159,
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, 177,
178, 179, 180, 181, 13, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 14, 194,
195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, 208, 209, 210, 211, 212,
213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 17,
230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 18, 242, 243, 244, 245, 246, 247,
248, 249, 250, 251, 252, 253, 19,
};
// 6.2.14 Tile size calculation
[[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) {
@@ -227,25 +200,17 @@ constexpr std::array<u8, 254> map_lut{
[[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) {
new_prob--;
old_prob--;
std::size_t index{};
if (old_prob * 2 <= 0xff) {
index = static_cast<std::size_t>((std::max)(0, RecenterNonNeg(new_prob, old_prob) - 1));
} else {
index = static_cast<std::size_t>(
(std::max)(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
}
return static_cast<s32>(map_lut[index]);
u8 i = old_prob * 2 <= 0xff
? u8((std::max)(0, RecenterNonNeg(new_prob, old_prob) - 1))
: u8((std::max)(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
return s32((i + 7) % 13 == 0 ? (i + 7) / 13 - 1 : i + 20 - (i + 7) / 13);
}
} // Anonymous namespace
VP9::VP9(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_,
Host1x::FrameQueue& frame_queue_)
: Decoder{host1x_, id_, regs_, frame_queue_} {
codec = Host1x::NvdecCommon::VideoCodec::VP9;
initialized = decode_api.Initialize(codec);
VP9::VP9(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_)
: Decoder{host1x_, id_, regs_}
{
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::VP9);
}
VP9::~VP9() = default;
@@ -377,8 +342,7 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
if (update_map) {
EntropyProbs entropy_probs{};
memory_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs,
sizeof(entropy_probs));
host1x.gmmu_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs, sizeof(entropy_probs));
auto WriteProb = [&](u8 prob) {
bool coded = prob != 255;
@@ -442,8 +406,7 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
}
Vp9PictureInfo VP9::GetVp9PictureInfo() {
memory_manager.ReadBlock(regs.picture_info_offset.Address(), &current_picture_info,
sizeof(PictureInfo));
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), &current_picture_info, sizeof(PictureInfo));
Vp9PictureInfo vp9_info = current_picture_info.Convert();
InsertEntropy(regs.vp9_prob_tab_buffer_offset.Address(), vp9_info.entropy);
@@ -459,7 +422,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo() {
void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
EntropyProbs entropy;
memory_manager.ReadBlock(offset, &entropy, sizeof(EntropyProbs));
host1x.gmmu_manager.ReadBlock(offset, &entropy, sizeof(EntropyProbs));
entropy.Convert(dst);
}
@@ -469,9 +432,7 @@ Vp9FrameContainer VP9::GetCurrentFrame() {
// gpu.SyncGuestHost(); epic, why?
current_frame.info = GetVp9PictureInfo();
current_frame.bit_stream.resize(current_frame.info.bitstream_size);
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(),
current_frame.bit_stream.data(),
current_frame.info.bitstream_size);
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), current_frame.bit_stream.data(), current_frame.info.bitstream_size);
}
if (!next_frame.bit_stream.empty()) {
Vp9FrameContainer temp{
@@ -893,13 +854,9 @@ std::span<const u8> VP9::ComposeFrame() {
// Write headers and frame to buffer
frame_scratch.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size());
std::copy(uncompressed_header.begin(), uncompressed_header.end(), frame_scratch.begin());
std::copy(compressed_header.begin(), compressed_header.end(),
frame_scratch.begin() + uncompressed_header.size());
std::copy(bitstream.begin(), bitstream.end(),
frame_scratch.begin() + uncompressed_header.size() + compressed_header.size());
std::copy(compressed_header.begin(), compressed_header.end(), frame_scratch.begin() + uncompressed_header.size());
std::copy(bitstream.begin(), bitstream.end(), frame_scratch.begin() + uncompressed_header.size() + compressed_header.size());
vp9_hidden_frame = WasFrameHidden();
return GetFrameBytes();
}
@@ -929,7 +886,7 @@ void VpxRangeEncoder::Write(bool bit, s32 probability) {
local_range = range - split;
}
s32 shift = static_cast<s32>(norm_lut[local_range]);
s32 shift = s32(local_range == 0 ? 0 : (std::countl_zero<uint32_t>(local_range) - 24));
local_range <<= shift;
count += shift;
+6 -5
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
@@ -11,7 +14,7 @@
#include "common/scratch_buffer.h"
#include "common/stream.h"
#include "video_core/host1x/codecs/decoder.h"
#include "video_core/host1x/codecs/vp9_types.h"
#include "video_core/host1x/codec_types.h"
#include "video_core/host1x/nvdec_common.h"
namespace Tegra {
@@ -113,8 +116,7 @@ private:
class VP9 final : public Decoder {
public:
explicit VP9(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
Host1x::FrameQueue& frame_queue);
explicit VP9(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id);
~VP9() override;
VP9(const VP9&) = delete;
@@ -193,11 +195,10 @@ private:
[[nodiscard]] std::vector<u8> ComposeCompressedHeader();
[[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
Common::ScratchBuffer<u8> frame_scratch;
std::array<s8, 4> loop_filter_ref_deltas{};
std::array<s8, 2> loop_filter_mode_deltas{};
Common::ScratchBuffer<u8> frame_scratch;
Vp9FrameContainer next_frame{};
std::array<Vp9EntropyProbs, 4> frame_ctxs{};
bool swap_ref_indices{};
@@ -9,7 +9,7 @@
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/memory.h"
#include "video_core/host1x/ffmpeg/ffmpeg.h"
#include "video_core/host1x/ffmpeg.h"
#include "video_core/memory_manager.h"
extern "C" {
@@ -34,7 +34,9 @@ constexpr std::array PreferredGpuDecoders = {
AV_HWDEVICE_TYPE_DXVA2,
AV_HWDEVICE_TYPE_D3D12VA,
#elif defined(__FreeBSD__)
AV_HWDEVICE_TYPE_VAAPI,
AV_HWDEVICE_TYPE_VDPAU,
AV_HWDEVICE_TYPE_DRM,
#elif defined(__APPLE__)
AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
#elif defined(ANDROID)
@@ -105,18 +107,17 @@ Frame::~Frame() {
Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
const AVCodecID av_codec = [&] {
switch (codec) {
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
return AV_CODEC_ID_H264;
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
return AV_CODEC_ID_VP8;
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
return AV_CODEC_ID_VP9;
default:
UNIMPLEMENTED_MSG("Unknown codec {}", codec);
return AV_CODEC_ID_NONE;
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
return AV_CODEC_ID_H264;
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
return AV_CODEC_ID_VP8;
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
return AV_CODEC_ID_VP9;
default:
UNIMPLEMENTED_MSG("Unknown codec {}", codec);
return AV_CODEC_ID_NONE;
}
}();
m_codec = avcodec_find_decoder(av_codec);
}
@@ -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
+9 -6
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: 2021 yuzu Emulator Project
@@ -12,9 +12,12 @@
namespace Tegra::Host1x {
Host1x::Host1x(Core::System& system_)
: system{system_}, syncpoint_manager{},
memory_manager(system.DeviceMemory()), gmmu_manager{system, memory_manager, 32, 0, 12},
allocator{std::make_unique<Common::FlatAllocator<u32, 0, 32>>(1 << 12)} {}
: system{system_}
, syncpoint_manager{}
, memory_manager(system.DeviceMemory())
, gmmu_manager{system, memory_manager, 32, 0, 12}
, allocator{1 << 12}
{}
Host1x::~Host1x() = default;
@@ -24,13 +27,13 @@ void Host1x::StartDevice(s32 fd, ChannelType type, u32 syncpt) {
#ifdef YUZU_LEGACY
std::call_once(nvdec_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer
#endif
devices[fd] = std::make_unique<Tegra::Host1x::Nvdec>(*this, fd, syncpt, frame_queue);
devices[fd] = std::make_unique<Tegra::Host1x::Nvdec>(*this, fd, syncpt);
break;
case ChannelType::VIC:
#ifdef YUZU_LEGACY
std::call_once(vic_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer
#endif
devices[fd] = std::make_unique<Tegra::Host1x::Vic>(*this, fd, syncpt, frame_queue);
devices[fd] = std::make_unique<Tegra::Host1x::Vic>(*this, fd, syncpt);
break;
default:
LOG_ERROR(HW_GPU, "Unimplemented host1x device {}", static_cast<u32>(type));
+3 -12
View File
@@ -184,20 +184,12 @@ public:
return memory_manager;
}
Tegra::MemoryManager& GMMU() {
return gmmu_manager;
}
const Tegra::MemoryManager& GMMU() const {
return gmmu_manager;
}
Common::FlatAllocator<u32, 0, 32>& Allocator() {
return *allocator;
return allocator;
}
const Common::FlatAllocator<u32, 0, 32>& Allocator() const {
return *allocator;
return allocator;
}
void StartDevice(s32 fd, ChannelType type, u32 syncpt);
@@ -211,12 +203,11 @@ public:
it->second->PushEntries(std::move(entries));
}
private:
Core::System& system;
SyncpointManager syncpoint_manager;
Tegra::MaxwellDeviceMemoryManager memory_manager;
Tegra::MemoryManager gmmu_manager;
std::unique_ptr<Common::FlatAllocator<u32, 0, 32>> allocator;
Common::FlatAllocator<u32, 0, 32> allocator;
FrameQueue frame_queue;
ankerl::unordered_dense::map<s32, std::unique_ptr<CDmaPusher>> devices;
#ifdef YUZU_LEGACY
+31 -30
View File
@@ -1,9 +1,10 @@
// 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 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <variant>
#include "common/assert.h"
#include "common/polyfill_thread.h"
@@ -19,10 +20,13 @@ namespace Tegra::Host1x {
#define NVDEC_REG_INDEX(field_name) \
(offsetof(NvdecCommon::NvdecRegisters, field_name) / sizeof(u64))
Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt, FrameQueue& frame_queue_)
: CDmaPusher{host1x_, id_}, id{id_}, syncpoint{syncpt}, frame_queue{frame_queue_} {
Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt)
: CDmaPusher{host1x_, id_}
, id{id_}
, syncpoint{syncpt}
{
LOG_INFO(HW_GPU, "Created nvdec {}", id);
frame_queue.Open(id);
host1x.frame_queue.Open(id);
}
Nvdec::~Nvdec() {
@@ -43,24 +47,22 @@ void Nvdec::ProcessMethod(u32 method, u32 argument) {
}
void Nvdec::CreateDecoder(NvdecCommon::VideoCodec codec) {
if (decoder.get()) {
return;
if (std::holds_alternative<std::monostate>(decoder)) {
switch (codec) {
case NvdecCommon::VideoCodec::H264:
decoder.emplace<Decoders::H264>(host1x, regs, id);
break;
case NvdecCommon::VideoCodec::VP8:
decoder.emplace<Decoders::VP8>(host1x, regs, id);
break;
case NvdecCommon::VideoCodec::VP9:
decoder.emplace<Decoders::VP9>(host1x, regs, id);
break;
default:
break;
}
LOG_INFO(HW_GPU, "Created decoder {} for id {}", codec, id);
}
switch (codec) {
case NvdecCommon::VideoCodec::H264:
decoder = std::make_unique<Decoders::H264>(host1x, regs, id, frame_queue);
break;
case NvdecCommon::VideoCodec::VP8:
decoder = std::make_unique<Decoders::VP8>(host1x, regs, id, frame_queue);
break;
case NvdecCommon::VideoCodec::VP9:
decoder = std::make_unique<Decoders::VP9>(host1x, regs, id, frame_queue);
break;
default:
UNIMPLEMENTED_MSG("Codec {}", decoder->GetCurrentCodecName());
break;
}
LOG_INFO(HW_GPU, "Created decoder {} for id {}", decoder->GetCurrentCodecName(), id);
}
void Nvdec::Execute() {
@@ -70,15 +72,14 @@ void Nvdec::Execute() {
std::this_thread::sleep_for(std::chrono::milliseconds(8));
return;
}
switch (decoder->GetCurrentCodec()) {
case NvdecCommon::VideoCodec::H264:
case NvdecCommon::VideoCodec::VP8:
case NvdecCommon::VideoCodec::VP9:
decoder->Decode();
break;
default:
UNIMPLEMENTED_MSG("Codec {}", decoder->GetCurrentCodecName());
break;
if (auto* h264 = std::get_if<Decoders::H264>(&decoder)) {
h264->Decode();
} else if (auto* vp8 = std::get_if<Decoders::VP8>(&decoder)) {
vp8->Decode();
} else if (auto* vp9 = std::get_if<Decoders::VP9>(&decoder)) {
vp9->Decode();
} else {
LOG_ERROR(HW_GPU, "Unrecognized codec executed?");
}
}

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