Compare commits

..

2 Commits

Author SHA1 Message Date
lizzie ade32609df license ffs 2026-04-26 09:20:11 +00:00
lizzie 8f64430215 [video_core] simplify InvalidationAccumulator
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-04-26 09:19:30 +00:00
119 changed files with 15331 additions and 17470 deletions
+7 -2
View File
@@ -268,6 +268,11 @@ if (NOT EXISTS ${PROJECT_BINARY_DIR}/${compat_json})
file(WRITE ${PROJECT_BINARY_DIR}/${compat_json} "") file(WRITE ${PROJECT_BINARY_DIR}/${compat_json} "")
endif() 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)) if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
set(HAS_NCE 1) set(HAS_NCE 1)
add_compile_definitions(HAS_NCE=1) add_compile_definitions(HAS_NCE=1)
@@ -419,10 +424,10 @@ if (zstd_ADDED)
add_library(zstd::libzstd ALIAS libzstd_static) add_library(zstd::libzstd ALIAS libzstd_static)
endif() endif()
if (NOT YUZU_STATIC_ROOM)
# nlohmann # nlohmann
AddJsonPackage(nlohmann) AddJsonPackage(nlohmann)
if (NOT YUZU_STATIC_ROOM)
# zlib # zlib
AddJsonPackage(zlib) AddJsonPackage(zlib)
@@ -480,7 +485,7 @@ endfunction()
# ============================================= # =============================================
if (APPLE) if (APPLE)
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security) foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia)
find_library(${fw}_LIBRARY ${fw} REQUIRED) find_library(${fw}_LIBRARY ${fw} REQUIRED)
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY}) list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
endforeach() endforeach()
+2 -4
View File
@@ -33,11 +33,9 @@ endif()
set(GIT_DESC ${BUILD_VERSION}) set(GIT_DESC ${BUILD_VERSION})
# Generate cpp with Git revision from template # 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
# TODO(crueter): Stable releases feed. # Auto-updater metadata! Must somewhat mirror GitHub/Forgejo API endpoint
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") set(BUILD_AUTO_UPDATE_API_PATH "/latest/release.json")
if (NIGHTLY_BUILD) if (NIGHTLY_BUILD)
+498 -566
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+500 -568
View File
File diff suppressed because it is too large Load Diff
+572 -644
View File
File diff suppressed because it is too large Load Diff
+504 -571
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+504 -571
View File
File diff suppressed because it is too large Load Diff
+504 -572
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+496 -566
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+497 -564
View File
File diff suppressed because it is too large Load Diff
+504 -571
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+503 -572
View File
File diff suppressed because it is too large Load Diff
+498 -565
View File
File diff suppressed because it is too large Load Diff
+502 -572
View File
File diff suppressed because it is too large Load Diff
+498 -566
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+501 -571
View File
File diff suppressed because it is too large Load Diff
+505 -576
View File
File diff suppressed because it is too large Load Diff
+503 -571
View File
File diff suppressed because it is too large Load Diff
+4 -30
View File
@@ -4,8 +4,7 @@
- [Arch Linux](#arch-linux) - [Arch Linux](#arch-linux)
- [Gentoo Linux](#gentoo-linux) - [Gentoo Linux](#gentoo-linux)
- [macOS](#macos) - [macOS](#macos)
- [OpenIndiana](#openindiana) - [Solaris](#solaris)
- [OmniOS](#omnios)
- [HaikuOS](#haikuos) - [HaikuOS](#haikuos)
- [OpenBSD](#openbsd) - [OpenBSD](#openbsd)
- [FreeBSD](#freebsd) - [FreeBSD](#freebsd)
@@ -32,14 +31,14 @@ If you're having issues with building, always consult that ebuild.
macOS is largely untested. Expect crashes, significant Vulkan issues, and other fun stuff. macOS is largely untested. Expect crashes, significant Vulkan issues, and other fun stuff.
## OpenIndiana ## Solaris
Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability. Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability.
Run the usual update + install of essential toolings: `sudo pkg update && sudo pkg install git cmake`. Run the usual update + install of essential toolings: `sudo pkg update && sudo pkg install git cmake`.
- **gcc**: Install either `developer/gcc-14`. - **gcc**: `sudo pkg install developer/gcc-14`.
- **clang**: Version 20 is broken, install `developer/clang-19`. - **clang**: Version 20 is broken, use `sudo pkg install developer/clang-19`.
Qt Widgets appears to be broken. For now, add `-DENABLE_QT=OFF` to your configure command. In the meantime, a Qt Quick frontend is in the works--check back later! Qt Widgets appears to be broken. For now, add `-DENABLE_QT=OFF` to your configure command. In the meantime, a Qt Quick frontend is in the works--check back later!
@@ -68,31 +67,6 @@ export LIBGL_ALWAYS_SOFTWARE=1
- If using OpenIndiana, due to a bug in SDL2's CMake configuration, audio driver defaults to SunOS `<sys/audioio.h>`, which does not exist on OpenIndiana. Using external or bundled SDL2 may solve this. - If using OpenIndiana, due to a bug in SDL2's CMake configuration, audio driver defaults to SunOS `<sys/audioio.h>`, which does not exist on OpenIndiana. Using external or bundled SDL2 may solve this.
- System OpenSSL generally does not work. Instead, use `-DYUZU_USE_BUNDLED_OPENSSL=ON` to use a bundled static OpenSSL, or build a system dependency from source. - System OpenSSL generally does not work. Instead, use `-DYUZU_USE_BUNDLED_OPENSSL=ON` to use a bundled static OpenSSL, or build a system dependency from source.
## OmniOS
Install `developer/gcc14` on OmniOS using pkgsrc.
Since so many dependencies are missing on `OmniOS`, you may wish to use `-DCPMUTIL_FORCE_BUNDLED=ON -DYUZU_USE_EXTERNAL_SDL2=ON`
For OmniOS you are required to build glslang yourself:
```sh
sudo pkg install python-313
git clone --depth=1 https://github.com/KhronosGroup/glslang.git
cd glslang
python3.13 ./update_glslang_sources.py
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -- -j `nproc`
cmake --install build
```
It may be tempting to specify `-t glslang`, but this will cause installation to fail. So don't.
Using `--parallel` on CMake incorrectly passes `dmake ... -jn` instead of `dmake ... -j n`, this is a bug with OmniOS's CMake, and as such it's recommended to not use this option until it's fixed.
You may also need to install `gmake` in order to properly build FFmpeg, this is provided by the `build-essential` package.
If it wasn't obvious already, you require a X11 server to properly run the emulator within OmniOS, [this guide](https://web.archive.org/web/20260424200928/https://geekblood.wordpress.com/2017/10/26/installing-x11-and-a-desktop-environment-on-omnios/) is a great starting point for that, the links to pkgsrc are outdated so follow [this exemplar](https://pkgsrc.smartos.org/install-on-illumos/) as well:
## HaikuOS ## HaikuOS
It's recommended to do a `pkgman full-sync` before installing. See [HaikuOS: Installing applications](https://www.haiku-os.org/guides/daily-tasks/install-applications/). Sometimes the process may be interrupted by an error like "Interrupted syscall". Simply firing the command again fixes the issue. By default `g++` is included on the default installation. It's recommended to do a `pkgman full-sync` before installing. See [HaikuOS: Installing applications](https://www.haiku-os.org/guides/daily-tasks/install-applications/). Sometimes the process may be interrupted by an error like "Interrupted syscall". Simply firing the command again fixes the issue. By default `g++` is included on the default installation.
+3 -13
View File
@@ -291,23 +291,13 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak
</details> </details>
<details> <details>
<summary>OpenIndiana</summary> <summary>Solaris / OpenIndiana</summary>
```sh ```sh
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt sudo pkg install qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
``` ```
[Caveats](./Caveats.md#openindiana). [Caveats](./Caveats.md#solaris).
</details>
<details>
<summary>OmniOS</summary>
```sh
sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
```
[Caveats](./Caveats.md#omnios).
</details> </details>
<details> <details>
-10
View File
@@ -25,16 +25,6 @@ if (NIGHTLY_BUILD)
add_compile_definitions(NIGHTLY_BUILD) add_compile_definitions(NIGHTLY_BUILD)
endif() 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 # Set compilation flags
if (MSVC AND NOT CXX_CLANG) if (MSVC AND NOT CXX_CLANG)
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
-6
View File
@@ -203,12 +203,6 @@ android {
resValue("string", "app_name_suffixed", "Eden Optimized") resValue("string", "app_name_suffixed", "Eden Optimized")
applicationId = "com.miHoYo.Yuanshen" applicationId = "com.miHoYo.Yuanshen"
externalNativeBuild {
cmake {
arguments.add("-DGENSHIN_SPOOF=ON")
}
}
ndk { ndk {
abiFilters += listOf("arm64-v8a") abiFilters += listOf("arm64-v8a")
} }
@@ -33,19 +33,6 @@ import org.yuzu.yuzu_emu.applets.web.WebBrowser
* with the native side of the Yuzu code. * with the native side of the Yuzu code.
*/ */
object NativeLibrary { 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 @JvmField
var sEmulationActivity = WeakReference<EmulationActivity?>(null) var sEmulationActivity = WeakReference<EmulationActivity?>(null)
@@ -253,7 +240,17 @@ object NativeLibrary {
/** /**
* Checks for available updates. * Checks for available updates.
*/ */
external fun checkForUpdate(): UpdateResult? 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
/** /**
* Returns whether the update checker is enabled through CMAKE options. * Returns whether the update checker is enabled through CMAKE options.
@@ -175,25 +175,25 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val latestVersion = NativeLibrary.checkForUpdate() val latestVersion = NativeLibrary.checkForUpdate()
if (latestVersion != null) { if (latestVersion != null) {
runOnUiThread { runOnUiThread {
showUpdateDialog(latestVersion) val tag: String = latestVersion[0]
val name: String = latestVersion[1]
showUpdateDialog(tag, name)
} }
} }
}.start() }.start()
} }
// TODO(crueter): body, "View on Forgejo" button private fun showUpdateDialog(tag: String, name: String) {
private fun showUpdateDialog(release: NativeLibrary.UpdateResult) {
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(R.string.update_available) .setTitle(R.string.update_available)
.setMessage(getString(R.string.update_available_description, release.title)) .setMessage(getString(R.string.update_available_description, name))
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
val assets = release.assets var artifact = tag
// Nightly builds have a slightly different format
if (assets.isEmpty()) { if (NativeLibrary.isNightlyBuild()) {
openLink(release.url) artifact = tag.substringAfter('.', tag)
} else {
downloadAndInstallUpdate(release)
} }
downloadAndInstallUpdate(tag, artifact)
} }
.setNeutralButton(R.string.cancel) { dialog, _ -> .setNeutralButton(R.string.cancel) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
@@ -206,23 +206,17 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
.show() .show()
} }
private fun openLink(link: String) { private fun downloadAndInstallUpdate(version: String, artifact: String) {
val intent = Intent(Intent.ACTION_VIEW, link.toUri())
startActivity(intent)
}
private fun downloadAndInstallUpdate(release: NativeLibrary.UpdateResult) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val packageId = applicationContext.packageName val packageId = applicationContext.packageName
val asset = release.assets[0] val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
val artifact = asset.split("/").last()
val apkFile = File(cacheDir, "update-$artifact.apk") val apkFile = File(cacheDir, "update-$artifact.apk")
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
showDownloadProgressDialog() showDownloadProgressDialog()
} }
val downloader = APKDownloader(asset, apkFile) val downloader = APKDownloader(apkUrl, apkFile)
downloader.download( downloader.download(
onProgress = { progress -> onProgress = { progress ->
runOnUiThread { runOnUiThread {
@@ -254,7 +248,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} else { } else {
Toast.makeText( Toast.makeText(
this@MainActivity, this@MainActivity,
getString(R.string.update_download_failed) + "\n\nURL: $asset", getString(R.string.update_download_failed) + "\n\nURL: $apkUrl",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
} }
@@ -283,7 +277,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private fun updateDownloadProgress(progress: Int) { private fun updateDownloadProgress(progress: Int) {
progressBar?.progress = progress progressBar?.progress = progress
progressMessage?.text = getString(R.string.percent, progress) progressMessage?.text = "$progress%"
} }
private fun dismissDownloadProgressDialog() { 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 #ifdef ENABLE_UPDATE_CHECKER
JNIEXPORT jobject JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate( JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
JNIEnv* env, JNIEnv* env,
jobject obj) { jobject obj) {
std::optional<Common::Net::Release> release = UpdateChecker::GetUpdate(); std::optional<UpdateChecker::Update> release = UpdateChecker::GetUpdate();
if (!release) return nullptr; if (!release) return nullptr;
const std::string tag = release->tag; const std::string tag = release->tag;
const std::string title = release->title; const std::string name = release->name;
const std::string body = release->body;
const std::string url = release->html_url;
// Android *should* only ever define a single asset. jobjectArray result = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr);
// If not, something has gone wrong, but the Kotlin side can handle it.
const auto assets = release->GetPlatformAssets();
jclass updateResultClass = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary$UpdateResult"); const jstring jtag = env->NewStringUTF(tag.c_str());
if (!updateResultClass) { const jstring jname = env->NewStringUTF(name.c_str());
LOG_ERROR(Frontend, "Could not find UpdateResult class");
return nullptr;
}
jmethodID updateResultCtor = env->GetMethodID(updateResultClass, "<init>", "()V");
if (!updateResultCtor) {
LOG_ERROR(Frontend, "Could not find UpdateResult ctor");
env->DeleteLocalRef(updateResultClass);
return nullptr;
}
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->SetObjectArrayElement(result, 0, jtag);
env->SetObjectArrayElement(result, 1, jname);
env->DeleteLocalRef(jtag); env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jtitle); env->DeleteLocalRef(jname);
env->DeleteLocalRef(jbody);
env->DeleteLocalRef(jurl);
env->DeleteLocalRef(updateResultClass); return result;
return updateResult;
} }
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
}
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", artifact_str, variant);
const std::string url = fmt::format("{}/{}/{}",
std::string{Common::g_build_auto_update_api},
version_str, apk_filename);
env->ReleaseStringUTFChars(tag, version_str);
env->ReleaseStringUTFChars(artifact, artifact_str);
env->ReleaseStringUTFChars(packageId, package_id_str);
return env->NewStringUTF(url.c_str());
}
#endif #endif
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getBuildVersion( JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getBuildVersion(
@@ -1159,6 +1159,5 @@
<string name="license_fidelityfx_fsr_description">تحسين الجودة بدرجة عالية من AMD</string> <string name="license_fidelityfx_fsr_description">تحسين الجودة بدرجة عالية من AMD</string>
<string name="external_content">محتوى خارجي</string> <string name="external_content">محتوى خارجي</string>
<string name="add_folders">إضافة مجلد</string> <string name="add_folders">إضافة مجلد</string>
<string name="percent">%1$d%%</string>
</resources> </resources>
@@ -767,7 +767,7 @@
<string name="version">Versión</string> <string name="version">Versión</string>
<string name="copy_details">Copiar detalles</string> <string name="copy_details">Copiar detalles</string>
<string name="add_ons">Complementos</string> <string name="add_ons">Complementos</string>
<string name="add_ons_description">Alternar mods, actualizaciones y contenido descargable</string> <string name="add_ons_description">Activa/desactiva mods, actualizaciones y contenidos descargables</string>
<string name="playtime">Tiempo jugado:</string> <string name="playtime">Tiempo jugado:</string>
<string name="reset_playtime">Borrar tiempo de juego</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> <string name="reset_playtime_description">Restablecer el tiempo de juego actual a 0 segundos</string>
@@ -1153,6 +1153,5 @@
<string name="license_fidelityfx_fsr_description">Upscaling de alta calidad de AMD</string> <string name="license_fidelityfx_fsr_description">Upscaling de alta calidad de AMD</string>
<string name="external_content">Contenido externo</string> <string name="external_content">Contenido externo</string>
<string name="add_folders">Añadir carpeta</string> <string name="add_folders">Añadir carpeta</string>
<string name="percent">%1$d%%</string>
</resources> </resources>
@@ -1150,4 +1150,5 @@
<string name="license_fidelityfx_fsr_description">Высококачественное масштабирование от AMD</string> <string name="license_fidelityfx_fsr_description">Высококачественное масштабирование от AMD</string>
<string name="external_content">Дополнительный контент</string> <string name="external_content">Дополнительный контент</string>
<string name="add_folders">Добавить папку</string> <string name="add_folders">Добавить папку</string>
</resources> </resources>
@@ -1155,6 +1155,5 @@
<string name="license_fidelityfx_fsr_description">Високоякісне масштабування від AMD</string> <string name="license_fidelityfx_fsr_description">Високоякісне масштабування від AMD</string>
<string name="external_content">Зовнішній вміст</string> <string name="external_content">Зовнішній вміст</string>
<string name="add_folders">Додати теку</string> <string name="add_folders">Додати теку</string>
<string name="percent">%1$d%%</string>
</resources> </resources>
@@ -483,8 +483,6 @@
<string name="enable_buffer_history">启用缓冲区历史</string> <string name="enable_buffer_history">启用缓冲区历史</string>
<string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string> <string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string>
<string name="use_optimized_vertex_buffers">优化顶点缓冲区</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="hacks">Hacks</string>
<string name="fast_gpu_time">GPU 超频频率</string> <string name="fast_gpu_time">GPU 超频频率</string>
@@ -492,9 +490,6 @@
<string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string> <string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string>
<string name="skip_cpu_inner_invalidation_description">在内存更新期间跳过某些CPU端缓存无效化,减少CPU使用率并提高其性能。可能会导致某些游戏出现故障或崩溃。</string> <string name="skip_cpu_inner_invalidation_description">在内存更新期间跳过某些CPU端缓存无效化,减少CPU使用率并提高其性能。可能会导致某些游戏出现故障或崩溃。</string>
<string name="fix_bloom_effects">修复 Bloom 效果</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">使用异步着色器</string>
<string name="renderer_asynchronous_shaders_description">异步编译着色器。这可能会减少卡顿,但也可能会导致图形错误。</string> <string name="renderer_asynchronous_shaders_description">异步编译着色器。这可能会减少卡顿,但也可能会导致图形错误。</string>
<string name="gpu_unswizzle_settings">GPU 还原设置</string> <string name="gpu_unswizzle_settings">GPU 还原设置</string>
@@ -1149,6 +1144,5 @@
<string name="license_fidelityfx_fsr_description">AMD 的高品质画面增强技术</string> <string name="license_fidelityfx_fsr_description">AMD 的高品质画面增强技术</string>
<string name="external_content">外部内容</string> <string name="external_content">外部内容</string>
<string name="add_folders">添加文件夹</string> <string name="add_folders">添加文件夹</string>
<string name="percent">%1$d%%</string>
</resources> </resources>
@@ -1783,6 +1783,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<string name="external_content">External Content</string> <string name="external_content">External Content</string>
<string name="add_folders">Add Folder</string> <string name="add_folders">Add Folder</string>
<string name="percent">%1$d%%</string>
</resources> </resources>
+2 -3
View File
@@ -147,8 +147,7 @@ add_library(
zstd_compression.h zstd_compression.h
fs/ryujinx_compat.h fs/ryujinx_compat.cpp fs/ryujinx_compat.h fs/ryujinx_compat.cpp
fs/symlink.h fs/symlink.cpp fs/symlink.h fs/symlink.cpp
httplib.h httplib.h)
net/net.h net/net.cpp)
if(WIN32) if(WIN32)
target_sources(common PRIVATE windows/timer_resolution.cpp target_sources(common PRIVATE windows/timer_resolution.cpp
@@ -246,7 +245,7 @@ else()
target_link_libraries(common PUBLIC Boost::headers) target_link_libraries(common PUBLIC Boost::headers)
endif() endif()
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json) target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib)
if (lz4_ADDED) if (lz4_ADDED)
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
+25 -14
View File
@@ -13,11 +13,11 @@
namespace Common { namespace Common {
template <typename BaseAddr> template <typename BaseAddr>
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_, std::size_t first_level_bits_, std::size_t page_bits_) MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
: address_space_bits{address_space_bits_} std::size_t first_level_bits_,
, first_level_bits{first_level_bits_} std::size_t page_bits_)
, page_bits{page_bits_} : address_space_bits{address_space_bits_},
{ first_level_bits{first_level_bits_}, page_bits{page_bits_} {
if (page_bits == 0) { if (page_bits == 0) {
return; return;
} }
@@ -30,9 +30,12 @@ MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bit
void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)}; void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
#else #else
void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)}; 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; base = nullptr;
}
#endif #endif
ASSERT(base); ASSERT(base);
base_ptr = reinterpret_cast<BaseAddr*>(base); base_ptr = reinterpret_cast<BaseAddr*>(base);
} }
@@ -53,21 +56,29 @@ template <typename BaseAddr>
void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) { void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
const u64 new_start = start >> first_level_shift; const u64 new_start = start >> first_level_shift;
const u64 new_end = (start + size) >> first_level_shift; const u64 new_end = (start + size) >> first_level_shift;
for (u64 i = new_start; i <= new_end; i++) for (u64 i = new_start; i <= new_end; i++) {
if (!first_level_map[i]) if (!first_level_map[i]) {
AllocateLevel(i); AllocateLevel(i);
} }
}
}
template <typename BaseAddr> template <typename BaseAddr>
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 index) { void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
void* ptr = reinterpret_cast<char *>(base_ptr) + index * first_level_chunk_size; void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
#ifdef _WIN32 #ifdef _WIN32
void* base = VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE); void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
ASSERT(base);
#else #else
void* base = ptr; 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 #endif
first_level_map[index] = base; ASSERT(base);
first_level_map[level] = base;
} }
} // namespace Common } // namespace Common
-287
View File
@@ -1,287 +0,0 @@
// 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
@@ -1,56 +0,0 @@
// 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();
}
+4 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -142,8 +142,10 @@ struct PageTable {
VirtualBuffer<PageEntryData> entries; VirtualBuffer<PageEntryData> entries;
static_assert(sizeof(PageEntryData) == 32); static_assert(sizeof(PageEntryData) == 32);
u8* fastmem_arena{};
std::size_t current_address_space_width_in_bits{}; std::size_t current_address_space_width_in_bits{};
u8* fastmem_arena{};
std::size_t page_size{}; std::size_t page_size{};
}; };
-6
View File
@@ -22,9 +22,6 @@
#define BUILD_AUTO_UPDATE_API "@BUILD_AUTO_UPDATE_API@" #define BUILD_AUTO_UPDATE_API "@BUILD_AUTO_UPDATE_API@"
#define BUILD_AUTO_UPDATE_API_PATH "@BUILD_AUTO_UPDATE_API_PATH@" #define BUILD_AUTO_UPDATE_API_PATH "@BUILD_AUTO_UPDATE_API_PATH@"
#define BUILD_AUTO_UPDATE_REPO "@BUILD_AUTO_UPDATE_REPO@" #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@ #define IS_NIGHTLY_BUILD @IS_NIGHTLY_BUILD@
namespace Common { namespace Common {
@@ -48,8 +45,5 @@ 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[] = 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_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_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 } // namespace Common
-3
View File
@@ -28,8 +28,5 @@ extern const char g_build_auto_update_website[];
extern const char g_build_auto_update_api[]; extern const char g_build_auto_update_api[];
extern const char g_build_auto_update_api_path[]; extern const char g_build_auto_update_api_path[];
extern const char g_build_auto_update_repo[]; 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 } // namespace Common
+15 -16
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -24,9 +21,7 @@ public:
// "with the current allocator"); // "with the current allocator");
constexpr VirtualBuffer() = default; constexpr VirtualBuffer() = default;
explicit VirtualBuffer(std::size_t count) noexcept explicit VirtualBuffer(std::size_t count) : alloc_size{count * sizeof(T)} {
: alloc_size{count * sizeof(T)}
{
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size)); base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
} }
@@ -38,8 +33,8 @@ public:
VirtualBuffer& operator=(const VirtualBuffer&) = delete; VirtualBuffer& operator=(const VirtualBuffer&) = delete;
VirtualBuffer(VirtualBuffer&& other) noexcept 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 { VirtualBuffer& operator=(VirtualBuffer&& other) noexcept {
alloc_size = std::exchange(other.alloc_size, 0); alloc_size = std::exchange(other.alloc_size, 0);
@@ -47,31 +42,35 @@ public:
return *this; return *this;
} }
void resize(std::size_t count) noexcept { void resize(std::size_t count) {
if (auto const new_size = count * sizeof(T); new_size != alloc_size) { const auto new_size = count * sizeof(T);
if (new_size == alloc_size) {
return;
}
FreeMemoryPages(base_ptr, alloc_size); FreeMemoryPages(base_ptr, alloc_size);
alloc_size = new_size; alloc_size = new_size;
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size)); base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
} }
}
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept { [[nodiscard]] constexpr const T& operator[](std::size_t index) const {
return base_ptr[index]; return base_ptr[index];
} }
[[nodiscard]] constexpr T& operator[](std::size_t index) noexcept { [[nodiscard]] constexpr T& operator[](std::size_t index) {
return base_ptr[index]; return base_ptr[index];
} }
[[nodiscard]] constexpr T* data() noexcept { [[nodiscard]] constexpr T* data() {
return base_ptr; return base_ptr;
} }
[[nodiscard]] constexpr const T* data() const noexcept { [[nodiscard]] constexpr const T* data() const {
return base_ptr; return base_ptr;
} }
[[nodiscard]] constexpr std::size_t size() const noexcept { [[nodiscard]] constexpr std::size_t size() const {
return alloc_size / sizeof(T); return alloc_size / sizeof(T);
} }
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // 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="r11" bitsize="32" type="uint32"/>
<reg name="r12" bitsize="32" type="uint32"/> <reg name="r12" bitsize="32" type="uint32"/>
<reg name="sp" bitsize="32" type="data_ptr"/> <reg name="sp" bitsize="32" type="data_ptr"/>
<reg name="lr" bitsize="32"/> <reg name="lr" bitsize="32" type="code_ptr"/>
<reg name="pc" bitsize="32" type="code_ptr"/> <reg name="pc" bitsize="32" type="code_ptr"/>
<!-- The CPSR is register 25, rather than register 16, because <!-- The CPSR is register 25, rather than register 16, because
the FPA registers historically were placed between the PC the FPA registers historically were placed between the PC
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // 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); R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
// Check that our range is valid. // Check that our range is valid.
BucketTree::Offsets table_offsets{}; BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); 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(offset, BlockSize));
ASSERT(Common::IsAligned(size, 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(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets))));
ASSERT(table_offsets.IsInclude(offset, size)); ASSERT(table_offsets.IsInclude(offset, size));
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -92,7 +89,7 @@ public:
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
virtual size_t GetSize() const override { virtual size_t GetSize() const override {
BucketTree::Offsets offsets{}; BucketTree::Offsets offsets;
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets)))); ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets))));
return offsets.end_offset; return offsets.end_offset;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // 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(virtual_address >= 0, ResultInvalidOffset);
R_UNLESS(!this->IsEmpty(), ResultOutOfRange); R_UNLESS(!this->IsEmpty(), ResultOutOfRange);
BucketTree::Offsets offsets{}; BucketTree::Offsets offsets;
R_TRY(this->GetOffsets(std::addressof(offsets))); R_TRY(this->GetOffsets(std::addressof(offsets)));
R_TRY(visitor->Initialize(this, offsets)); R_TRY(visitor->Initialize(this, offsets));
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -125,7 +125,7 @@ private:
} }
// Get the table offsets. // Get the table offsets.
BucketTree::Offsets table_offsets{}; BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments. // Validate arguments.
@@ -177,7 +177,7 @@ private:
ASSERT(out != nullptr); ASSERT(out != nullptr);
// Get our table offsets. // Get our table offsets.
BucketTree::Offsets offsets{}; BucketTree::Offsets offsets;
R_TRY(m_table.GetOffsets(std::addressof(offsets))); R_TRY(m_table.GetOffsets(std::addressof(offsets)));
// Set the output. // Set the output.
@@ -195,7 +195,7 @@ private:
R_SUCCEED_IF(size == 0); R_SUCCEED_IF(size == 0);
// Get the table offsets. // Get the table offsets.
BucketTree::Offsets table_offsets{}; BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments. // Validate arguments.
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -55,7 +52,7 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
// Check that our range is valid. // Check that our range is valid.
BucketTree::Offsets table_offsets{}; BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -167,7 +164,7 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
R_SUCCEED_IF(size == 0); R_SUCCEED_IF(size == 0);
// Get the table offsets. // Get the table offsets.
BucketTree::Offsets table_offsets{}; BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments. // Validate arguments.
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -21,7 +18,7 @@ size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const {
SparseStorage* self = const_cast<SparseStorage*>(this); SparseStorage* self = const_cast<SparseStorage*>(this);
if (self->GetEntryTable().IsEmpty()) { if (self->GetEntryTable().IsEmpty()) {
BucketTree::Offsets table_offsets{}; BucketTree::Offsets table_offsets;
ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets)))); ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets))));
ASSERT(table_offsets.IsInclude(offset, size)); ASSERT(table_offsets.IsInclude(offset, size));
+32 -16
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -191,7 +191,8 @@ Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start
m_cached_physical_heap_region = nullptr; m_cached_physical_heap_region = nullptr;
// Initialize our implementation. // Initialize our implementation.
m_impl.Resize(m_address_space_width, PageBits); m_impl = std::make_unique<Common::PageTable>();
m_impl->Resize(m_address_space_width, PageBits);
// Set the tracking memory. // Set the tracking memory.
m_memory = std::addressof(memory); m_memory = std::addressof(memory);
@@ -201,7 +202,13 @@ Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start
m_memory_block_slab_manager)); 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. // Calculate region extents.
const size_t as_width = GetAddressSpaceWidth(as_type); const size_t as_width = GetAddressSpaceWidth(as_type);
const KProcessAddress start = 0; const KProcessAddress start = 0;
@@ -312,10 +319,14 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
// Determine random placements for each region. // Determine random placements for each region.
size_t alias_rnd = 0, heap_rnd = 0, stack_rnd = 0, kmap_rnd = 0; size_t alias_rnd = 0, heap_rnd = 0, stack_rnd = 0, kmap_rnd = 0;
if (enable_aslr) { if (enable_aslr) {
alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment; alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment; RegionAlignment;
stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment; heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * 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. // Setup heap and alias regions.
@@ -434,13 +445,15 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
ASSERT(heap_last < kmap_start || kmap_last < heap_start); ASSERT(heap_last < kmap_start || kmap_last < heap_start);
// Initialize our implementation. // Initialize our implementation.
m_impl.Resize(m_address_space_width, PageBits); m_impl = std::make_unique<Common::PageTable>();
m_impl->Resize(m_address_space_width, PageBits);
// Set the tracking memory. // Set the tracking memory.
m_memory = std::addressof(memory); m_memory = std::addressof(memory);
// Initialize our memory block manager. // 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() { Result KPageTableBase::FinalizeProcess() {
@@ -463,7 +476,7 @@ void KPageTableBase::Finalize() {
this->FinalizeProcess(); this->FinalizeProcess();
auto BlockCallback = [&](KProcessAddress addr, u64 size) { 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); m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
} }
@@ -501,6 +514,9 @@ void KPageTableBase::Finalize() {
m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
m_mapped_ipc_server_memory); 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 { KProcessAddress KPageTableBase::GetRegionAddress(Svc::MemoryState state) const {
@@ -2333,7 +2349,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
TraversalContext context; TraversalContext context;
TraversalEntry next_entry; TraversalEntry next_entry;
bool traverse_valid = 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); R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
// Set tracking variables. // Set tracking variables.
@@ -2344,7 +2360,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
while (true) { while (true) {
// Continue the traversal. // Continue the traversal.
traverse_valid = 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) { if (!traverse_valid) {
break; break;
} }
@@ -5714,14 +5730,14 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
this->MakePageGroup(pages_to_close, virt_addr, num_pages); this->MakePageGroup(pages_to_close, virt_addr, num_pages);
// Unmap. // 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(); R_SUCCEED();
} }
case OperationType::Map: { case OperationType::Map: {
ASSERT(virt_addr != 0); ASSERT(virt_addr != 0);
ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize)); 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); ConvertToMemoryPermission(properties.perm), false);
// Open references to pages, if we should. // Open references to pages, if we should.
@@ -5738,7 +5754,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
case OperationType::ChangePermissions: case OperationType::ChangePermissions:
case OperationType::ChangePermissionsAndRefresh: case OperationType::ChangePermissionsAndRefresh:
case OperationType::ChangePermissionsAndRefreshAndFlush: { 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)); ConvertToMemoryPermission(properties.perm));
R_SUCCEED(); R_SUCCEED();
} }
@@ -5772,7 +5788,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
const size_t size{node.GetNumPages() * PageSize}; const size_t size{node.GetNumPages() * PageSize};
// Map the pages. // 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); ConvertToMemoryPermission(properties.perm), separate_heap);
virt_addr += size; virt_addr += size;
+22 -7
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -215,7 +215,7 @@ private:
mutable KLightLock m_general_lock; mutable KLightLock m_general_lock;
mutable KLightLock m_map_physical_memory_lock; mutable KLightLock m_map_physical_memory_lock;
KLightLock m_device_map_lock; KLightLock m_device_map_lock;
Common::PageTable m_impl{}; std::unique_ptr<Common::PageTable> m_impl{};
Core::Memory::Memory* m_memory{}; Core::Memory::Memory* m_memory{};
KMemoryBlockManager m_memory_block_manager{}; KMemoryBlockManager m_memory_block_manager{};
u32 m_allocate_option{}; u32 m_allocate_option{};
@@ -300,11 +300,26 @@ public:
} }
public: public:
[[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return *m_memory; } Core::Memory::Memory& GetMemory() {
[[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return *m_memory; } 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; } 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;
}
protected: protected:
// NOTE: These three functions (Operate, Operate, FinalizeUpdate) are virtual functions // NOTE: These three functions (Operate, Operate, FinalizeUpdate) are virtual functions
// in Nintendo's kernel. We devirtualize them, since KPageTable is the only derived // in Nintendo's kernel. We devirtualize them, since KPageTable is the only derived
+16 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -33,10 +33,21 @@ public:
m_page_table.Finalize(); m_page_table.Finalize();
} }
[[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return m_page_table.GetMemory(); } Core::Memory::Memory& GetMemory() {
[[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return m_page_table.GetMemory(); } 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(); }
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();
}
size_t GetNumGuardPages() const { size_t GetNumGuardPages() const {
return m_page_table.GetNumGuardPages(); return m_page_table.GetNumGuardPages();
+5 -23
View File
@@ -50,14 +50,6 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
}; };
const auto ExitContext = [&]() { 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. // Unlock the thread.
interface->UnlockThread(thread); interface->UnlockThread(thread);
@@ -105,16 +97,11 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
} }
// Determine why we stopped. // Determine why we stopped.
// If a step completed successfully, skip other halt reason handlers — const bool supervisor_call = True(hr & Core::HaltReason::SupervisorCall);
// the step takes priority (e.g. step may also set InstructionBreakpoint const bool prefetch_abort = True(hr & Core::HaltReason::PrefetchAbort);
// if the next instruction happens to be a breakpoint). const bool breakpoint = True(hr & Core::HaltReason::InstructionBreakpoint);
const bool step_completed = True(hr & Core::HaltReason::StepThread) const bool data_abort = True(hr & Core::HaltReason::DataAbort);
&& thread->GetStepState() == StepState::StepPerformed; const bool interrupt = True(hr & Core::HaltReason::BreakLoop);
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 // Since scheduling may occur here, we cannot use any cached
// state after returning from calls we make. // state after returning from calls we make.
@@ -124,11 +111,6 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
if (breakpoint || prefetch_abort) { if (breakpoint || prefetch_abort) {
if (breakpoint) { if (breakpoint) {
interface->RewindBreakpointInstruction(); 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()) { if (system.DebuggerEnabled()) {
system.GetDebugger().NotifyThreadStopped(thread); system.GetDebugger().NotifyThreadStopped(thread);
+128 -31
View File
@@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // 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/builtin_news.h"
#include "core/hle/service/bcat/news/msgpack.h" #include "core/hle/service/bcat/news/msgpack.h"
#include "core/hle/service/bcat/news/news_storage.h" #include "core/hle/service/bcat/news/news_storage.h"
@@ -24,8 +22,10 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <future> #include <future>
#include <iomanip>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <sstream>
#include <thread> #include <thread>
#ifdef YUZU_BUNDLED_OPENSSL #ifdef YUZU_BUNDLED_OPENSSL
@@ -35,6 +35,9 @@
namespace Service::News { namespace Service::News {
namespace { namespace {
// TODO(crueter): COMPILE DEFINITION
constexpr const char* GitHubAPI_EdenReleases = "/api/v1/repos/eden-emu/eden/releases";
// Cached logo data // Cached logo data
std::vector<u8> default_logo_small; std::vector<u8> default_logo_small;
std::vector<u8> default_logo_large; std::vector<u8> default_logo_large;
@@ -63,6 +66,24 @@ u32 HashToNewsId(std::string_view key) {
return static_cast<u32>(std::hash<std::string_view>{}(key) & 0x7FFFFFFF); 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) { std::vector<u8> TryLoadFromDisk(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) return {}; if (!std::filesystem::exists(path)) return {};
@@ -79,9 +100,8 @@ std::vector<u8> TryLoadFromDisk(const std::filesystem::path& path) {
return data; return data;
} }
// TODO(crueter): Migrate to use Common::Net
std::vector<u8> DownloadImage(const std::string& url_path, const std::filesystem::path& cache_path) { std::vector<u8> DownloadImage(const std::string& url_path, const std::filesystem::path& cache_path) {
LOG_DEBUG(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path); LOG_INFO(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path);
try { try {
httplib::Client cli("https://eden-emu.dev"); httplib::Client cli("https://eden-emu.dev");
cli.set_follow_location(true); cli.set_follow_location(true);
@@ -206,6 +226,67 @@ void WriteCachedJson(std::string_view json) {
(void)Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, 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. // idk but News App does not render Markdown or HTML, so remove some formatting.
std::string SanitizeMarkdown(std::string_view markdown) { std::string SanitizeMarkdown(std::string_view markdown) {
std::string result; std::string result;
@@ -261,7 +342,9 @@ std::string SanitizeMarkdown(std::string_view markdown) {
return text; return text;
} }
std::string FormatBody(std::string body, const std::string_view &title) { std::string FormatBody(const nlohmann::json& release, std::string_view title) {
std::string body = release.value("body", std::string{});
if (body.empty()) { if (body.empty()) {
return std::string(title); return std::string(title);
} }
@@ -292,32 +375,52 @@ std::string FormatBody(std::string body, const std::string_view &title) {
return body; return body;
} }
void ImportReleases(const std::vector<Common::Net::Release> &releases) { 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;
std::vector<u32> news_ids; std::vector<u32> news_ids;
for (const auto& rel : releases) { for (const auto& rel : root) {
const u32 news_id = u32(rel.id & 0x7FFFFFFF); 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);
news_ids.push_back(news_id); news_ids.push_back(news_id);
} }
PreloadNewsImages(news_ids); PreloadNewsImages(news_ids);
for (const auto& rel : releases) { for (const auto& rel : root) {
const std::string title = rel.title; if (!rel.is_object()) continue;
const std::string body = rel.body;
const std::string html_url = rel.html_url;
const u32 news_id = u32(rel.id & 0x7FFFFFFF); std::string title = rel.value("name", rel.value("tag_name", std::string{}));
const u64 published = rel.published; 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 u64 pickup_limit = published + 600000000; const u64 pickup_limit = published + 600000000;
const u32 priority = rel.prerelease ? 1500 : 2500; const u32 priority = rel.value("prerelease", false) ? 1500 : 2500;
std::string author = "Eden"; std::string author = "eden";
if (rel.contains("author") && rel["author"].is_object()) {
author = rel["author"].value("login", "eden");
}
auto payload = BuildMsgpack(title, FormatBody(body, title), title, published, auto payload = BuildMsgpack(title, FormatBody(rel, title), title, published,
pickup_limit, priority, {"en"}, author, {}, pickup_limit, priority, {"en"}, author, {},
html_url, news_id); rel.value("html_url", std::string{}), news_id);
const std::string news_id_str = fmt::format("LA{:020}", rel.id); const std::string news_id_str = fmt::format("LA{:020}", news_id);
GithubNewsMeta meta{ GithubNewsMeta meta{
.news_id = news_id_str, .news_id = news_id_str,
@@ -462,21 +565,15 @@ void EnsureBuiltinNewsLoaded() {
LoadDefaultLogos(); LoadDefaultLogos();
if (const auto cached = ReadCachedJson()) { if (const auto cached = ReadCachedJson()) {
const std::string_view body = cached.value(); ImportReleases(*cached);
const auto releases = Common::Net::Release::ListFromJson(body, Common::g_build_auto_update_stable_api, Common::g_build_auto_update_stable_repo); LOG_DEBUG(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size());
ImportReleases(releases);
LOG_INFO(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size());
} }
std::thread([] { std::thread([] {
if (const auto fresh = Common::Net::GetReleasesBody()) { if (const auto fresh = DownloadReleasesJson()) {
const std::string_view body = fresh.value(); WriteCachedJson(*fresh);
WriteCachedJson(body); ImportReleases(*fresh);
const auto releases = Common::Net::Release::ListFromJson(body, Common::g_build_auto_update_stable_api, Common::g_build_auto_update_stable_repo); LOG_DEBUG(Service_BCAT, "news: {} entries updated from Forgejo", NewsStorage::Instance().ListAll().size());
ImportReleases(releases);
LOG_INFO(Service_BCAT, "news: {} entries updated from Forgejo", NewsStorage::Instance().ListAll().size());
} }
}).detach(); }).detach();
}); });
+7 -3
View File
@@ -89,7 +89,7 @@ void NvMap::UnmapHandle(Handle& handle_description) {
// Free and unmap the handle from Host1x GMMU // Free and unmap the handle from Host1x GMMU
if (handle_description.pin_virt_address) { if (handle_description.pin_virt_address) {
host1x.gmmu_manager.Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address), host1x.GMMU().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
handle_description.aligned_size); handle_description.aligned_size);
host1x.Allocator().Free(handle_description.pin_virt_address, host1x.Allocator().Free(handle_description.pin_virt_address,
static_cast<u32>(handle_description.aligned_size)); static_cast<u32>(handle_description.aligned_size));
@@ -169,8 +169,12 @@ DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
std::scoped_lock lock(handle_description->mutex); std::scoped_lock lock(handle_description->mutex);
const auto map_low_area = [&] { const auto map_low_area = [&] {
if (handle_description->pin_virt_address == 0) { if (handle_description->pin_virt_address == 0) {
u32 address = host1x.Allocator().Allocate(u32(handle_description->aligned_size)); auto& gmmu_allocator = host1x.Allocator();
host1x.gmmu_manager.Map(GPUVAddr(address), handle_description->d_address, handle_description->aligned_size); 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);
handle_description->pin_virt_address = address; handle_description->pin_virt_address = address;
} }
}; };
@@ -10,7 +10,6 @@
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h"
#include "common/logging.h" #include "common/logging.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/service/nvdrv/core/container.h" #include "core/hle/service/nvdrv/core/container.h"
@@ -131,12 +130,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 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)}; const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
vm.small_page_allocator.emplace(start_pages, end_pages); vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)}; const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
const auto end_big_pages{ const auto end_big_pages{
static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)}; static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
vm.big_page_allocator.emplace(start_big_pages, end_big_pages); vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split, gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split,
vm.big_page_size_bits, VM::PAGE_SIZE_BITS); vm.big_page_size_bits, VM::PAGE_SIZE_BITS);
@@ -189,8 +188,8 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
} }
allocation_map[params.offset] = { allocation_map[params.offset] = {
.mappings{},
.size = size, .size = size,
.mappings{},
.page_size = params.page_size, .page_size = params.page_size,
.sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None, .sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
.big_pages = params.page_size != VM::YUZU_PAGESIZE, .big_pages = params.page_size != VM::YUZU_PAGESIZE,
@@ -200,54 +199,73 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
} }
void nvhost_as_gpu::FreeMappingLocked(u64 offset) { void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
auto const it = mapping_map.find(offset); auto mapping{mapping_map.at(offset)};
auto const mapping = it->second;
if (!mapping.fixed) { if (!mapping->fixed) {
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; 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_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}; u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
u64 aligned_size{Common::AlignUp(mapping.size, page_size)}; u64 aligned_size{Common::AlignUp(mapping->size, page_size)};
allocator.Free(u32(mapping.offset >> page_size_bits), u32(aligned_size >> page_size_bits));
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
static_cast<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 // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully // Only FreeSpace can unmap them fully
if (mapping.sparse_alloc) { if (mapping->sparse_alloc) {
gmmu->MapSparse(offset, mapping.size, mapping.big_page); gmmu->MapSparse(offset, mapping->size, mapping->big_page);
} else { } else {
gmmu->Unmap(offset, mapping.size); gmmu->Unmap(offset, mapping->size);
} }
mapping_map.erase(it);
mapping_map.erase(offset);
} }
NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) { 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); std::scoped_lock lock(mutex);
if (!vm.initialised) { if (!vm.initialised) {
return NvResult::BadValue; return NvResult::BadValue;
} }
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_offset : allocation.mappings) try {
FreeMappingLocked(mapping_offset); auto allocation{allocation_map[params.offset]};
if (allocation.page_size != params.page_size ||
allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
return NvResult::BadValue;
}
for (const auto& mapping : allocation.mappings) {
FreeMappingLocked(mapping->offset);
}
// Unset sparse flag if required // Unset sparse flag if required
if (allocation.sparse) if (allocation.sparse) {
gmmu->Unmap(params.offset, allocation.size); 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};
allocator.Free(u32(params.offset >> page_size_bits), u32(allocation.size >> page_size_bits));
allocation_map.erase(params.offset);
return NvResult::Success;
} }
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));
allocation_map.erase(params.offset);
} catch (const std::out_of_range&) {
return NvResult::BadValue; return NvResult::BadValue;
} }
return NvResult::Success;
}
NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) { NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) {
LOG_DEBUG(Service_NVDRV, "called, num_entries={:#X}", entries.size()); LOG_DEBUG(Service_NVDRV, "called, num_entries={:#X}", entries.size());
@@ -309,18 +327,26 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
// Remaps a subregion of an existing mapping to a different PA // Remaps a subregion of an existing mapping to a different PA
if ((params.flags & MappingFlags::Remap) != MappingFlags::None) { if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
if (auto const it = mapping_map.find(params.offset); it != mapping_map.end()) { try {
auto const mapping = it->second; 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 (mapping->size < params.mapping_size) {
LOG_WARNING(Service_NVDRV,
"Cannot remap a partially mapped GPU address space region: {:#X}",
params.offset);
return NvResult::BadValue; return NvResult::BadValue;
} }
u64 gpu_address = u64(params.offset + params.buffer_offset);
VAddr device_address{mapping.ptr + params.buffer_offset}; u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
gmmu->Map(gpu_address, device_address, params.mapping_size, Tegra::PTEKind(params.kind), mapping.big_page); 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);
return NvResult::Success; return NvResult::Success;
} else { } catch (const std::out_of_range&) {
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}", params.offset); LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}",
params.offset);
return NvResult::BadValue; return NvResult::BadValue;
} }
} }
@@ -330,7 +356,8 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
return NvResult::BadValue; return NvResult::BadValue;
} }
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset); DAddr device_address{
static_cast<DAddr>(nvmap.PinHandle(params.handle, false) + params.buffer_offset)};
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size}; u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
bool big_page{[&]() { bool big_page{[&]() {
@@ -354,22 +381,32 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
} }
const bool use_big_pages = alloc->second.big_pages && big_page; 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);
alloc->second.mappings.push_back(params.offset); auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, true, use_big_pages, alloc->second.sparse)); true, use_big_pages, alloc->second.sparse)};
alloc->second.mappings.push_back(mapping);
mapping_map[params.offset] = mapping;
} else { } else {
auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; 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{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
params.offset = u64(allocator.Allocate(u32(Common::AlignUp(size, page_size) >> page_size_bits))) << page_size_bits; params.offset = static_cast<u64>(allocator.Allocate(
static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
<< page_size_bits;
if (!params.offset) { if (!params.offset) {
ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!"); ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
return NvResult::InsufficientMemory; return NvResult::InsufficientMemory;
} }
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)); 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;
} }
map_buffer_offsets.insert(params.offset); map_buffer_offsets.insert(params.offset);
@@ -378,32 +415,37 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
} }
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) { NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
std::scoped_lock lock(mutex); if (map_buffer_offsets.find(params.offset) != map_buffer_offsets.end()) {
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); LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
std::scoped_lock lock(mutex);
if (!vm.initialised) { if (!vm.initialised) {
return NvResult::BadValue; return NvResult::BadValue;
} }
auto const it = mapping_map.find(params.offset); auto mapping{mapping_map.at(params.offset)};
auto const mapping = it->second;
if (!mapping.fixed) { if (!mapping->fixed) {
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; 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_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));
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
static_cast<u32>(mapping->size >> page_size_bits));
} }
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully // Only FreeSpace can unmap them fully
if (mapping.sparse_alloc) { if (mapping->sparse_alloc) {
gmmu->MapSparse(params.offset, mapping.size, mapping.big_page); gmmu->MapSparse(params.offset, mapping->size, mapping->big_page);
} else { } else {
gmmu->Unmap(params.offset, mapping.size); gmmu->Unmap(params.offset, mapping->size);
} }
nvmap.UnpinHandle(mapping.handle); nvmap.UnpinHandle(mapping->handle);
mapping_map.erase(it);
map_buffer_offsets.erase(offset_it); mapping_map.erase(params.offset);
map_buffer_offsets.erase(params.offset);
} }
return NvResult::Success; return NvResult::Success;
} }
@@ -436,7 +478,8 @@ void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
} }
NvResult nvhost_as_gpu::GetVARegions1(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); std::scoped_lock lock(mutex);
@@ -165,35 +165,34 @@ private:
NvCore::NvMap& nvmap; NvCore::NvMap& nvmap;
struct Mapping { struct Mapping {
NvCore::NvMap::Handle::Id handle;
DAddr ptr; DAddr ptr;
u64 offset; u64 offset;
u64 size; u64 size;
NvCore::NvMap::Handle::Id handle; bool fixed;
bool fixed : 1; bool big_page; // Only valid if fixed == false
bool big_page : 1; // Only valid if fixed == false bool sparse_alloc;
bool sparse_alloc : 1;
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_) Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_,
: ptr(ptr_), offset(offset_), size(size_), handle(handle_) bool big_page_, bool sparse_alloc_)
, fixed(fixed_), big_page(big_page_), sparse_alloc(sparse_alloc_) : handle(handle_), ptr(ptr_), offset(offset_), size(size_), fixed(fixed_),
{} big_page(big_page_), sparse_alloc(sparse_alloc_) {}
}; };
struct Allocation { struct Allocation {
std::vector<u64> mappings;
u64 size; u64 size;
std::list<std::shared_ptr<Mapping>> mappings;
u32 page_size; u32 page_size;
bool sparse; bool sparse;
bool big_pages; bool big_pages;
}; };
//!< This maps the base addresses of mapped buffers to their total sizes and 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 //!< 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. //!< have been split into multiple GPU side buffers with the remap flag.
std::map<u64, Mapping> mapping_map; std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
//!< Holds allocations created by AllocSpace from
//!< which fixed buffers can be mapped into //!< which fixed buffers can be mapped into
std::map<u64, Allocation> allocation_map;
std::mutex mutex; //!< Locks all AS operations std::mutex mutex; //!< Locks all AS operations
struct VM { struct VM {
@@ -214,8 +213,9 @@ private:
using Allocator = Common::FlatAllocator<u32, 0, 32>; using Allocator = Common::FlatAllocator<u32, 0, 32>;
std::optional<Allocator> big_page_allocator; std::unique_ptr<Allocator> big_page_allocator;
std::optional<Allocator> small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel std::shared_ptr<Allocator>
small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
bool initialised{}; bool initialised{};
} vm; } vm;
@@ -228,8 +228,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
code_size += patch_ctx.GetTotalPatchSize(); code_size += patch_ctx.GetTotalPatchSize();
// TODO: this is bad form of ASLR, it sucks // TODO: this is bad form of ASLR, it sucks
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue() size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000; ? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
// Setup the process code layout // Setup the process code layout
if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) { if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) {
+3 -2
View File
@@ -89,8 +89,9 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
codeset.DataSegment().size += kip->GetBSSSize(); codeset.DataSegment().size += kip->GetBSSSize();
// TODO: this is bad form of ASLR, it sucks // TODO: this is bad form of ASLR, it sucks
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue() size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000; ? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
// Setup the process code layout // Setup the process code layout
if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) { if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) {
+3 -2
View File
@@ -242,8 +242,9 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
}(); }();
// TODO: this is bad form of ASLR, it sucks // TODO: this is bad form of ASLR, it sucks
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue() size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000; ? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
// Setup the process code layout // Setup the process code layout
if (process if (process
@@ -866,10 +866,10 @@ void EmitIR<IR::Opcode::VectorMaxS32>(oaknut::CodeGenerator& code, EmitContext&
template<> template<>
void EmitIR<IR::Opcode::VectorMaxS64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::VectorMaxS64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) { (void)code;
code.CMGT(Qresult->D2(), Qa->D2(), Qb->D2()); (void)ctx;
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16()); (void)inst;
}); UNREACHABLE();
} }
template<> template<>
@@ -889,10 +889,10 @@ void EmitIR<IR::Opcode::VectorMaxU32>(oaknut::CodeGenerator& code, EmitContext&
template<> template<>
void EmitIR<IR::Opcode::VectorMaxU64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::VectorMaxU64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) { (void)code;
code.CMHI(Qresult->D2(), Qa->D2(), Qb->D2()); (void)ctx;
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16()); (void)inst;
}); UNREACHABLE();
} }
template<> template<>
@@ -912,10 +912,10 @@ void EmitIR<IR::Opcode::VectorMinS32>(oaknut::CodeGenerator& code, EmitContext&
template<> template<>
void EmitIR<IR::Opcode::VectorMinS64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::VectorMinS64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) { (void)code;
code.CMGT(Qresult->D2(), Qb->D2(), Qa->D2()); (void)ctx;
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16()); (void)inst;
}); UNREACHABLE();
} }
template<> template<>
@@ -935,10 +935,10 @@ void EmitIR<IR::Opcode::VectorMinU32>(oaknut::CodeGenerator& code, EmitContext&
template<> template<>
void EmitIR<IR::Opcode::VectorMinU64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { void EmitIR<IR::Opcode::VectorMinU64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) { (void)code;
code.CMHI(Qresult->D2(), Qb->D2(), Qa->D2()); (void)ctx;
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16()); (void)inst;
}); UNREACHABLE();
} }
template<> template<>
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project. /* 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)); ir.PushRSB(ir.current_location->AdvancePC(4));
const u64 target = ir.PC() + offset; const u64 target = ir.PC() + offset;
ir.SetTerm(IR::Term::LinkBlockFast{ir.current_location->SetPC(target)}); ir.SetTerm(IR::Term::LinkBlock{ir.current_location->SetPC(target)});
return false; return false;
} }
File diff suppressed because one or more lines are too long
+122 -21
View File
@@ -5,45 +5,146 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#ifdef NIGHTLY_BUILD
#include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#endif
#include <fmt/format.h> #include <fmt/format.h>
#include "common/net/net.h" #include "common/logging.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "update_checker.h" #include "update_checker.h"
#include "common/logging.h" #include "common/httplib.h"
std::optional<Common::Net::Release> UpdateChecker::GetUpdate() { #ifdef YUZU_BUNDLED_OPENSSL
const auto latest = Common::Net::GetLatestRelease(); #include <openssl/cert.h>
if (!latest) return std::nullopt; #endif
LOG_INFO(Frontend, "Received update {}", latest->title); #include <nlohmann/json.hpp>
#include <optional>
#include <string>
#ifdef NIGHTLY_BUILD std::optional<std::string> UpdateChecker::GetResponse(std::string url, 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(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());
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);
#else
const auto update_check_url = std::string{Common::g_build_auto_update_api};
#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 (!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; std::vector<std::string> result;
boost::split(result, latest->tag, boost::is_any_of(".")); boost::split(result, latest_release_tag->tag, boost::is_any_of("."));
if (result.size() != 2) if (result.size() != 2)
return std::nullopt; goto empty;
tag = result[1];
const std::string tag = result[1];
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-")); boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
if (result.empty()) if (result.empty())
return std::nullopt; goto empty;
build = result[0];
const std::string build = result[0]; } else {
#else tag = latest_release_tag->tag;
const std::string tag = latest->tag; build = Common::g_build_version;
const std::string build = Common::g_build_version; }
#endif
if (tag != build) if (tag != build)
return latest; return latest_release_tag.value();
}
empty:
return std::nullopt; return std::nullopt;
} }
+8 -2
View File
@@ -8,10 +8,16 @@
#pragma once #pragma once
#include <optional> #include <optional>
#include "common/net/net.h" #include <string>
namespace UpdateChecker { namespace UpdateChecker {
std::optional<Common::Net::Release> GetUpdate(); typedef struct {
std::string tag;
std::string name;
} Update;
std::optional<std::string> GetResponse(std::string url, std::string path);
std::optional<Update> GetLatestRelease();
std::optional<Update> GetUpdate();
} // namespace UpdateChecker } // namespace UpdateChecker
+5 -11
View File
@@ -157,14 +157,11 @@ void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t c
if (!controller.device->IsConnected()) { if (!controller.device->IsConnected()) {
return; return;
} }
if (auto* shared_memory = controller.shared_memory; shared_memory) { auto* shared_memory = controller.shared_memory;
const auto& battery_level = controller.device->GetBattery(); const auto& battery_level = controller.device->GetBattery();
shared_memory->battery_level_dual = battery_level.dual.battery_level; shared_memory->battery_level_dual = battery_level.dual.battery_level;
shared_memory->battery_level_left = battery_level.left.battery_level; shared_memory->battery_level_left = battery_level.left.battery_level;
shared_memory->battery_level_right = battery_level.right.battery_level; shared_memory->battery_level_right = battery_level.right.battery_level;
} else {
LOG_WARNING(Service_HID, "shared_memory is null {}", controller_idx);
}
break; break;
} }
default: default:
@@ -183,10 +180,6 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) {
const auto& body_colors = controller.device->GetColors(); const auto& body_colors = controller.device->GetColors();
const auto& battery_level = controller.device->GetBattery(); const auto& battery_level = controller.device->GetBattery();
auto* shared_memory = controller.shared_memory; 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) { if (controller_type == Core::HID::NpadStyleIndex::None) {
npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id);
return; return;
@@ -808,7 +801,7 @@ Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) {
auto* shared_memory = controller.shared_memory; auto* shared_memory = controller.shared_memory;
if (!shared_memory) { if (!shared_memory) {
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id); LOG_WARNING(Service_HID, "DisconnectNpad: shared_memory is null for npad_id={}", npad_id);
return ResultSuccess; return ResultSuccess;
} }
// Don't reset shared_memory->assignment_mode this value is persistent // Don't reset shared_memory->assignment_mode this value is persistent
@@ -1202,10 +1195,11 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) { AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) {
const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid(); 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 { return {
.ui_variant = 0, .ui_variant = 0,
.footer = shared_memory ? shared_memory->applet_footer_type : AppletFooterUiType::None, .footer = shared_memory->applet_footer_type,
}; };
} }
+5 -4
View File
@@ -8,7 +8,7 @@ add_subdirectory(host_shaders)
add_subdirectory(gpu_logging) add_subdirectory(gpu_logging)
if(LIBVA_FOUND) if(LIBVA_FOUND)
set_source_files_properties(host1x/ffmpeg.cpp set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1) PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES}) list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
endif() endif()
@@ -44,6 +44,7 @@ add_library(video_core STATIC
engines/sw_blitter/converter.h engines/sw_blitter/converter.h
engines/const_buffer_info.h engines/const_buffer_info.h
engines/draw_manager.cpp engines/draw_manager.cpp
engines/draw_manager.h
engines/engine_interface.h engines/engine_interface.h
engines/engine_upload.cpp engines/engine_upload.cpp
engines/engine_upload.h engines/engine_upload.h
@@ -71,9 +72,9 @@ add_library(video_core STATIC
host1x/codecs/vp8.h host1x/codecs/vp8.h
host1x/codecs/vp9.cpp host1x/codecs/vp9.cpp
host1x/codecs/vp9.h host1x/codecs/vp9.h
host1x/codec_types.h host1x/codecs/vp9_types.h
host1x/ffmpeg.cpp host1x/ffmpeg/ffmpeg.cpp
host1x/ffmpeg.h host1x/ffmpeg/ffmpeg.h
host1x/control.cpp host1x/control.cpp
host1x/control.h host1x/control.h
host1x/gpu_device_memory_manager.cpp host1x/gpu_device_memory_manager.cpp
+19 -13
View File
@@ -356,7 +356,7 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
if (is_indexed) { if (is_indexed) {
BindHostIndexBuffer(); BindHostIndexBuffer();
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) { } else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
const auto& draw_state = maxwell3d->draw_manager.draw_state; const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads || if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) { draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first, runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
@@ -740,25 +740,30 @@ void BufferCache<P>::BindHostIndexBuffer() {
TouchBuffer(buffer, channel_state->index_buffer.buffer_id); TouchBuffer(buffer, channel_state->index_buffer.buffer_id);
const u32 offset = buffer.Offset(channel_state->index_buffer.device_addr); const u32 offset = buffer.Offset(channel_state->index_buffer.device_addr);
const u32 size = channel_state->index_buffer.size; const u32 size = channel_state->index_buffer.size;
const auto& draw_state = maxwell3d->draw_manager.draw_state; const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
if (draw_state.inline_index_draw_indexes.empty()) { if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
SynchronizeBuffer(buffer, channel_state->index_buffer.device_addr, size);
} else {
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) { if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
auto upload_staging = runtime.UploadStagingBuffer(size); auto upload_staging = runtime.UploadStagingBuffer(size);
std::array<BufferCopy, 1> copies{{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}}; std::array<BufferCopy, 1> copies{
std::memcpy(upload_staging.mapped_span.data(), draw_state.inline_index_draw_indexes.data(), size); {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); runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true);
} else { } else {
buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes); 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) { 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); runtime.BindIndexBuffer(buffer, new_offset, size);
} else { } else {
buffer.MarkUsage(offset, size); 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);
} }
} }
@@ -940,9 +945,10 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
return alignment > 1 && (offset % alignment) != 0; return alignment > 1 && (offset % alignment) != 0;
} }
}(); }();
const bool use_fast_buffer = needs_alignment_stream const bool use_fast_buffer = needs_alignment_stream ||
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size (has_host_buffer &&
&& !memory_tracker.IsRegionGpuModified(device_addr, size)); size <= channel_state->uniform_buffer_skip_cache_size &&
!memory_tracker.IsRegionGpuModified(device_addr, size));
if (use_fast_buffer) { if (use_fast_buffer) {
if constexpr (IS_OPENGL) { if constexpr (IS_OPENGL) {
if (runtime.HasFastBufferSubData()) { if (runtime.HasFastBufferSubData()) {
@@ -1223,7 +1229,7 @@ template <class P>
void BufferCache<P>::UpdateIndexBuffer() { void BufferCache<P>::UpdateIndexBuffer() {
// We have to check for the dirty flags and index count // We have to check for the dirty flags and index count
// The index count is currently changed without updating the dirty flags // The index count is currently changed without updating the dirty flags
const auto& draw_state = maxwell3d->draw_manager.draw_state; const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
const auto& index_buffer_ref = draw_state.index_buffer; const auto& index_buffer_ref = draw_state.index_buffer;
auto& flags = maxwell3d->dirty.flags; auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::IndexBuffer]) { if (!flags[Dirty::IndexBuffer]) {
@@ -32,7 +32,7 @@
#include "video_core/control/channel_state_cache.h" #include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h" #include "video_core/delayed_destruction_ring.h"
#include "video_core/dirty_flags.h" #include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h" #include "video_core/engines/draw_manager.h"
#include "video_core/engines/kepler_compute.h" #include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h" #include "video_core/memory_manager.h"
@@ -305,7 +305,7 @@ public:
[[nodiscard]] bool IsRegionCpuModified(DAddr addr, size_t size); [[nodiscard]] bool IsRegionCpuModified(DAddr addr, size_t size);
void SetDrawIndirect( void SetDrawIndirect(
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect_) { const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect_) {
current_draw_indirect = current_draw_indirect_; current_draw_indirect = current_draw_indirect_;
} }
@@ -480,7 +480,7 @@ private:
#endif #endif
DelayedDestructionRing<Buffer, TICKS_TO_DESTROY> delayed_destruction_ring; DelayedDestructionRing<Buffer, TICKS_TO_DESTROY> delayed_destruction_ring;
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{}; const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect{};
u32 last_index_count = 0; u32 last_index_count = 0;
+27 -19
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Ryujinx Team and Contributors // SPDX-FileCopyrightText: Ryujinx Team and Contributors
@@ -20,21 +20,27 @@
namespace Tegra { namespace Tegra {
CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id) CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id)
: host_processor(host1x_) : host1x{host1x_}, memory_manager{host1x.GMMU()},
, host1x{host1x_} host_processor{std::make_unique<Host1x::Control>(host1x_)}, current_class{
, current_class{ChClassId(id)} static_cast<ChClassId>(id)} {
{ thread = std::jthread([this](std::stop_token stop_token) { ProcessEntries(stop_token); });
thread = std::jthread([this](std::stop_token stop_token) { }
CDmaPusher::~CDmaPusher() = default;
void CDmaPusher::ProcessEntries(std::stop_token stop_token) {
Common::SetCurrentThreadPriority(Common::ThreadPriority::High); Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
ChCommandHeaderList command_list{host1x.System().ApplicationMemory(), 0, 0}; ChCommandHeaderList command_list{host1x.System().ApplicationMemory(), 0, 0};
u32 count{}; u32 count{};
u32 method_offset{}; u32 method_offset{};
u32 mask{}; u32 mask{};
bool incrementing{}; bool incrementing{};
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
{ {
std::unique_lock l{command_mutex}; std::unique_lock l{command_mutex};
command_cv.wait(l, stop_token, [this]() { return command_lists.size() > 0; }); command_cv.wait(l, stop_token,
[this]() { return command_lists.size() > 0; });
if (stop_token.stop_requested()) { if (stop_token.stop_requested()) {
return; return;
} }
@@ -64,7 +70,7 @@ CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id)
case ChSubmissionMode::SetClass: { case ChSubmissionMode::SetClass: {
mask = value.value & 0x3f; mask = value.value & 0x3f;
method_offset = value.method_offset; method_offset = value.method_offset;
current_class = ChClassId((value.value >> 6) & 0x3ff); current_class = static_cast<ChClassId>((value.value >> 6) & 0x3ff);
break; break;
} }
case ChSubmissionMode::Incrementing: case ChSubmissionMode::Incrementing:
@@ -84,37 +90,39 @@ CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id)
break; break;
} }
default: default:
LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1, (i - 1) * sizeof(u32), command_list.size()); LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1,
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", u32(mode)); (i - 1) * sizeof(u32), command_list.size());
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!",
static_cast<u32>(mode));
break; break;
} }
} }
} }
});
} }
CDmaPusher::~CDmaPusher() = default;
void CDmaPusher::ExecuteCommand(u32 method, u32 arg) { void CDmaPusher::ExecuteCommand(u32 method, u32 arg) {
switch (current_class) { switch (current_class) {
case ChClassId::Control: case ChClassId::Control:
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), method, arg); LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}",
host_processor.ProcessMethod(Host1x::Control::Method(method), arg); static_cast<u32>(current_class), method, arg);
host_processor->ProcessMethod(static_cast<Host1x::Control::Method>(method), arg);
break; break;
default: default:
thi_regs.reg_array[method] = arg; thi_regs.reg_array[method] = arg;
switch (static_cast<ThiMethod>(method)) { switch (static_cast<ThiMethod>(method)) {
case ThiMethod::IncSyncpt: { case ThiMethod::IncSyncpt: {
const auto syncpoint_id = u32(arg & 0xFF); const auto syncpoint_id = static_cast<u32>(arg & 0xFF);
[[maybe_unused]] const auto cond = u32((arg >> 8) & 0xFF); [[maybe_unused]] const auto cond = static_cast<u32>((arg >> 8) & 0xFF);
LOG_TRACE(Service_NVDRV, "Class {} IncSyncpt Method, syncpt {} cond {}", u32(current_class), syncpoint_id, cond); LOG_TRACE(Service_NVDRV, "Class {} IncSyncpt Method, syncpt {} cond {}",
static_cast<u32>(current_class), syncpoint_id, cond);
auto& syncpoint_manager = host1x.GetSyncpointManager(); auto& syncpoint_manager = host1x.GetSyncpointManager();
syncpoint_manager.IncrementGuest(syncpoint_id); syncpoint_manager.IncrementGuest(syncpoint_id);
syncpoint_manager.IncrementHost(syncpoint_id); syncpoint_manager.IncrementHost(syncpoint_id);
break; break;
} }
case ThiMethod::SetMethod1: case ThiMethod::SetMethod1:
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), u32(thi_regs.method_0), arg); LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}",
static_cast<u32>(current_class), static_cast<u32>(thi_regs.method_0), arg);
ProcessMethod(thi_regs.method_0, arg); ProcessMethod(thi_regs.method_0, arg);
break; break;
default: default:
+14 -11
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -18,11 +15,11 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "common/polyfill_thread.h" #include "common/polyfill_thread.h"
#include "core/memory.h" #include "core/memory.h"
#include "video_core/host1x/control.h"
namespace Tegra { namespace Tegra {
namespace Host1x { namespace Host1x {
class Control;
class Host1x; class Host1x;
class Nvdec; class Nvdec;
class SyncptIncrManager; class SyncptIncrManager;
@@ -120,19 +117,25 @@ protected:
virtual void ProcessMethod(u32 method, u32 arg) = 0; virtual void ProcessMethod(u32 method, u32 arg) = 0;
Host1x::Host1x& host1x;
Tegra::MemoryManager& memory_manager;
private: 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 /// Invoke command class devices to execute the command based on the current state
void ExecuteCommand(u32 state_offset, u32 data); void ExecuteCommand(u32 state_offset, u32 data);
protected: std::unique_ptr<Host1x::Control> host_processor;
ThiRegisters thi_regs{};
std::deque<ChCommandHeaderList> command_lists;
std::condition_variable_any command_cv;
Host1x::Control host_processor;
std::mutex command_mutex; std::mutex command_mutex;
Host1x::Host1x& host1x; std::condition_variable_any command_cv;
ChClassId current_class; std::deque<ChCommandHeaderList> command_lists;
std::jthread thread; std::jthread thread;
ThiRegisters thi_regs{};
ChClassId current_class;
}; };
} // namespace Tegra } // namespace Tegra
+139 -103
View File
@@ -1,24 +1,23 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h" #include "common/settings.h"
#include "video_core/dirty_flags.h" #include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h" #include "video_core/engines/draw_manager.h"
#include "video_core/rasterizer_interface.h" #include "video_core/rasterizer_interface.h"
namespace Tegra::Engines { namespace Tegra::Engines {
DrawManager::DrawManager(Maxwell3D* maxwell3d_) : maxwell3d(maxwell3d_) {}
void Maxwell3D::DrawManager::ProcessMethodCall(Maxwell3D& maxwell3d, u32 method, u32 argument) { void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
const auto& regs{maxwell3d->regs};
switch (method) { switch (method) {
case MAXWELL3D_REG_INDEX(clear_surface): case MAXWELL3D_REG_INDEX(clear_surface):
return Clear(maxwell3d, 1); return Clear(1);
case MAXWELL3D_REG_INDEX(draw.begin): case MAXWELL3D_REG_INDEX(draw.begin):
return DrawBegin(maxwell3d); return DrawBegin();
case MAXWELL3D_REG_INDEX(draw.end): case MAXWELL3D_REG_INDEX(draw.end):
return DrawEnd(maxwell3d); return DrawEnd();
case MAXWELL3D_REG_INDEX(vertex_buffer.first): case MAXWELL3D_REG_INDEX(vertex_buffer.first):
case MAXWELL3D_REG_INDEX(vertex_buffer.count): case MAXWELL3D_REG_INDEX(vertex_buffer.count):
case MAXWELL3D_REG_INDEX(index_buffer.first): case MAXWELL3D_REG_INDEX(index_buffer.first):
@@ -34,29 +33,33 @@ void Maxwell3D::DrawManager::ProcessMethodCall(Maxwell3D& maxwell3d, u32 method,
case MAXWELL3D_REG_INDEX(index_buffer32_first): case MAXWELL3D_REG_INDEX(index_buffer32_first):
case MAXWELL3D_REG_INDEX(index_buffer16_first): case MAXWELL3D_REG_INDEX(index_buffer16_first):
case MAXWELL3D_REG_INDEX(index_buffer8_first): case MAXWELL3D_REG_INDEX(index_buffer8_first):
return DrawIndexSmall(maxwell3d, argument); return DrawIndexSmall(argument);
case MAXWELL3D_REG_INDEX(draw_inline_index): case MAXWELL3D_REG_INDEX(draw_inline_index):
SetInlineIndexBuffer(maxwell3d, argument); SetInlineIndexBuffer(argument);
break; break;
case MAXWELL3D_REG_INDEX(inline_index_2x16.even): case MAXWELL3D_REG_INDEX(inline_index_2x16.even):
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_2x16.even); SetInlineIndexBuffer(regs.inline_index_2x16.even);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_2x16.odd); SetInlineIndexBuffer(regs.inline_index_2x16.odd);
break; break;
case MAXWELL3D_REG_INDEX(inline_index_4x8.index0): case MAXWELL3D_REG_INDEX(inline_index_4x8.index0):
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index0); SetInlineIndexBuffer(regs.inline_index_4x8.index0);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index1); SetInlineIndexBuffer(regs.inline_index_4x8.index1);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index2); SetInlineIndexBuffer(regs.inline_index_4x8.index2);
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index3); SetInlineIndexBuffer(regs.inline_index_4x8.index3);
break; break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_first): case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
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); DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
regs.vertex_array_instance_first.start.Value(),
regs.vertex_array_instance_first.count.Value(), false);
break; break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): { case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
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); DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
regs.vertex_array_instance_subsequent.start.Value(),
regs.vertex_array_instance_subsequent.count.Value(), true);
break; break;
} }
case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
DrawTexture(maxwell3d); DrawTexture();
break; break;
} }
default: default:
@@ -64,87 +67,101 @@ void Maxwell3D::DrawManager::ProcessMethodCall(Maxwell3D& maxwell3d, u32 method,
} }
} }
void Maxwell3D::DrawManager::Clear(Maxwell3D& maxwell3d, u32 layer_count) { void DrawManager::Clear(u32 layer_count) {
if (maxwell3d.ShouldExecute()) { if (maxwell3d->ShouldExecute()) {
maxwell3d.rasterizer->Clear(layer_count); maxwell3d->rasterizer->Clear(layer_count);
} }
} }
void Maxwell3D::DrawManager::DrawDeferred(Maxwell3D& maxwell3d) { void DrawManager::DrawDeferred() {
if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0) { if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0) {
return; return;
} }
DrawEnd(maxwell3d, draw_state.instance_count + 1, true); DrawEnd(draw_state.instance_count + 1, true);
draw_state.instance_count = 0; draw_state.instance_count = 0;
} }
void Maxwell3D::DrawManager::DrawArray(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, u32 base_instance, u32 num_instances) { void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
u32 base_instance, u32 num_instances) {
draw_state.topology = topology; draw_state.topology = topology;
draw_state.vertex_buffer.first = vertex_first; draw_state.vertex_buffer.first = vertex_first;
draw_state.vertex_buffer.count = vertex_count; draw_state.vertex_buffer.count = vertex_count;
draw_state.base_instance = base_instance; draw_state.base_instance = base_instance;
ProcessDraw(maxwell3d, false, num_instances); ProcessDraw(false, num_instances);
} }
void Maxwell3D::DrawManager::DrawArrayInstanced(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, bool subsequent) { void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
bool subsequent) {
draw_state.topology = topology; draw_state.topology = topology;
draw_state.vertex_buffer.first = vertex_first; draw_state.vertex_buffer.first = vertex_first;
draw_state.vertex_buffer.count = vertex_count; draw_state.vertex_buffer.count = vertex_count;
if (!subsequent) { if (!subsequent) {
draw_state.instance_count = 1; draw_state.instance_count = 1;
} }
draw_state.base_instance = draw_state.instance_count - 1; draw_state.base_instance = draw_state.instance_count - 1;
draw_state.draw_mode = DrawMode::Instance; draw_state.draw_mode = DrawMode::Instance;
draw_state.instance_count++; draw_state.instance_count++;
ProcessDraw(maxwell3d, false, 1); ProcessDraw(false, 1);
} }
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) { 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};
draw_state.topology = topology; draw_state.topology = topology;
draw_state.index_buffer = maxwell3d.regs.index_buffer; draw_state.index_buffer = regs.index_buffer;
draw_state.index_buffer.first = index_first; draw_state.index_buffer.first = index_first;
draw_state.index_buffer.count = index_count; draw_state.index_buffer.count = index_count;
draw_state.base_index = base_index; draw_state.base_index = base_index;
draw_state.base_instance = base_instance; draw_state.base_instance = base_instance;
ProcessDraw(maxwell3d, true, num_instances); ProcessDraw(true, num_instances);
} }
void Maxwell3D::DrawManager::DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology) { void DrawManager::DrawArrayIndirect(PrimitiveTopology topology) {
draw_state.topology = topology; draw_state.topology = topology;
ProcessDrawIndirect(maxwell3d);
ProcessDrawIndirect();
} }
void Maxwell3D::DrawManager::DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count) { void DrawManager::DrawIndexedIndirect(PrimitiveTopology topology, u32 index_first,
u32 index_count) {
const auto& regs{maxwell3d->regs};
draw_state.topology = topology; draw_state.topology = topology;
draw_state.index_buffer = maxwell3d.regs.index_buffer; draw_state.index_buffer = regs.index_buffer;
draw_state.index_buffer.first = index_first; draw_state.index_buffer.first = index_first;
draw_state.index_buffer.count = index_count; draw_state.index_buffer.count = index_count;
ProcessDrawIndirect(maxwell3d);
ProcessDrawIndirect();
} }
void Maxwell3D::DrawManager::SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index) { void DrawManager::SetInlineIndexBuffer(u32 index) {
draw_state.inline_index_draw_indexes.push_back(u8(index & 0x000000ff)); draw_state.inline_index_draw_indexes.push_back(static_cast<u8>(index & 0x000000ff));
draw_state.inline_index_draw_indexes.push_back(u8((index & 0x0000ff00) >> 8)); draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x0000ff00) >> 8));
draw_state.inline_index_draw_indexes.push_back(u8((index & 0x00ff0000) >> 16)); draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x00ff0000) >> 16));
draw_state.inline_index_draw_indexes.push_back(u8((index & 0xff000000) >> 24)); draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0xff000000) >> 24));
draw_state.draw_mode = DrawMode::InlineIndex; draw_state.draw_mode = DrawMode::InlineIndex;
} }
void Maxwell3D::DrawManager::DrawBegin(Maxwell3D& maxwell3d) { void DrawManager::DrawBegin() {
auto reset_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First; const auto& regs{maxwell3d->regs};
auto increment_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent; 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;
if (reset_instance_count) { if (reset_instance_count) {
DrawDeferred(maxwell3d); DrawDeferred();
draw_state.instance_count = 0; draw_state.instance_count = 0;
draw_state.draw_mode = DrawMode::General; draw_state.draw_mode = DrawMode::General;
} else if (increment_instance_count) { } else if (increment_instance_count) {
draw_state.instance_count++; draw_state.instance_count++;
draw_state.draw_mode = DrawMode::Instance; draw_state.draw_mode = DrawMode::Instance;
} }
draw_state.topology = maxwell3d.regs.draw.topology;
draw_state.topology = regs.draw.topology;
} }
void Maxwell3D::DrawManager::DrawEnd(Maxwell3D& maxwell3d, u32 instance_count, bool force_draw) { void DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
const auto& regs{maxwell3d->regs};
switch (draw_state.draw_mode) { switch (draw_state.draw_mode) {
case DrawMode::Instance: case DrawMode::Instance:
if (!force_draw) { if (!force_draw) {
@@ -152,100 +169,119 @@ void Maxwell3D::DrawManager::DrawEnd(Maxwell3D& maxwell3d, u32 instance_count, b
} }
[[fallthrough]]; [[fallthrough]];
case DrawMode::General: case DrawMode::General:
draw_state.base_instance = maxwell3d.regs.global_base_instance_index; draw_state.base_instance = regs.global_base_instance_index;
draw_state.base_index = maxwell3d.regs.global_base_vertex_index; draw_state.base_index = regs.global_base_vertex_index;
if (draw_state.draw_indexed) { if (draw_state.draw_indexed) {
draw_state.index_buffer = maxwell3d.regs.index_buffer; draw_state.index_buffer = regs.index_buffer;
ProcessDraw(maxwell3d, true, instance_count); ProcessDraw(true, instance_count);
} else { } else {
draw_state.vertex_buffer = maxwell3d.regs.vertex_buffer; draw_state.vertex_buffer = regs.vertex_buffer;
ProcessDraw(maxwell3d, false, instance_count); ProcessDraw(false, instance_count);
} }
draw_state.draw_indexed = false; draw_state.draw_indexed = false;
break; break;
case DrawMode::InlineIndex: case DrawMode::InlineIndex:
draw_state.base_instance = maxwell3d.regs.global_base_instance_index; draw_state.base_instance = regs.global_base_instance_index;
draw_state.base_index = maxwell3d.regs.global_base_vertex_index; draw_state.base_index = regs.global_base_vertex_index;
draw_state.index_buffer = maxwell3d.regs.index_buffer; draw_state.index_buffer = regs.index_buffer;
draw_state.index_buffer.count = u32(draw_state.inline_index_draw_indexes.size() / 4); draw_state.index_buffer.count =
static_cast<u32>(draw_state.inline_index_draw_indexes.size() / 4);
draw_state.index_buffer.format = Maxwell3D::Regs::IndexFormat::UnsignedInt; draw_state.index_buffer.format = Maxwell3D::Regs::IndexFormat::UnsignedInt;
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; maxwell3d->dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
ProcessDraw(maxwell3d, true, instance_count); ProcessDraw(true, instance_count);
draw_state.inline_index_draw_indexes.clear(); draw_state.inline_index_draw_indexes.clear();
break; break;
} }
} }
void Maxwell3D::DrawManager::DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument) { void DrawManager::DrawIndexSmall(u32 argument) {
Maxwell3D::Regs::IndexBufferSmall index_small_params{argument}; const auto& regs{maxwell3d->regs};
draw_state.base_instance = maxwell3d.regs.global_base_instance_index; IndexBufferSmall index_small_params{argument};
draw_state.base_index = maxwell3d.regs.global_base_vertex_index; draw_state.base_instance = regs.global_base_instance_index;
draw_state.index_buffer = maxwell3d.regs.index_buffer; draw_state.base_index = regs.global_base_vertex_index;
draw_state.index_buffer = regs.index_buffer;
draw_state.index_buffer.first = index_small_params.first; draw_state.index_buffer.first = index_small_params.first;
draw_state.index_buffer.count = index_small_params.count; draw_state.index_buffer.count = index_small_params.count;
draw_state.topology = index_small_params.topology; draw_state.topology = index_small_params.topology;
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; maxwell3d->dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
ProcessDraw(maxwell3d, true, 1); ProcessDraw(true, 1);
} }
void Maxwell3D::DrawManager::DrawTexture(Maxwell3D& maxwell3d) { void DrawManager::DrawTexture() {
draw_texture_state.dst_x0 = f32(maxwell3d.regs.draw_texture.dst_x0) / 4096.f; const auto& regs{maxwell3d->regs};
draw_texture_state.dst_y0 = f32(maxwell3d.regs.draw_texture.dst_y0) / 4096.f; draw_texture_state.dst_x0 = static_cast<float>(regs.draw_texture.dst_x0) / 4096.f;
const auto dst_width = f32(maxwell3d.regs.draw_texture.dst_width) / 4096.f; draw_texture_state.dst_y0 = static_cast<float>(regs.draw_texture.dst_y0) / 4096.f;
const auto dst_height = f32(maxwell3d.regs.draw_texture.dst_height) / 4096.f; const auto dst_width = static_cast<float>(regs.draw_texture.dst_width) / 4096.f;
const bool lower_left{maxwell3d.regs.window_origin.mode != Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft}; 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};
if (lower_left) { if (lower_left) {
draw_texture_state.dst_y0 = f32(maxwell3d.regs.surface_clip.height) - draw_texture_state.dst_y0; draw_texture_state.dst_y0 =
static_cast<f32>(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_x1 = draw_texture_state.dst_x0 + dst_width;
draw_texture_state.dst_y1 = draw_texture_state.dst_y0 + dst_height; draw_texture_state.dst_y1 = draw_texture_state.dst_y0 + dst_height;
draw_texture_state.src_x0 = f32(maxwell3d.regs.draw_texture.src_x0) / 4096.f; draw_texture_state.src_x0 = static_cast<float>(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_y0 = static_cast<float>(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_x1 =
draw_texture_state.src_y1 = (f32(maxwell3d.regs.draw_texture.dy_dv) / 4294967296.f) * dst_height + draw_texture_state.src_y0; (static_cast<float>(regs.draw_texture.dx_du) / 4294967296.f) * dst_width +
draw_texture_state.src_sampler = maxwell3d.regs.draw_texture.src_sampler; draw_texture_state.src_x0;
draw_texture_state.src_texture = maxwell3d.regs.draw_texture.src_texture; draw_texture_state.src_y1 =
maxwell3d.rasterizer->DrawTexture(); (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();
} }
void Maxwell3D::DrawManager::UpdateTopology(Maxwell3D& maxwell3d) { void DrawManager::UpdateTopology() {
switch (maxwell3d.regs.primitive_topology_control) { const auto& regs{maxwell3d->regs};
case Maxwell3D::Regs::PrimitiveTopologyControl::UseInBeginMethods: switch (regs.primitive_topology_control) {
case PrimitiveTopologyControl::UseInBeginMethods:
break; break;
case Maxwell3D::Regs::PrimitiveTopologyControl::UseSeparateState: case PrimitiveTopologyControl::UseSeparateState:
switch (maxwell3d.regs.topology_override) { switch (regs.topology_override) {
case Maxwell3D::Regs::PrimitiveTopologyOverride::None: case PrimitiveTopologyOverride::None:
break; break;
case Maxwell3D::Regs::PrimitiveTopologyOverride::Points: case PrimitiveTopologyOverride::Points:
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Points; draw_state.topology = PrimitiveTopology::Points;
break; break;
case Maxwell3D::Regs::PrimitiveTopologyOverride::Lines: case PrimitiveTopologyOverride::Lines:
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Lines; draw_state.topology = PrimitiveTopology::Lines;
break; break;
case Maxwell3D::Regs::PrimitiveTopologyOverride::LineStrip: case PrimitiveTopologyOverride::LineStrip:
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::LineStrip; draw_state.topology = PrimitiveTopology::LineStrip;
break; break;
default: default:
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology(maxwell3d.regs.topology_override); draw_state.topology = static_cast<PrimitiveTopology>(regs.topology_override);
break; break;
} }
break; break;
} }
} }
void Maxwell3D::DrawManager::ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed, u32 instance_count) { 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); LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology,
UpdateTopology(maxwell3d); draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
if (maxwell3d.ShouldExecute()) {
maxwell3d.rasterizer->Draw(draw_indexed, instance_count); UpdateTopology();
if (maxwell3d->ShouldExecute()) {
maxwell3d->rasterizer->Draw(draw_indexed, instance_count);
} }
} }
void Maxwell3D::DrawManager::ProcessDrawIndirect(Maxwell3D& maxwell3d) { 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); LOG_TRACE(
UpdateTopology(maxwell3d); HW_GPU,
if (maxwell3d.ShouldExecute()) { "called, topology={}, is_indexed={}, includes_count={}, buffer_size={}, max_draw_count={}",
maxwell3d.rasterizer->DrawIndirect(); 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();
} }
} }
} // namespace Tegra::Engines } // namespace Tegra::Engines
+117
View File
@@ -0,0 +1,117 @@
// 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
+7 -6
View File
@@ -13,7 +13,7 @@
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "video_core/dirty_flags.h" #include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.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/gpu.h"
#include "video_core/memory_manager.h" #include "video_core/memory_manager.h"
@@ -26,8 +26,7 @@ namespace Tegra::Engines {
constexpr u32 MacroRegistersStart = 0xE00; constexpr u32 MacroRegistersStart = 0xE00;
Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_) Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
: draw_manager() : draw_manager{std::make_unique<DrawManager>(this)}, system{system_}
, system{system_}
, memory_manager{memory_manager_} , memory_manager{memory_manager_}
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
, macro_engine(bool(Settings::values.disable_macro_jit)) , macro_engine(bool(Settings::values.disable_macro_jit))
@@ -374,7 +373,8 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
case MAXWELL3D_REG_INDEX(sync_info): case MAXWELL3D_REG_INDEX(sync_info):
return ProcessSyncPoint(); return ProcessSyncPoint();
case MAXWELL3D_REG_INDEX(launch_dma): 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): case MAXWELL3D_REG_INDEX(inline_data):
upload_state.ProcessData(argument, is_last_call); upload_state.ProcessData(argument, is_last_call);
return; return;
@@ -386,7 +386,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
case MAXWELL3D_REG_INDEX(tiled_cache_barrier): case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
return rasterizer->TiledCacheBarrier(); return rasterizer->TiledCacheBarrier();
default: default:
draw_manager.ProcessMethodCall(*this, method, argument); draw_manager->ProcessMethodCall(method, argument);
break; break;
} }
} }
@@ -401,7 +401,8 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
// Execute the current macro. // Execute the current macro.
macro_engine.Execute(*this, macro_positions[entry], parameters); macro_engine.Execute(*this, macro_positions[entry], parameters);
draw_manager.DrawDeferred(*this);
draw_manager->DrawDeferred();
} }
void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
+6 -59
View File
@@ -25,7 +25,6 @@
#include "video_core/gpu.h" #include "video_core/gpu.h"
#include "video_core/macro.h" #include "video_core/macro.h"
#include "video_core/textures/texture.h" #include "video_core/textures/texture.h"
#include "video_core/engines/maxwell_3d.h"
namespace Core { namespace Core {
class System; class System;
@@ -41,6 +40,8 @@ class RasterizerInterface;
namespace Tegra::Engines { namespace Tegra::Engines {
class DrawManager;
/** /**
* This Engine is known as GF100_3D. Documentation can be found in: * 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 * https://github.com/NVIDIA/open-gpu-doc/blob/master/classes/3d/clb197.h
@@ -3022,7 +3023,8 @@ public:
u32 bindless_texture_const_buffer_slot; ///< 0x2608 u32 bindless_texture_const_buffer_slot; ///< 0x2608
u32 trap_handler; ///< 0x260C u32 trap_handler; ///< 0x260C
INSERT_PADDING_BYTES_NOINIT(0x1F0); 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); INSERT_PADDING_BYTES_NOINIT(0x93C);
ShaderPerformance shader_performance; ///< 0x333C ShaderPerformance shader_performance; ///< 0x333C
INSERT_PADDING_BYTES_NOINIT(0x18); INSERT_PADDING_BYTES_NOINIT(0x18);
@@ -3033,62 +3035,6 @@ public:
}; };
// clang-format on // 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{}; Regs regs{};
/// Store temporary hw register values, used by some calls to restore state after a operation /// Store temporary hw register values, used by some calls to restore state after a operation
@@ -3156,7 +3102,8 @@ public:
Tables tables{}; Tables tables{};
} dirty; } dirty;
DrawManager draw_manager; std::unique_ptr<DrawManager> draw_manager;
friend class DrawManager;
GPUVAddr GetMacroAddress(size_t index) const { GPUVAddr GetMacroAddress(size_t index) const {
return macro_addresses[index]; return macro_addresses[index];
+1 -1
View File
@@ -89,7 +89,7 @@ struct GPU::Impl {
renderer = std::move(renderer_); renderer = std::move(renderer_);
rasterizer = renderer->ReadRasterizer(); rasterizer = renderer->ReadRasterizer();
host1x.MemoryManager().BindInterface(rasterizer); host1x.MemoryManager().BindInterface(rasterizer);
host1x.gmmu_manager.BindRasterizer(rasterizer); host1x.GMMU().BindRasterizer(rasterizer);
} }
/// Flush all current written commands into the host GPU for execution. /// Flush all current written commands into the host GPU for execution.
+22 -11
View File
@@ -19,20 +19,19 @@
namespace VideoCommon::GPUThread { namespace VideoCommon::GPUThread {
ThreadManager::ThreadManager(Core::System& system_, bool is_async_) /// Runs the GPU thread
: system{system_}, is_async{is_async_} {} static void RunThread(std::stop_token stop_token, Core::System& system,
VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
ThreadManager::~ThreadManager() = default; Tegra::Control::Scheduler& scheduler, SynchState& state) {
void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, Tegra::Control::Scheduler& scheduler) {
rasterizer = renderer.ReadRasterizer();
thread = std::jthread([&](std::stop_token stop_token) {
Common::SetCurrentThreadName("GPU"); Common::SetCurrentThreadName("GPU");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
system.RegisterHostThread(); system.RegisterHostThread();
auto current_context = context.Acquire(); auto current_context = context.Acquire();
VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
CommandDataContainer next; CommandDataContainer next;
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
state.queue.PopWait(next, stop_token); state.queue.PopWait(next, stop_token);
if (stop_token.stop_requested()) { if (stop_token.stop_requested()) {
@@ -43,9 +42,9 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
} else if (std::holds_alternative<GPUTickCommand>(next.data)) { } else if (std::holds_alternative<GPUTickCommand>(next.data)) {
system.GPU().TickWork(); system.GPU().TickWork();
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) { } else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
renderer.ReadRasterizer()->FlushRegion(flush->addr, flush->size); rasterizer->FlushRegion(flush->addr, flush->size);
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) { } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
renderer.ReadRasterizer()->OnCacheInvalidation(invalidate->addr, invalidate->size); rasterizer->OnCacheInvalidation(invalidate->addr, invalidate->size);
} else { } else {
ASSERT(false); ASSERT(false);
} }
@@ -57,7 +56,19 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
state.cv.notify_all(); 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) {
rasterizer = renderer.ReadRasterizer();
thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
std::ref(scheduler), std::ref(state));
} }
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) { void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
+19 -17
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -16,12 +13,9 @@
namespace Tegra { namespace Tegra {
Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs) Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs)
: host1x(host1x_) : host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
, state{regs} vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
, h264_decoder(host1x_) vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
, vp8_decoder(host1x_)
, vp9_decoder(host1x_)
{}
Codec::~Codec() = default; Codec::~Codec() = default;
@@ -38,11 +32,13 @@ void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) {
void Codec::Decode() { void Codec::Decode() {
const bool is_first_frame = !initialized; const bool is_first_frame = !initialized;
if (is_first_frame) if (is_first_frame) {
Initialize(); Initialize();
}
if (!initialized) if (!initialized) {
return; return;
}
// Assemble bitstream. // Assemble bitstream.
bool vp9_hidden_frame = false; bool vp9_hidden_frame = false;
@@ -50,13 +46,13 @@ void Codec::Decode() {
const auto packet_data = [&]() { const auto packet_data = [&]() {
switch (current_codec) { switch (current_codec) {
case Tegra::Host1x::NvdecCommon::VideoCodec::H264: 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: case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
return vp8_decoder.ComposeFrame(state); return vp8_decoder->ComposeFrame(state);
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9: case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
vp9_decoder.ComposeFrame(state); vp9_decoder->ComposeFrame(state);
vp9_hidden_frame = vp9_decoder.WasFrameHidden(); vp9_hidden_frame = vp9_decoder->WasFrameHidden();
return vp9_decoder.GetFrameBytes(); return vp9_decoder->GetFrameBytes();
default: default:
ASSERT(false); ASSERT(false);
return std::span<const u8>{}; return std::span<const u8>{};
@@ -85,13 +81,19 @@ void Codec::Decode() {
std::unique_ptr<FFmpeg::Frame> Codec::GetCurrentFrame() { std::unique_ptr<FFmpeg::Frame> Codec::GetCurrentFrame() {
// Sometimes VIC will request more frames than have been decoded. // Sometimes VIC will request more frames than have been decoded.
// in this case, return a blank frame and don't overwrite previous data. // in this case, return a blank frame and don't overwrite previous data.
if (frames.empty()) if (frames.empty()) {
return {}; return {};
}
auto frame = std::move(frames.front()); auto frame = std::move(frames.front());
frames.pop(); frames.pop();
return frame; return frame;
} }
Host1x::NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
return current_codec;
}
std::string_view Codec::GetCurrentCodecName() const { std::string_view Codec::GetCurrentCodecName() const {
switch (current_codec) { switch (current_codec) {
case Host1x::NvdecCommon::VideoCodec::None: case Host1x::NvdecCommon::VideoCodec::None:
+14 -10
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -11,14 +8,17 @@
#include <string_view> #include <string_view>
#include <queue> #include <queue>
#include "common/common_types.h" #include "common/common_types.h"
#include "video_core/host1x/codecs/h264.h" #include "video_core/host1x/ffmpeg/ffmpeg.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" #include "video_core/host1x/nvdec_common.h"
namespace Tegra { namespace Tegra {
namespace Decoder {
class H264;
class VP8;
class VP9;
} // namespace Decoder
namespace Host1x { namespace Host1x {
class Host1x; class Host1x;
} // namespace Host1x } // namespace Host1x
@@ -40,6 +40,9 @@ public:
/// Returns next decoded frame /// Returns next decoded frame
[[nodiscard]] std::unique_ptr<FFmpeg::Frame> GetCurrentFrame(); [[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 /// Return name of the current codec
[[nodiscard]] std::string_view GetCurrentCodecName() const; [[nodiscard]] std::string_view GetCurrentCodecName() const;
@@ -50,9 +53,10 @@ private:
Host1x::Host1x& host1x; Host1x::Host1x& host1x;
const Host1x::NvdecCommon::NvdecRegisters& state; const Host1x::NvdecCommon::NvdecRegisters& state;
Decoders::H264 h264_decoder; std::unique_ptr<Decoder::H264> h264_decoder;
Decoders::VP8 vp8_decoder; std::unique_ptr<Decoder::VP8> vp8_decoder;
Decoders::VP9 vp9_decoder; std::unique_ptr<Decoder::VP9> vp9_decoder;
std::queue<std::unique_ptr<FFmpeg::Frame>> frames{}; std::queue<std::unique_ptr<FFmpeg::Frame>> frames{};
}; };
+11 -12
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -12,11 +12,10 @@
namespace Tegra { namespace Tegra {
Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::NvdecRegisters& regs_) Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::NvdecRegisters& regs_,
: host1x(host1x_) Host1x::FrameQueue& frame_queue_)
, regs{regs_} : host1x(host1x_), memory_manager{host1x.GMMU()}, regs{regs_}, id{id_}, frame_queue{
, id{id_} frame_queue_} {}
{}
Decoder::~Decoder() = default; Decoder::~Decoder() = default;
@@ -54,11 +53,11 @@ void Decoder::Decode() {
} }
if (UsingDecodeOrder()) { if (UsingDecodeOrder()) {
host1x.frame_queue.PushDecodeOrder(id, luma_top, std::move(frame)); frame_queue.PushDecodeOrder(id, luma_top, std::move(frame));
host1x.frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy)); frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy));
} else { } else {
host1x.frame_queue.PushPresentOrder(id, luma_top, std::move(frame)); frame_queue.PushPresentOrder(id, luma_top, std::move(frame));
host1x.frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy)); frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy));
} }
} else { } else {
auto [luma_offset, chroma_offset] = GetProgressiveOffsets(); auto [luma_offset, chroma_offset] = GetProgressiveOffsets();
@@ -69,9 +68,9 @@ void Decoder::Decode() {
} }
if (UsingDecodeOrder()) { if (UsingDecodeOrder()) {
host1x.frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame)); frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame));
} else { } else {
host1x.frame_queue.PushPresentOrder(id, luma_offset, std::move(frame)); frame_queue.PushPresentOrder(id, luma_offset, std::move(frame));
} }
} }
} }
+15 -5
View File
@@ -14,7 +14,7 @@
#include <queue> #include <queue>
#include "common/common_types.h" #include "common/common_types.h"
#include "video_core/host1x/ffmpeg.h" #include "video_core/host1x/ffmpeg/ffmpeg.h"
#include "video_core/host1x/nvdec_common.h" #include "video_core/host1x/nvdec_common.h"
namespace Tegra { namespace Tegra {
@@ -35,23 +35,33 @@ public:
return decode_api.UsingDecodeOrder(); return decode_api.UsingDecodeOrder();
} }
/// Returns the value of current_codec
[[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const {
return codec;
}
/// Return name of the current codec /// Return name of the current codec
[[nodiscard]] virtual std::string_view GetCurrentCodecName() const = 0; [[nodiscard]] virtual std::string_view GetCurrentCodecName() const = 0;
protected: protected:
explicit Decoder(Host1x::Host1x& host1x, s32 id, const Host1x::NvdecCommon::NvdecRegisters& regs); explicit Decoder(Host1x::Host1x& host1x, s32 id,
const Host1x::NvdecCommon::NvdecRegisters& regs,
Host1x::FrameQueue& frame_queue);
virtual std::span<const u8> ComposeFrame() = 0; virtual std::span<const u8> ComposeFrame() = 0;
virtual std::tuple<u64, u64> GetProgressiveOffsets() = 0; virtual std::tuple<u64, u64> GetProgressiveOffsets() = 0;
virtual std::tuple<u64, u64, u64, u64> GetInterlacedOffsets() = 0; virtual std::tuple<u64, u64, u64, u64> GetInterlacedOffsets() = 0;
virtual bool IsInterlaced() = 0; virtual bool IsInterlaced() = 0;
FFmpeg::DecodeApi decode_api;
Host1x::Host1x& host1x; Host1x::Host1x& host1x;
Tegra::MemoryManager& memory_manager;
const Host1x::NvdecCommon::NvdecRegisters& regs; const Host1x::NvdecCommon::NvdecRegisters& regs;
s32 id; s32 id;
bool initialized : 1 = false; Host1x::FrameQueue& frame_queue;
bool vp9_hidden_frame : 1 = false; Host1x::NvdecCommon::VideoCodec codec;
FFmpeg::DecodeApi decode_api;
bool initialized{};
bool vp9_hidden_frame{};
}; };
} // namespace Tegra } // namespace Tegra
+50 -37
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -14,11 +14,25 @@
#include "video_core/memory_manager.h" #include "video_core/memory_manager.h"
namespace Tegra::Decoders { 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,
};
H264::H264(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_) constexpr std::array<u8, 16> zig_zag_scan{
: Decoder{host1x_, id_, regs_} 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,
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::H264); };
} // 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() = default; H264::~H264() = default;
@@ -51,11 +65,14 @@ bool H264::IsInterlaced() {
} }
std::span<const u8> H264::ComposeFrame() { std::span<const u8> H264::ComposeFrame() {
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context, sizeof(H264DecoderContext)); memory_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context,
sizeof(H264DecoderContext));
const s64 frame_number = current_context.h264_parameter_set.frame_number.Value(); const s64 frame_number = current_context.h264_parameter_set.frame_number.Value();
if (!is_first_frame && frame_number != 0) { if (!is_first_frame && frame_number != 0) {
frame_scratch.resize_destructive(current_context.stream_len); frame_scratch.resize_destructive(current_context.stream_len);
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data(), frame_scratch.size()); memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data(),
frame_scratch.size());
return frame_scratch; return frame_scratch;
} }
@@ -157,13 +174,15 @@ std::span<const u8> H264::ComposeFrame() {
for (s32 index = 0; index < 6; index++) { for (s32 index = 0; index < 6; index++) {
writer.WriteBit(true); writer.WriteBit(true);
writer.WriteScalingList(current_context.weight_scale_4x4, index * 16, 16); std::span<const u8> matrix{current_context.weight_scale_4x4};
writer.WriteScalingList(scan_scratch, matrix, index * 16, 16);
} }
if (current_context.h264_parameter_set.transform_8x8_mode_flag) { if (current_context.h264_parameter_set.transform_8x8_mode_flag) {
for (s32 index = 0; index < 2; index++) { for (s32 index = 0; index < 2; index++) {
writer.WriteBit(true); writer.WriteBit(true);
writer.WriteScalingList(current_context.weight_scale_8x8, index * 64, 64); std::span<const u8> matrix{current_context.weight_scale_8x8};
writer.WriteScalingList(scan_scratch, matrix, index * 64, 64);
} }
} }
@@ -177,7 +196,11 @@ std::span<const u8> H264::ComposeFrame() {
const auto& encoded_header = writer.GetByteArray(); const auto& encoded_header = writer.GetByteArray();
frame_scratch.resize(encoded_header.size() + current_context.stream_len); frame_scratch.resize(encoded_header.size() + current_context.stream_len);
std::memcpy(frame_scratch.data(), encoded_header.data(), encoded_header.size()); std::memcpy(frame_scratch.data(), encoded_header.data(), encoded_header.size());
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data() + encoded_header.size(), current_context.stream_len);
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(),
frame_scratch.data() + encoded_header.size(),
current_context.stream_len);
return frame_scratch; return frame_scratch;
} }
@@ -206,39 +229,25 @@ void H264BitWriter::WriteBit(bool state) {
WriteBits(state ? 1 : 0, 1); WriteBits(state ? 1 : 0, 1);
} }
void H264BitWriter::WriteScalingList(std::span<const u8> list, s32 start, s32 count) { void H264BitWriter::WriteScalingList(Common::ScratchBuffer<u8>& scan, std::span<const u8> list,
s32 start, s32 count) {
scan.resize_destructive(count);
if (count == 16) { if (count == 16) {
u8 last_scale = 8; std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());
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 { } else {
// ZigZag LUTs from libavcodec: this is the famous zigzag pattern found in the ffmpeg logo itself! std::memcpy(scan.data(), zig_zag_direct.data(), scan.size());
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; u8 last_scale = 8;
for (s32 index = 0; index < count; index++) { for (s32 index = 0; index < count; index++) {
const u8 value = list[start + scan[index]]; const u8 value = list[start + scan[index]];
const s32 delta_scale = s32(value - last_scale); const s32 delta_scale = static_cast<s32>(value - last_scale);
WriteSe(delta_scale); WriteSe(delta_scale);
last_scale = value; last_scale = value;
} }
} }
}
std::vector<u8>& H264BitWriter::GetByteArray() { std::vector<u8>& H264BitWriter::GetByteArray() {
return byte_array; return byte_array;
@@ -277,15 +286,19 @@ void H264BitWriter::WriteBits(s32 value, s32 bit_count) {
void H264BitWriter::WriteExpGolombCodedInt(s32 value) { void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
const s32 sign = value <= 0 ? 0 : 1; const s32 sign = value <= 0 ? 0 : 1;
if (!sign) value = -value; if (value < 0) {
WriteExpGolombCodedUInt((value << 1) - sign); value = -value;
}
value = (value << 1) - sign;
WriteExpGolombCodedUInt(value);
} }
void H264BitWriter::WriteExpGolombCodedUInt(u32 value) { void H264BitWriter::WriteExpGolombCodedUInt(u32 value) {
const s32 size = 32 - std::countl_zero(value + 1); const s32 size = 32 - std::countl_zero(value + 1);
WriteBits(1, size); WriteBits(1, size);
value -= (1U << (size - 1)) - 1; value -= (1U << (size - 1)) - 1;
WriteBits(s32(value), size - 1); WriteBits(static_cast<s32>(value), size - 1);
} }
s32 H264BitWriter::GetFreeBufferBits() { s32 H264BitWriter::GetFreeBufferBits() {
+187 -9
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -9,10 +6,11 @@
#include <span> #include <span>
#include <vector> #include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/scratch_buffer.h" #include "common/scratch_buffer.h"
#include "video_core/host1x/codecs/decoder.h" #include "video_core/host1x/codecs/decoder.h"
#include "video_core/host1x/codec_types.h"
#include "video_core/host1x/nvdec_common.h" #include "video_core/host1x/nvdec_common.h"
namespace Tegra { namespace Tegra {
@@ -42,7 +40,8 @@ public:
/// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification /// 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 /// Writes the scaling matrices of the sream
void WriteScalingList(std::span<const u8> list, s32 start, s32 count); void WriteScalingList(Common::ScratchBuffer<u8>& scan, std::span<const u8> list, s32 start,
s32 count);
/// Return the bitstream as a vector. /// Return the bitstream as a vector.
[[nodiscard]] std::vector<u8>& GetByteArray(); [[nodiscard]] std::vector<u8>& GetByteArray();
@@ -62,9 +61,188 @@ private:
std::vector<u8> byte_array; 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 { class H264 final : public Decoder {
public: public:
explicit H264(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id); explicit H264(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
Host1x::FrameQueue& frame_queue);
~H264() override; ~H264() override;
H264(const H264&) = delete; H264(const H264&) = delete;
@@ -85,10 +263,10 @@ public:
} }
private: private:
H264DecoderContext current_context{};
std::array<u8, 64> scan_scratch;
Common::ScratchBuffer<u8> frame_scratch;
bool is_first_frame{true}; bool is_first_frame{true};
Common::ScratchBuffer<u8> frame_scratch;
Common::ScratchBuffer<u8> scan_scratch;
H264DecoderContext current_context{};
}; };
} // namespace Decoders } // namespace Decoders
+26 -23
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -11,10 +8,11 @@
#include "video_core/memory_manager.h" #include "video_core/memory_manager.h"
namespace Tegra::Decoders { namespace Tegra::Decoders {
VP8::VP8(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_) VP8::VP8(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_,
: Decoder{host1x_, id_, regs_} Host1x::FrameQueue& frame_queue_)
{ : Decoder{host1x_, id_, regs_, frame_queue_} {
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::VP8); codec = Host1x::NvdecCommon::VideoCodec::VP8;
initialized = decode_api.Initialize(codec);
} }
VP8::~VP8() = default; VP8::~VP8() = default;
@@ -27,30 +25,35 @@ std::tuple<u64, u64> VP8::GetProgressiveOffsets() {
std::tuple<u64, u64, u64, u64> VP8::GetInterlacedOffsets() { std::tuple<u64, u64, u64, u64> VP8::GetInterlacedOffsets() {
auto luma_top{regs.surface_luma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()}; auto luma_top{regs.surface_luma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
auto luma_bottom = regs.surface_luma_offsets[u32(Vp8SurfaceIndex::Current)].Address(); auto luma_bottom{
auto chroma_top = regs.surface_chroma_offsets[u32(Vp8SurfaceIndex::Current)].Address(); regs.surface_luma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
auto chroma_bottom = regs.surface_chroma_offsets[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()};
return {luma_top, luma_bottom, chroma_top, chroma_bottom}; return {luma_top, luma_bottom, chroma_top, chroma_bottom};
} }
std::span<const u8> VP8::ComposeFrame() { std::span<const u8> VP8::ComposeFrame() {
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context, sizeof(VP8PictureInfo)); memory_manager.ReadBlock(regs.picture_info_offset.Address(), &current_context,
sizeof(VP8PictureInfo));
const bool is_key_frame = current_context.key_frame == 1u; const bool is_key_frame = current_context.key_frame == 1u;
const auto bitstream_size = size_t(current_context.vld_buffer_size); const auto bitstream_size = static_cast<size_t>(current_context.vld_buffer_size);
const size_t header_size = is_key_frame ? 10u : 3u; const size_t header_size = is_key_frame ? 10u : 3u;
frame_scratch.resize(header_size + bitstream_size); frame_scratch.resize(header_size + bitstream_size);
// Based on page 30 of the VP8 specification. // Based on page 30 of the VP8 specification.
// https://datatracker.ietf.org/doc/rfc6386/ // 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] = is_key_frame ? 0u : 1u; // 1-bit frame type (0: keyframe, 1: interframes).
frame_scratch[0] |= u8((current_context.version & 7u) << 1u); // 3-bit version number frame_scratch[0] |=
frame_scratch[0] |= u8(1u << 4u); // 1-bit show_frame flag 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
// The next 19-bits are the first partition size // The next 19-bits are the first partition size
frame_scratch[0] |= u8((current_context.first_part_size & 7u) << 5u); frame_scratch[0] |= static_cast<u8>((current_context.first_part_size & 7u) << 5u);
frame_scratch[1] = u8((current_context.first_part_size & 0x7f8u) >> 3u); frame_scratch[1] = static_cast<u8>((current_context.first_part_size & 0x7f8u) >> 3u);
frame_scratch[2] = u8((current_context.first_part_size & 0x7f800u) >> 11u); frame_scratch[2] = static_cast<u8>((current_context.first_part_size & 0x7f800u) >> 11u);
if (is_key_frame) { if (is_key_frame) {
frame_scratch[3] = 0x9du; frame_scratch[3] = 0x9du;
@@ -58,15 +61,15 @@ std::span<const u8> VP8::ComposeFrame() {
frame_scratch[5] = 0x2au; frame_scratch[5] = 0x2au;
// TODO(ameerj): Horizontal/Vertical Scale // TODO(ameerj): Horizontal/Vertical Scale
// 16 bits: (2 bits Horizontal Scale << 14) | Width (14 bits) // 16 bits: (2 bits Horizontal Scale << 14) | Width (14 bits)
frame_scratch[6] = u8(current_context.frame_width & 0xff); frame_scratch[6] = static_cast<u8>(current_context.frame_width & 0xff);
frame_scratch[7] = u8(((current_context.frame_width >> 8) & 0x3f)); frame_scratch[7] = static_cast<u8>(((current_context.frame_width >> 8) & 0x3f));
// 16 bits:(2 bits Vertical Scale << 14) | Height (14 bits) // 16 bits:(2 bits Vertical Scale << 14) | Height (14 bits)
frame_scratch[8] = u8(current_context.frame_height & 0xff); frame_scratch[8] = static_cast<u8>(current_context.frame_height & 0xff);
frame_scratch[9] = u8(((current_context.frame_height >> 8) & 0x3f)); frame_scratch[9] = static_cast<u8>(((current_context.frame_height >> 8) & 0x3f));
} }
const u64 bitstream_offset = regs.frame_bitstream_offset.Address(); const u64 bitstream_offset = regs.frame_bitstream_offset.Address();
host1x.gmmu_manager.ReadBlock(bitstream_offset, frame_scratch.data() + header_size, bitstream_size); memory_manager.ReadBlock(bitstream_offset, frame_scratch.data() + header_size, bitstream_size);
return frame_scratch; return frame_scratch;
} }
+46 -6
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -14,7 +11,6 @@
#include "common/scratch_buffer.h" #include "common/scratch_buffer.h"
#include "video_core/host1x/codecs/decoder.h" #include "video_core/host1x/codecs/decoder.h"
#include "video_core/host1x/nvdec_common.h" #include "video_core/host1x/nvdec_common.h"
#include "video_core/host1x/codec_types.h"
namespace Tegra { namespace Tegra {
@@ -32,7 +28,8 @@ enum class Vp8SurfaceIndex : u32 {
class VP8 final : public Decoder { class VP8 final : public Decoder {
public: public:
explicit VP8(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id); explicit VP8(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
Host1x::FrameQueue& frame_queue);
~VP8() override; ~VP8() override;
VP8(const VP8&) = delete; VP8(const VP8&) = delete;
@@ -55,8 +52,51 @@ public:
} }
private: private:
VP8PictureInfo current_context{};
Common::ScratchBuffer<u8> frame_scratch; 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{};
}; };
} // namespace Decoders } // namespace Decoders
+63 -20
View File
@@ -1,11 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm> // for std::copy #include <algorithm> // for std::copy
#include <bit>
#include <numeric> #include <numeric>
#include "common/alignment.h" #include "common/alignment.h"
@@ -16,11 +15,11 @@
namespace Tegra::Decoders { namespace Tegra::Decoders {
namespace { namespace {
static constexpr u32 diff_update_probability = 252; constexpr u32 diff_update_probability = 252;
static constexpr u32 frame_sync_code = 0x498342; constexpr u32 frame_sync_code = 0x498342;
// Default compressed header probabilities once frame context resets // Default compressed header probabilities once frame context resets
static constexpr Vp9EntropyProbs default_probs{ constexpr Vp9EntropyProbs default_probs{
.y_mode_prob{ .y_mode_prob{
65, 32, 18, 144, 162, 194, 41, 51, 98, 132, 68, 18, 165, 217, 196, 45, 40, 78, 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, 173, 80, 19, 176, 240, 193, 64, 35, 46, 221, 135, 38, 194, 248, 121, 96, 85, 29,
@@ -159,6 +158,34 @@ static constexpr Vp9EntropyProbs default_probs{
.high_precision{128, 128}, .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 // 6.2.14 Tile size calculation
[[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) { [[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) {
@@ -200,17 +227,25 @@ static constexpr Vp9EntropyProbs default_probs{
[[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) { [[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) {
new_prob--; new_prob--;
old_prob--; old_prob--;
u8 i = old_prob * 2 <= 0xff
? u8((std::max)(0, RecenterNonNeg(new_prob, old_prob) - 1)) std::size_t index{};
: 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); 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]);
} }
} // Anonymous namespace } // Anonymous namespace
VP9::VP9(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_) VP9::VP9(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_,
: Decoder{host1x_, id_, regs_} Host1x::FrameQueue& frame_queue_)
{ : Decoder{host1x_, id_, regs_, frame_queue_} {
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::VP9); codec = Host1x::NvdecCommon::VideoCodec::VP9;
initialized = decode_api.Initialize(codec);
} }
VP9::~VP9() = default; VP9::~VP9() = default;
@@ -342,7 +377,8 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
if (update_map) { if (update_map) {
EntropyProbs entropy_probs{}; EntropyProbs entropy_probs{};
host1x.gmmu_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs, sizeof(entropy_probs)); memory_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs,
sizeof(entropy_probs));
auto WriteProb = [&](u8 prob) { auto WriteProb = [&](u8 prob) {
bool coded = prob != 255; bool coded = prob != 255;
@@ -406,7 +442,8 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
} }
Vp9PictureInfo VP9::GetVp9PictureInfo() { Vp9PictureInfo VP9::GetVp9PictureInfo() {
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), &current_picture_info, sizeof(PictureInfo)); memory_manager.ReadBlock(regs.picture_info_offset.Address(), &current_picture_info,
sizeof(PictureInfo));
Vp9PictureInfo vp9_info = current_picture_info.Convert(); Vp9PictureInfo vp9_info = current_picture_info.Convert();
InsertEntropy(regs.vp9_prob_tab_buffer_offset.Address(), vp9_info.entropy); InsertEntropy(regs.vp9_prob_tab_buffer_offset.Address(), vp9_info.entropy);
@@ -422,7 +459,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo() {
void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) { void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
EntropyProbs entropy; EntropyProbs entropy;
host1x.gmmu_manager.ReadBlock(offset, &entropy, sizeof(EntropyProbs)); memory_manager.ReadBlock(offset, &entropy, sizeof(EntropyProbs));
entropy.Convert(dst); entropy.Convert(dst);
} }
@@ -432,7 +469,9 @@ Vp9FrameContainer VP9::GetCurrentFrame() {
// gpu.SyncGuestHost(); epic, why? // gpu.SyncGuestHost(); epic, why?
current_frame.info = GetVp9PictureInfo(); current_frame.info = GetVp9PictureInfo();
current_frame.bit_stream.resize(current_frame.info.bitstream_size); current_frame.bit_stream.resize(current_frame.info.bitstream_size);
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), current_frame.bit_stream.data(), current_frame.info.bitstream_size); memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(),
current_frame.bit_stream.data(),
current_frame.info.bitstream_size);
} }
if (!next_frame.bit_stream.empty()) { if (!next_frame.bit_stream.empty()) {
Vp9FrameContainer temp{ Vp9FrameContainer temp{
@@ -854,9 +893,13 @@ std::span<const u8> VP9::ComposeFrame() {
// Write headers and frame to buffer // Write headers and frame to buffer
frame_scratch.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size()); frame_scratch.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size());
std::copy(uncompressed_header.begin(), uncompressed_header.end(), frame_scratch.begin()); 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(compressed_header.begin(), compressed_header.end(),
std::copy(bitstream.begin(), bitstream.end(), frame_scratch.begin() + uncompressed_header.size() + compressed_header.size()); 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(); vp9_hidden_frame = WasFrameHidden();
return GetFrameBytes(); return GetFrameBytes();
} }
@@ -886,7 +929,7 @@ void VpxRangeEncoder::Write(bool bit, s32 probability) {
local_range = range - split; local_range = range - split;
} }
s32 shift = s32(local_range == 0 ? 0 : (std::countl_zero<uint32_t>(local_range) - 24)); s32 shift = static_cast<s32>(norm_lut[local_range]);
local_range <<= shift; local_range <<= shift;
count += shift; count += shift;
+5 -6
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -14,7 +11,7 @@
#include "common/scratch_buffer.h" #include "common/scratch_buffer.h"
#include "common/stream.h" #include "common/stream.h"
#include "video_core/host1x/codecs/decoder.h" #include "video_core/host1x/codecs/decoder.h"
#include "video_core/host1x/codec_types.h" #include "video_core/host1x/codecs/vp9_types.h"
#include "video_core/host1x/nvdec_common.h" #include "video_core/host1x/nvdec_common.h"
namespace Tegra { namespace Tegra {
@@ -116,7 +113,8 @@ private:
class VP9 final : public Decoder { class VP9 final : public Decoder {
public: public:
explicit VP9(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id); explicit VP9(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
Host1x::FrameQueue& frame_queue);
~VP9() override; ~VP9() override;
VP9(const VP9&) = delete; VP9(const VP9&) = delete;
@@ -195,10 +193,11 @@ private:
[[nodiscard]] std::vector<u8> ComposeCompressedHeader(); [[nodiscard]] std::vector<u8> ComposeCompressedHeader();
[[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader(); [[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
Common::ScratchBuffer<u8> frame_scratch;
std::array<s8, 4> loop_filter_ref_deltas{}; std::array<s8, 4> loop_filter_ref_deltas{};
std::array<s8, 2> loop_filter_mode_deltas{}; std::array<s8, 2> loop_filter_mode_deltas{};
Common::ScratchBuffer<u8> frame_scratch;
Vp9FrameContainer next_frame{}; Vp9FrameContainer next_frame{};
std::array<Vp9EntropyProbs, 4> frame_ctxs{}; std::array<Vp9EntropyProbs, 4> frame_ctxs{};
bool swap_ref_indices{}; bool swap_ref_indices{};
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -9,192 +6,12 @@
#include <array> #include <array>
#include <vector> #include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
namespace Tegra { namespace Tegra {
namespace Decoders { 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 { enum class Vp9SurfaceIndex : u32 {
Last = 0, Last = 0,
Golden = 1, Golden = 1,
@@ -470,7 +287,10 @@ struct RefPoolElement {
bool refresh{}; bool refresh{};
}; };
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(Vp9EntropyProbs, field_name) == position) #define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(partition_prob, 0x0024); ASSERT_POSITION(partition_prob, 0x0024);
ASSERT_POSITION(switchable_interp_prob, 0x0724); ASSERT_POSITION(switchable_interp_prob, 0x0724);
ASSERT_POSITION(sign, 0x0772); ASSERT_POSITION(sign, 0x0772);
@@ -478,7 +298,10 @@ ASSERT_POSITION(class_0_fr, 0x079E);
ASSERT_POSITION(high_precision, 0x07B2); ASSERT_POSITION(high_precision, 0x07B2);
#undef ASSERT_POSITION #undef ASSERT_POSITION
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(PictureInfo, field_name) == position) #define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(PictureInfo, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(bitstream_size, 0x30); ASSERT_POSITION(bitstream_size, 0x30);
ASSERT_POSITION(last_frame_size, 0x48); ASSERT_POSITION(last_frame_size, 0x48);
ASSERT_POSITION(first_level, 0x70); ASSERT_POSITION(first_level, 0x70);
@@ -486,7 +309,9 @@ ASSERT_POSITION(segmentation, 0x80);
ASSERT_POSITION(loop_filter, 0xE4); ASSERT_POSITION(loop_filter, 0xE4);
#undef ASSERT_POSITION #undef ASSERT_POSITION
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(EntropyProbs, field_name) == position) #define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(EntropyProbs, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(inter_mode_prob, 0x400); ASSERT_POSITION(inter_mode_prob, 0x400);
ASSERT_POSITION(tx_8x8_prob, 0x470); ASSERT_POSITION(tx_8x8_prob, 0x470);
@@ -496,47 +321,5 @@ ASSERT_POSITION(class_0_fr, 0x560);
ASSERT_POSITION(coef_probs, 0x5A0); ASSERT_POSITION(coef_probs, 0x5A0);
#undef ASSERT_POSITION #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 Decoders
}; // namespace Tegra }; // namespace Tegra
@@ -9,7 +9,7 @@
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/memory.h" #include "core/memory.h"
#include "video_core/host1x/ffmpeg.h" #include "video_core/host1x/ffmpeg/ffmpeg.h"
#include "video_core/memory_manager.h" #include "video_core/memory_manager.h"
extern "C" { extern "C" {
@@ -34,9 +34,7 @@ constexpr std::array PreferredGpuDecoders = {
AV_HWDEVICE_TYPE_DXVA2, AV_HWDEVICE_TYPE_DXVA2,
AV_HWDEVICE_TYPE_D3D12VA, AV_HWDEVICE_TYPE_D3D12VA,
#elif defined(__FreeBSD__) #elif defined(__FreeBSD__)
AV_HWDEVICE_TYPE_VAAPI,
AV_HWDEVICE_TYPE_VDPAU, AV_HWDEVICE_TYPE_VDPAU,
AV_HWDEVICE_TYPE_DRM,
#elif defined(__APPLE__) #elif defined(__APPLE__)
AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
#elif defined(ANDROID) #elif defined(ANDROID)
@@ -118,6 +116,7 @@ Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
return AV_CODEC_ID_NONE; return AV_CODEC_ID_NONE;
} }
}(); }();
m_codec = avcodec_find_decoder(av_codec); m_codec = avcodec_find_decoder(av_codec);
} }
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+6 -9
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project // SPDX-FileCopyrightText: 2021 yuzu Emulator Project
@@ -12,12 +12,9 @@
namespace Tegra::Host1x { namespace Tegra::Host1x {
Host1x::Host1x(Core::System& system_) Host1x::Host1x(Core::System& system_)
: system{system_} : system{system_}, syncpoint_manager{},
, syncpoint_manager{} memory_manager(system.DeviceMemory()), gmmu_manager{system, memory_manager, 32, 0, 12},
, memory_manager(system.DeviceMemory()) allocator{std::make_unique<Common::FlatAllocator<u32, 0, 32>>(1 << 12)} {}
, gmmu_manager{system, memory_manager, 32, 0, 12}
, allocator{1 << 12}
{}
Host1x::~Host1x() = default; Host1x::~Host1x() = default;
@@ -27,13 +24,13 @@ void Host1x::StartDevice(s32 fd, ChannelType type, u32 syncpt) {
#ifdef YUZU_LEGACY #ifdef YUZU_LEGACY
std::call_once(nvdec_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer std::call_once(nvdec_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer
#endif #endif
devices[fd] = std::make_unique<Tegra::Host1x::Nvdec>(*this, fd, syncpt); devices[fd] = std::make_unique<Tegra::Host1x::Nvdec>(*this, fd, syncpt, frame_queue);
break; break;
case ChannelType::VIC: case ChannelType::VIC:
#ifdef YUZU_LEGACY #ifdef YUZU_LEGACY
std::call_once(vic_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer std::call_once(vic_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer
#endif #endif
devices[fd] = std::make_unique<Tegra::Host1x::Vic>(*this, fd, syncpt); devices[fd] = std::make_unique<Tegra::Host1x::Vic>(*this, fd, syncpt, frame_queue);
break; break;
default: default:
LOG_ERROR(HW_GPU, "Unimplemented host1x device {}", static_cast<u32>(type)); LOG_ERROR(HW_GPU, "Unimplemented host1x device {}", static_cast<u32>(type));

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