mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-26 09:06:45 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d1bb8af7e | |||
| 44fa2805d6 | |||
| 7d0e79335e | |||
| d17ecb01af | |||
| b537e83bed | |||
| 8765b49512 | |||
| a587b7dc3a | |||
| 90515bc6a2 | |||
| 676b1aabfc | |||
| 77decca678 | |||
| ed225f8a8b | |||
| d69bd86183 | |||
| c172abfb53 | |||
| d33dc16820 | |||
| 8cdaf19a83 | |||
| f088f5bd45 | |||
| cbeea5b954 | |||
| 1590e7c061 |
+4
-9
@@ -268,11 +268,6 @@ 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)
|
||||||
@@ -424,10 +419,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)
|
||||||
|
|
||||||
@@ -485,7 +480,7 @@ endfunction()
|
|||||||
# =============================================
|
# =============================================
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia)
|
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security)
|
||||||
find_library(${fw}_LIBRARY ${fw} REQUIRED)
|
find_library(${fw}_LIBRARY ${fw} REQUIRED)
|
||||||
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
|
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ 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
|
|
||||||
|
|
||||||
# Auto-updater metadata! Must somewhat mirror GitHub/Forgejo API endpoint
|
# TODO(crueter): Stable releases feed.
|
||||||
|
set(BUILD_AUTO_UPDATE_STABLE_REPO "eden-emu/eden")
|
||||||
|
set(BUILD_AUTO_UPDATE_STABLE_API "git.eden-emu.dev")
|
||||||
|
set(BUILD_AUTO_UPDATE_STABLE_API_PATH "/api/v1/repos/")
|
||||||
|
|
||||||
set(BUILD_AUTO_UPDATE_API_PATH "/latest/release.json")
|
set(BUILD_AUTO_UPDATE_API_PATH "/latest/release.json")
|
||||||
if (NIGHTLY_BUILD)
|
if (NIGHTLY_BUILD)
|
||||||
|
|||||||
Vendored
+566
-498
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+568
-500
File diff suppressed because it is too large
Load Diff
Vendored
+639
-567
File diff suppressed because it is too large
Load Diff
Vendored
+566
-499
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-499
File diff suppressed because it is too large
Load Diff
Vendored
+570
-502
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+564
-497
File diff suppressed because it is too large
Load Diff
Vendored
+566
-499
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+572
-503
File diff suppressed because it is too large
Load Diff
Vendored
+565
-498
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-498
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+566
-496
File diff suppressed because it is too large
Load Diff
Vendored
+574
-503
File diff suppressed because it is too large
Load Diff
Vendored
+566
-498
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,16 @@ 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)
|
||||||
|
|||||||
@@ -203,6 +203,12 @@ 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,6 +33,19 @@ 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)
|
||||||
|
|
||||||
@@ -240,17 +253,7 @@ object NativeLibrary {
|
|||||||
/**
|
/**
|
||||||
* Checks for available updates.
|
* Checks for available updates.
|
||||||
*/
|
*/
|
||||||
external fun checkForUpdate(): Array<String>?
|
external fun checkForUpdate(): UpdateResult?
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
||||||
val tag: String = latestVersion[0]
|
showUpdateDialog(latestVersion)
|
||||||
val name: String = latestVersion[1]
|
|
||||||
showUpdateDialog(tag, name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showUpdateDialog(tag: String, name: String) {
|
// TODO(crueter): body, "View on Forgejo" button
|
||||||
|
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, name))
|
.setMessage(getString(R.string.update_available_description, release.title))
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
var artifact = tag
|
val assets = release.assets
|
||||||
// Nightly builds have a slightly different format
|
|
||||||
if (NativeLibrary.isNightlyBuild()) {
|
if (assets.isEmpty()) {
|
||||||
artifact = tag.substringAfter('.', tag)
|
openLink(release.url)
|
||||||
|
} else {
|
||||||
|
downloadAndInstallUpdate(release)
|
||||||
}
|
}
|
||||||
downloadAndInstallUpdate(tag, artifact)
|
|
||||||
}
|
}
|
||||||
.setNeutralButton(R.string.cancel) { dialog, _ ->
|
.setNeutralButton(R.string.cancel) { dialog, _ ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
@@ -206,17 +206,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadAndInstallUpdate(version: String, artifact: String) {
|
private fun openLink(link: String) {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, link.toUri())
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadAndInstallUpdate(release: NativeLibrary.UpdateResult) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val packageId = applicationContext.packageName
|
val packageId = applicationContext.packageName
|
||||||
val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
|
val asset = release.assets[0]
|
||||||
|
val artifact = asset.split("/").last()
|
||||||
val apkFile = File(cacheDir, "update-$artifact.apk")
|
val apkFile = File(cacheDir, "update-$artifact.apk")
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
showDownloadProgressDialog()
|
showDownloadProgressDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloader = APKDownloader(apkUrl, apkFile)
|
val downloader = APKDownloader(asset, apkFile)
|
||||||
downloader.download(
|
downloader.download(
|
||||||
onProgress = { progress ->
|
onProgress = { progress ->
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -248,7 +254,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
getString(R.string.update_download_failed) + "\n\nURL: $apkUrl",
|
getString(R.string.update_download_failed) + "\n\nURL: $asset",
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
@@ -277,7 +283,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
|
|
||||||
private fun updateDownloadProgress(progress: Int) {
|
private fun updateDownloadProgress(progress: Int) {
|
||||||
progressBar?.progress = progress
|
progressBar?.progress = progress
|
||||||
progressMessage?.text = "$progress%"
|
progressMessage?.text = getString(R.string.percent, progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dismissDownloadProgressDialog() {
|
private fun dismissDownloadProgressDialog() {
|
||||||
|
|||||||
@@ -1699,76 +1699,76 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isNightlyBuild(
|
|||||||
#ifdef ENABLE_UPDATE_CHECKER
|
#ifdef ENABLE_UPDATE_CHECKER
|
||||||
|
|
||||||
|
|
||||||
JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
JNIEXPORT jobject JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
jobject obj) {
|
jobject obj) {
|
||||||
std::optional<UpdateChecker::Update> release = UpdateChecker::GetUpdate();
|
std::optional<Common::Net::Release> 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 name = release->name;
|
const std::string title = release->title;
|
||||||
|
const std::string body = release->body;
|
||||||
|
const std::string url = release->html_url;
|
||||||
|
|
||||||
jobjectArray result = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr);
|
// Android *should* only ever define a single asset.
|
||||||
|
// If not, something has gone wrong, but the Kotlin side can handle it.
|
||||||
|
const auto assets = release->GetPlatformAssets();
|
||||||
|
|
||||||
const jstring jtag = env->NewStringUTF(tag.c_str());
|
jclass updateResultClass = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary$UpdateResult");
|
||||||
const jstring jname = env->NewStringUTF(name.c_str());
|
if (!updateResultClass) {
|
||||||
|
LOG_ERROR(Frontend, "Could not find UpdateResult class");
|
||||||
env->SetObjectArrayElement(result, 0, jtag);
|
return nullptr;
|
||||||
env->SetObjectArrayElement(result, 1, jname);
|
|
||||||
env->DeleteLocalRef(jtag);
|
|
||||||
env->DeleteLocalRef(jname);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
|
||||||
JNIEnv* env,
|
|
||||||
jobject obj,
|
|
||||||
jstring version) {
|
|
||||||
const char* version_str = env->GetStringUTFChars(version, nullptr);
|
|
||||||
const std::string url = fmt::format("{}/{}",
|
|
||||||
std::string{Common::g_build_auto_update_api},
|
|
||||||
version_str);
|
|
||||||
env->ReleaseStringUTFChars(version, version_str);
|
|
||||||
return env->NewStringUTF(url.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
|
||||||
JNIEnv* env,
|
|
||||||
jobject obj,
|
|
||||||
jstring tag,
|
|
||||||
jstring artifact,
|
|
||||||
jstring packageId) {
|
|
||||||
const char* version_str = env->GetStringUTFChars(tag, nullptr);
|
|
||||||
const char* artifact_str = env->GetStringUTFChars(artifact, nullptr);
|
|
||||||
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
|
|
||||||
|
|
||||||
std::string variant;
|
|
||||||
std::string package_id(package_id_str);
|
|
||||||
|
|
||||||
if (package_id.find("dev.legacy.eden_emulator") != std::string::npos) {
|
|
||||||
variant = "legacy";
|
|
||||||
} else if (package_id.find("com.miHoYo.Yuanshen") != std::string::npos) {
|
|
||||||
variant = "optimized";
|
|
||||||
} else {
|
|
||||||
#ifdef ARCHITECTURE_arm64
|
|
||||||
variant = "standard";
|
|
||||||
#else
|
|
||||||
variant = "chromeos";
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", artifact_str, variant);
|
jmethodID updateResultCtor = env->GetMethodID(updateResultClass, "<init>", "()V");
|
||||||
|
|
||||||
const std::string url = fmt::format("https://{}/{}/{}",
|
if (!updateResultCtor) {
|
||||||
std::string{Common::g_build_auto_update_api},
|
LOG_ERROR(Frontend, "Could not find UpdateResult ctor");
|
||||||
version_str, apk_filename);
|
env->DeleteLocalRef(updateResultClass);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
env->ReleaseStringUTFChars(tag, version_str);
|
jmethodID setTag = env->GetMethodID(updateResultClass, "setTag", "(Ljava/lang/String;)V");
|
||||||
env->ReleaseStringUTFChars(artifact, artifact_str);
|
jmethodID setTitle = env->GetMethodID(updateResultClass, "setTitle", "(Ljava/lang/String;)V");
|
||||||
env->ReleaseStringUTFChars(packageId, package_id_str);
|
jmethodID setBody = env->GetMethodID(updateResultClass, "setBody", "(Ljava/lang/String;)V");
|
||||||
return env->NewStringUTF(url.c_str());
|
jmethodID setUrl = env->GetMethodID(updateResultClass, "setUrl", "(Ljava/lang/String;)V");
|
||||||
|
jmethodID addAsset = env->GetMethodID(updateResultClass, "addAsset", "(Ljava/lang/String;)V");
|
||||||
|
|
||||||
|
jobject updateResult = env->NewObject(updateResultClass, updateResultCtor);
|
||||||
|
|
||||||
|
LOG_DEBUG(Frontend, "Tag: {}", tag);
|
||||||
|
LOG_DEBUG(Frontend, "Title: {}", title);
|
||||||
|
LOG_DEBUG(Frontend, "Body: {}", body);
|
||||||
|
LOG_DEBUG(Frontend, "Url: {}", url);
|
||||||
|
|
||||||
|
const auto jtag = env->NewStringUTF(tag.c_str());
|
||||||
|
const auto jtitle = env->NewStringUTF(title.c_str());
|
||||||
|
const auto jbody = env->NewStringUTF(body.c_str());
|
||||||
|
const auto jurl = env->NewStringUTF(url.c_str());
|
||||||
|
|
||||||
|
env->CallVoidMethod(updateResult, setTag, jtag);
|
||||||
|
env->CallVoidMethod(updateResult, setTitle, jtitle);
|
||||||
|
env->CallVoidMethod(updateResult, setBody, jbody);
|
||||||
|
env->CallVoidMethod(updateResult, setUrl, jurl);
|
||||||
|
|
||||||
|
// TODO(crueter): Handling for multiple assets?
|
||||||
|
// Maybe another data class x(
|
||||||
|
for (const Common::Net::Asset &a : assets) {
|
||||||
|
const auto jaurl = env->NewStringUTF(a.path.c_str());
|
||||||
|
env->CallVoidMethod(updateResult, addAsset, jaurl);
|
||||||
|
env->DeleteLocalRef(jaurl);
|
||||||
|
}
|
||||||
|
|
||||||
|
env->DeleteLocalRef(jtag);
|
||||||
|
env->DeleteLocalRef(jtitle);
|
||||||
|
env->DeleteLocalRef(jbody);
|
||||||
|
env->DeleteLocalRef(jurl);
|
||||||
|
|
||||||
|
env->DeleteLocalRef(updateResultClass);
|
||||||
|
|
||||||
|
return updateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getBuildVersion(
|
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getBuildVersion(
|
||||||
|
|||||||
@@ -1159,5 +1159,6 @@
|
|||||||
<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">Activa/desactiva mods, actualizaciones y contenidos descargables</string>
|
<string name="add_ons_description">Alternar mods, actualizaciones y contenido descargable</string>
|
||||||
<string name="playtime">Tiempo jugado:</string>
|
<string name="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,5 +1153,6 @@
|
|||||||
<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,5 +1150,4 @@
|
|||||||
<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,5 +1155,6 @@
|
|||||||
<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,6 +483,8 @@
|
|||||||
<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>
|
||||||
@@ -490,6 +492,9 @@
|
|||||||
<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>
|
||||||
@@ -1144,5 +1149,6 @@
|
|||||||
<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,5 +1783,6 @@ 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>
|
||||||
|
|||||||
@@ -147,7 +147,8 @@ 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
|
||||||
@@ -245,7 +246,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)
|
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json)
|
||||||
|
|
||||||
if (lz4_ADDED)
|
if (lz4_ADDED)
|
||||||
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
|
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
template <typename BaseAddr>
|
template <typename BaseAddr>
|
||||||
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
|
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_, std::size_t first_level_bits_, std::size_t page_bits_)
|
||||||
std::size_t first_level_bits_,
|
: address_space_bits{address_space_bits_}
|
||||||
std::size_t page_bits_)
|
, first_level_bits{first_level_bits_}
|
||||||
: address_space_bits{address_space_bits_},
|
, page_bits{page_bits_}
|
||||||
first_level_bits{first_level_bits_}, page_bits{page_bits_} {
|
{
|
||||||
if (page_bits == 0) {
|
if (page_bits == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
first_level_shift = address_space_bits - first_level_bits;
|
first_level_shift = address_space_bits - first_level_bits;
|
||||||
first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
|
first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
|
||||||
@@ -30,12 +30,9 @@ MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bit
|
|||||||
void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
|
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);
|
||||||
}
|
}
|
||||||
@@ -56,29 +53,21 @@ 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 level) {
|
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 index) {
|
||||||
void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
|
void* ptr = reinterpret_cast<char *>(base_ptr) + index * 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);
|
||||||
#else
|
|
||||||
void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
|
|
||||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
|
|
||||||
|
|
||||||
if (base == MAP_FAILED) {
|
|
||||||
base = nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
ASSERT(base);
|
ASSERT(base);
|
||||||
|
#else
|
||||||
first_level_map[level] = base;
|
void* base = ptr;
|
||||||
|
#endif
|
||||||
|
first_level_map[index] = base;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
@@ -0,0 +1,287 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <boost/algorithm/string/classification.hpp>
|
||||||
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
#include <boost/algorithm/string/split.hpp>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/scm_rev.h"
|
||||||
|
#include "net.h"
|
||||||
|
|
||||||
|
#include "common/logging.h"
|
||||||
|
|
||||||
|
#include "common/httplib.h"
|
||||||
|
|
||||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||||
|
#include <openssl/cert.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define QT_TR_NOOP(x) x
|
||||||
|
|
||||||
|
namespace Common::Net {
|
||||||
|
|
||||||
|
std::vector<Asset> Release::GetPlatformAssets() const {
|
||||||
|
// TODO(crueter): Need better handling for this as a whole.
|
||||||
|
#ifdef NIGHTLY_BUILD
|
||||||
|
std::vector<std::string> result;
|
||||||
|
boost::algorithm::split(result, tag, boost::is_any_of("."));
|
||||||
|
if (result.size() != 2)
|
||||||
|
return {};
|
||||||
|
const auto ref = result.at(1);
|
||||||
|
#else
|
||||||
|
const auto ref = tag;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<Asset> found_assets;
|
||||||
|
|
||||||
|
// FIXME: This is mildly inefficient.
|
||||||
|
// Finds assets based on a hierarchy of regex search strings.
|
||||||
|
const auto find_asset = [&found_assets, ref, this](const std::string& name,
|
||||||
|
const std::vector<std::string>& suffixes) {
|
||||||
|
for (const std::string& asset : assets) {
|
||||||
|
for (const auto& suffix : suffixes) {
|
||||||
|
if (asset.ends_with(suffix)) {
|
||||||
|
const std::string_view asset_sv = asset;
|
||||||
|
const size_t pos = asset_sv.find_last_of('/');
|
||||||
|
const std::string_view filename =
|
||||||
|
(pos != std::string_view::npos) ? asset_sv.substr(pos + 1) : asset_sv;
|
||||||
|
|
||||||
|
found_assets.emplace_back(Asset{
|
||||||
|
.name = name,
|
||||||
|
.url = host,
|
||||||
|
.path = asset,
|
||||||
|
.filename = std::string{filename},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifdef ARCHITECTURE_x86_64
|
||||||
|
find_asset("Standard", {"amd64-msvc-standard.exe", "amd64-msvc-standard.zip", "mingw-amd64-gcc-standard.exe", "mingw-amd64-gcc-standard.zip"});
|
||||||
|
find_asset("PGO", {"mingw-amd64-clang-pgo.exe", "mingw-amd64-clang-pgo.zip"});
|
||||||
|
#elif defined(ARCHITECTURE_arm64)
|
||||||
|
find_asset("Standard", {"mingw-arm64-clang-standard.exe", "mingw-arm64-clang-standard.zip"});
|
||||||
|
find_asset("PGO", {"mingw-arm64-clang-pgo.exe", "mingw-arm64-clang-pgo.zip"});
|
||||||
|
#endif
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#ifdef ARCHITECTURE_arm64
|
||||||
|
find_asset("Standard", {".dmg", ".tar.gz"});
|
||||||
|
#endif
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
#ifdef ARCHITECTURE_x86_64
|
||||||
|
find_asset("Standard", {"chromeos.apk"});
|
||||||
|
#elif defined(ARCHITECTURE_arm64)
|
||||||
|
#ifdef YUZU_LEGACY
|
||||||
|
find_asset("Standard", {"legacy.apk"});
|
||||||
|
#elif defined(GENSHIN_SPOOF)
|
||||||
|
find_asset("Standard", {"optimized.apk"});
|
||||||
|
#else
|
||||||
|
find_asset("Standard", {"standard.apk"});
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
return found_assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u64 ParseIsoTimestamp(const std::string& iso) {
|
||||||
|
if (iso.empty())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
std::string buf = iso;
|
||||||
|
if (buf.back() == 'Z')
|
||||||
|
buf.pop_back();
|
||||||
|
|
||||||
|
std::tm tm{};
|
||||||
|
std::istringstream ss(buf);
|
||||||
|
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
|
||||||
|
if (ss.fail())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
return static_cast<u64>(_mkgmtime(&tm));
|
||||||
|
#else
|
||||||
|
return static_cast<u64>(timegm(&tm));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Release> Release::FromJson(const nlohmann::json& json, const std::string& host,
|
||||||
|
const std::string& repo) {
|
||||||
|
Release rel;
|
||||||
|
if (!json.is_object())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
rel.tag = json.value("tag_name", std::string{});
|
||||||
|
if (rel.tag.empty())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
rel.title = json.value("name", rel.tag);
|
||||||
|
rel.id = json.value("id", std::hash<std::string>{}(rel.title));
|
||||||
|
|
||||||
|
rel.published = ParseIsoTimestamp(json.value("published_at", std::string{}));
|
||||||
|
rel.prerelease = json.value("prerelease", false);
|
||||||
|
|
||||||
|
auto body = json.value("body", rel.title);
|
||||||
|
boost::replace_all(body, "\\r", "");
|
||||||
|
boost::replace_all(body, "\\n", "\n");
|
||||||
|
rel.body = body;
|
||||||
|
|
||||||
|
rel.host = host;
|
||||||
|
|
||||||
|
const auto release_base =
|
||||||
|
fmt::format("{}/{}/releases", Common::g_build_auto_update_website, repo);
|
||||||
|
const auto fallback_html = fmt::format("{}/tag/{}", release_base, rel.tag);
|
||||||
|
rel.html_url = json.value("html_url", fallback_html);
|
||||||
|
|
||||||
|
// This is our own "fake" API.
|
||||||
|
if (json.contains("base")) {
|
||||||
|
const auto base = json.value("base", fmt::format("https://{}", Common::g_build_auto_update_api));
|
||||||
|
rel.base_download_url = fmt::format("{}/{}", base, rel.tag);
|
||||||
|
|
||||||
|
// Assets are easy :)
|
||||||
|
rel.assets = json.value("assets", std::vector<std::string>{});
|
||||||
|
} else {
|
||||||
|
const auto base_download_url = fmt::format("/{}/releases/download/{}", repo, rel.tag);
|
||||||
|
|
||||||
|
rel.base_download_url = base_download_url;
|
||||||
|
|
||||||
|
// assets are a bit more complex here. :(
|
||||||
|
std::vector<std::string> assets;
|
||||||
|
const nlohmann::json& arr = json["assets"];
|
||||||
|
for (const auto &obj : arr) {
|
||||||
|
const auto url = obj.value("browser_download_url", std::string{});
|
||||||
|
assets.emplace_back(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
rel.assets = assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rel;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Release> Release::FromJson(const std::string_view& json, const std::string& host,
|
||||||
|
const std::string& repo) {
|
||||||
|
try {
|
||||||
|
return FromJson(nlohmann::json::parse(json), host, repo);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
LOG_WARNING(Common, "Failed to parse JSON: {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Release> Release::ListFromJson(const nlohmann::json& json, const std::string& host,
|
||||||
|
const std::string& repo) {
|
||||||
|
if (!json.is_array())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<Release> releases;
|
||||||
|
for (const auto& obj : json) {
|
||||||
|
auto rel = Release::FromJson(obj, host, repo);
|
||||||
|
if (rel)
|
||||||
|
releases.emplace_back(rel.value());
|
||||||
|
}
|
||||||
|
return releases;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Release> Release::ListFromJson(const std::string_view& json, const std::string& host,
|
||||||
|
const std::string& repo) {
|
||||||
|
try {
|
||||||
|
return ListFromJson(nlohmann::json::parse(json), host, repo);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
LOG_WARNING(Common, "Failed to parse JSON: {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> MakeRequest(const std::string& url, const std::string& path) {
|
||||||
|
try {
|
||||||
|
constexpr std::size_t timeout_seconds = 15;
|
||||||
|
|
||||||
|
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(url);
|
||||||
|
client->set_connection_timeout(timeout_seconds);
|
||||||
|
client->set_read_timeout(timeout_seconds);
|
||||||
|
client->set_write_timeout(timeout_seconds);
|
||||||
|
|
||||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||||
|
client->load_ca_cert_store(kCert, sizeof(kCert));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (client == nullptr) {
|
||||||
|
LOG_ERROR(Common, "Invalid URL {}{}", url, path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
httplib::Request request{
|
||||||
|
.method = "GET",
|
||||||
|
.path = path,
|
||||||
|
};
|
||||||
|
|
||||||
|
client->set_follow_location(true);
|
||||||
|
httplib::Result result = client->send(request);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
LOG_ERROR(Common, "GET to {}{} returned null", url, path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& response = result.value();
|
||||||
|
if (response.status >= 400) {
|
||||||
|
LOG_ERROR(Common, "GET to {}{} returned error status code: {}", url, path,
|
||||||
|
response.status);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!response.headers.contains("content-type")) {
|
||||||
|
LOG_ERROR(Common, "GET to {}{} returned no content", url, path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body;
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
LOG_ERROR(Common, "GET to {}{} failed during update check: {}", url, path, e.what());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Release> GetReleases() {
|
||||||
|
const auto body = GetReleasesBody();
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
LOG_WARNING(Common, "Failed to get stable releases");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string_view body_str = body.value();
|
||||||
|
const auto url = fmt::format("https://{}", Common::g_build_auto_update_stable_api);
|
||||||
|
return Release::ListFromJson(body_str, url, Common::g_build_auto_update_stable_repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Release> GetLatestRelease() {
|
||||||
|
const auto releases_path = Common::g_build_auto_update_api_path;
|
||||||
|
const auto url = fmt::format("https://{}", Common::g_build_auto_update_api);
|
||||||
|
|
||||||
|
const auto body = MakeRequest(url, releases_path);
|
||||||
|
if (!body) {
|
||||||
|
LOG_WARNING(Common, "Failed to get latest release");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string_view body_str = body.value();
|
||||||
|
return Release::FromJson(body_str, url, Common::g_build_auto_update_repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> GetReleasesBody() {
|
||||||
|
const auto releases_path =
|
||||||
|
fmt::format("/{}/{}/releases", Common::g_build_auto_update_stable_api_path,
|
||||||
|
Common::g_build_auto_update_stable_repo);
|
||||||
|
const auto url = fmt::format("https://{}", Common::g_build_auto_update_stable_api);
|
||||||
|
|
||||||
|
return MakeRequest(url, releases_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common::Net
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Common::Net {
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
std::string name;
|
||||||
|
std::string url;
|
||||||
|
std::string path;
|
||||||
|
std::string filename;
|
||||||
|
} Asset;
|
||||||
|
|
||||||
|
typedef struct Release {
|
||||||
|
std::string title;
|
||||||
|
std::string body;
|
||||||
|
std::string tag;
|
||||||
|
std::string base_download_url;
|
||||||
|
std::string html_url;
|
||||||
|
std::string host;
|
||||||
|
|
||||||
|
std::vector<std::string> assets;
|
||||||
|
|
||||||
|
u64 id;
|
||||||
|
u64 published;
|
||||||
|
bool prerelease;
|
||||||
|
|
||||||
|
// Get the relevant list of assets for the current platform.
|
||||||
|
std::vector<Asset> GetPlatformAssets() const;
|
||||||
|
|
||||||
|
static std::optional<Release> FromJson(const nlohmann::json& json, const std::string &host, const std::string& repo);
|
||||||
|
static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo);
|
||||||
|
static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo);
|
||||||
|
static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo);
|
||||||
|
} Release;
|
||||||
|
|
||||||
|
// Make a request via httplib, and return the response body if applicable.
|
||||||
|
std::optional<std::string> MakeRequest(const std::string &url, const std::string &path);
|
||||||
|
|
||||||
|
// Get all of the latest stable releases.
|
||||||
|
std::vector<Release> GetReleases();
|
||||||
|
|
||||||
|
// Get all of the latest stable releases as text.
|
||||||
|
std::optional<std::string> GetReleasesBody();
|
||||||
|
|
||||||
|
// Get the latest release of the current channel.
|
||||||
|
std::optional<Release> GetLatestRelease();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
@@ -142,10 +142,8 @@ struct PageTable {
|
|||||||
VirtualBuffer<PageEntryData> entries;
|
VirtualBuffer<PageEntryData> entries;
|
||||||
static_assert(sizeof(PageEntryData) == 32);
|
static_assert(sizeof(PageEntryData) == 32);
|
||||||
|
|
||||||
std::size_t current_address_space_width_in_bits{};
|
|
||||||
|
|
||||||
u8* fastmem_arena{};
|
u8* fastmem_arena{};
|
||||||
|
std::size_t current_address_space_width_in_bits{};
|
||||||
std::size_t page_size{};
|
std::size_t page_size{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
#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 {
|
||||||
@@ -45,5 +48,8 @@ constexpr const char g_build_auto_update_website[] = BUILD_AUTO_UPDATE_WEBSITE;
|
|||||||
constexpr const char g_build_auto_update_api[] = BUILD_AUTO_UPDATE_API;
|
constexpr const char g_build_auto_update_api[] = 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
|
||||||
|
|||||||
@@ -28,5 +28,8 @@ extern const char g_build_auto_update_website[];
|
|||||||
extern const char g_build_auto_update_api[];
|
extern const char g_build_auto_update_api[];
|
||||||
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
|
||||||
|
|||||||
+18
-17
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -21,7 +24,9 @@ public:
|
|||||||
// "with the current allocator");
|
// "with the current allocator");
|
||||||
|
|
||||||
constexpr VirtualBuffer() = default;
|
constexpr VirtualBuffer() = default;
|
||||||
explicit VirtualBuffer(std::size_t count) : alloc_size{count * sizeof(T)} {
|
explicit VirtualBuffer(std::size_t count) noexcept
|
||||||
|
: alloc_size{count * sizeof(T)}
|
||||||
|
{
|
||||||
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,8 +38,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),
|
: alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr), nullptr}
|
||||||
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);
|
||||||
@@ -42,35 +47,31 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resize(std::size_t count) {
|
void resize(std::size_t count) noexcept {
|
||||||
const auto new_size = count * sizeof(T);
|
if (auto const new_size = count * sizeof(T); new_size != alloc_size) {
|
||||||
if (new_size == alloc_size) {
|
FreeMemoryPages(base_ptr, alloc_size);
|
||||||
return;
|
alloc_size = new_size;
|
||||||
|
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
FreeMemoryPages(base_ptr, alloc_size);
|
|
||||||
|
|
||||||
alloc_size = new_size;
|
|
||||||
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr const T& operator[](std::size_t index) const {
|
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept {
|
||||||
return base_ptr[index];
|
return base_ptr[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr T& operator[](std::size_t index) {
|
[[nodiscard]] constexpr T& operator[](std::size_t index) noexcept {
|
||||||
return base_ptr[index];
|
return base_ptr[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr T* data() {
|
[[nodiscard]] constexpr T* data() noexcept {
|
||||||
return base_ptr;
|
return base_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr const T* data() const {
|
[[nodiscard]] constexpr const T* data() const noexcept {
|
||||||
return base_ptr;
|
return base_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr std::size_t size() const {
|
[[nodiscard]] constexpr std::size_t size() const noexcept {
|
||||||
return alloc_size / sizeof(T);
|
return alloc_size / sizeof(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-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" type="code_ptr"/>
|
<reg name="lr" bitsize="32"/>
|
||||||
<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 2025 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
|
||||||
|
|
||||||
// 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,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -89,7 +92,7 @@ public:
|
|||||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
virtual size_t 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 2025 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
|
||||||
|
|
||||||
// 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 2025 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
|
||||||
|
|
||||||
// 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,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -52,7 +55,7 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s
|
|||||||
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
|
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,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -164,7 +167,7 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
|
|||||||
R_SUCCEED_IF(size == 0);
|
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,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -18,7 +21,7 @@ size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
|||||||
SparseStorage* self = const_cast<SparseStorage*>(this);
|
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));
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -191,8 +191,7 @@ Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start
|
|||||||
m_cached_physical_heap_region = nullptr;
|
m_cached_physical_heap_region = nullptr;
|
||||||
|
|
||||||
// Initialize our implementation.
|
// Initialize our implementation.
|
||||||
m_impl = std::make_unique<Common::PageTable>();
|
m_impl.Resize(m_address_space_width, PageBits);
|
||||||
m_impl->Resize(m_address_space_width, PageBits);
|
|
||||||
|
|
||||||
// Set the tracking memory.
|
// Set the tracking memory.
|
||||||
m_memory = std::addressof(memory);
|
m_memory = std::addressof(memory);
|
||||||
@@ -202,13 +201,7 @@ 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,
|
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) {
|
||||||
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;
|
||||||
@@ -319,14 +312,10 @@ 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) *
|
alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
|
||||||
RegionAlignment;
|
heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
|
||||||
heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
|
stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
|
||||||
RegionAlignment;
|
kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
|
||||||
stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
|
|
||||||
RegionAlignment;
|
|
||||||
kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
|
|
||||||
RegionAlignment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup heap and alias regions.
|
// Setup heap and alias regions.
|
||||||
@@ -445,15 +434,13 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
|
|||||||
ASSERT(heap_last < kmap_start || kmap_last < heap_start);
|
ASSERT(heap_last < kmap_start || kmap_last < heap_start);
|
||||||
|
|
||||||
// Initialize our implementation.
|
// Initialize our implementation.
|
||||||
m_impl = std::make_unique<Common::PageTable>();
|
m_impl.Resize(m_address_space_width, PageBits);
|
||||||
m_impl->Resize(m_address_space_width, PageBits);
|
|
||||||
|
|
||||||
// Set the tracking memory.
|
// 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,
|
R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, m_memory_block_slab_manager));
|
||||||
m_memory_block_slab_manager));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result KPageTableBase::FinalizeProcess() {
|
Result KPageTableBase::FinalizeProcess() {
|
||||||
@@ -476,7 +463,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,9 +501,6 @@ 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 {
|
||||||
@@ -2349,7 +2333,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.
|
||||||
@@ -2360,7 +2344,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;
|
||||||
}
|
}
|
||||||
@@ -5730,14 +5714,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.
|
||||||
@@ -5754,7 +5738,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();
|
||||||
}
|
}
|
||||||
@@ -5788,7 +5772,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;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-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;
|
||||||
std::unique_ptr<Common::PageTable> m_impl{};
|
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,26 +300,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Core::Memory::Memory& GetMemory() {
|
[[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return *m_memory; }
|
||||||
return *m_memory;
|
[[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return *m_memory; }
|
||||||
}
|
[[nodiscard]] Common::PageTable& GetImpl() noexcept { return m_impl; }
|
||||||
|
[[nodiscard]] Common::PageTable const& GetImpl() const noexcept { return m_impl; }
|
||||||
Core::Memory::Memory& GetMemory() const {
|
[[nodiscard]] size_t GetNumGuardPages() const noexcept { return this->IsKernel() ? 1 : 4; }
|
||||||
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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -33,21 +33,10 @@ public:
|
|||||||
m_page_table.Finalize();
|
m_page_table.Finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::Memory::Memory& GetMemory() {
|
[[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return m_page_table.GetMemory(); }
|
||||||
return m_page_table.GetMemory();
|
[[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return m_page_table.GetMemory(); }
|
||||||
}
|
[[nodiscard]] Common::PageTable& GetImpl() noexcept { return m_page_table.GetImpl(); }
|
||||||
|
[[nodiscard]] Common::PageTable const& GetImpl() const noexcept { return m_page_table.GetImpl(); }
|
||||||
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();
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ 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);
|
||||||
|
|
||||||
@@ -97,11 +105,16 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine why we stopped.
|
// Determine why we stopped.
|
||||||
const bool supervisor_call = True(hr & Core::HaltReason::SupervisorCall);
|
// If a step completed successfully, skip other halt reason handlers —
|
||||||
const bool prefetch_abort = True(hr & Core::HaltReason::PrefetchAbort);
|
// the step takes priority (e.g. step may also set InstructionBreakpoint
|
||||||
const bool breakpoint = True(hr & Core::HaltReason::InstructionBreakpoint);
|
// if the next instruction happens to be a breakpoint).
|
||||||
const bool data_abort = True(hr & Core::HaltReason::DataAbort);
|
const bool step_completed = True(hr & Core::HaltReason::StepThread)
|
||||||
const bool interrupt = True(hr & Core::HaltReason::BreakLoop);
|
&& thread->GetStepState() == StepState::StepPerformed;
|
||||||
|
const bool supervisor_call = !step_completed && True(hr & Core::HaltReason::SupervisorCall);
|
||||||
|
const bool prefetch_abort = !step_completed && True(hr & Core::HaltReason::PrefetchAbort);
|
||||||
|
const bool breakpoint = !step_completed && True(hr & Core::HaltReason::InstructionBreakpoint);
|
||||||
|
const bool data_abort = !step_completed && True(hr & Core::HaltReason::DataAbort);
|
||||||
|
const bool interrupt = !step_completed && True(hr & Core::HaltReason::BreakLoop);
|
||||||
|
|
||||||
// Since scheduling may occur here, we cannot use any cached
|
// Since scheduling may occur here, we cannot use any cached
|
||||||
// state after returning from calls we make.
|
// state after returning from calls we make.
|
||||||
@@ -111,6 +124,11 @@ 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);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// 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"
|
||||||
@@ -22,10 +24,8 @@
|
|||||||
#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,9 +35,6 @@
|
|||||||
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;
|
||||||
@@ -66,24 +63,6 @@ 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 {};
|
||||||
|
|
||||||
@@ -100,8 +79,9 @@ 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_INFO(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path);
|
LOG_DEBUG(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);
|
||||||
@@ -226,67 +206,6 @@ 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;
|
||||||
@@ -342,9 +261,7 @@ std::string SanitizeMarkdown(std::string_view markdown) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FormatBody(const nlohmann::json& release, std::string_view title) {
|
std::string FormatBody(std::string body, const 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);
|
||||||
}
|
}
|
||||||
@@ -375,52 +292,32 @@ std::string FormatBody(const nlohmann::json& release, std::string_view title) {
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImportReleases(std::string_view json_text) {
|
void ImportReleases(const std::vector<Common::Net::Release> &releases) {
|
||||||
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 : root) {
|
for (const auto& rel : releases) {
|
||||||
if (!rel.is_object()) continue;
|
const u32 news_id = u32(rel.id & 0x7FFFFFFF);
|
||||||
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 : root) {
|
for (const auto& rel : releases) {
|
||||||
if (!rel.is_object()) continue;
|
const std::string title = rel.title;
|
||||||
|
const std::string body = rel.body;
|
||||||
|
const std::string html_url = rel.html_url;
|
||||||
|
|
||||||
std::string title = rel.value("name", rel.value("tag_name", std::string{}));
|
const u32 news_id = u32(rel.id & 0x7FFFFFFF);
|
||||||
if (title.empty()) continue;
|
const u64 published = rel.published;
|
||||||
|
|
||||||
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.value("prerelease", false) ? 1500 : 2500;
|
const u32 priority = rel.prerelease ? 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(rel, title), title, published,
|
auto payload = BuildMsgpack(title, FormatBody(body, title), title, published,
|
||||||
pickup_limit, priority, {"en"}, author, {},
|
pickup_limit, priority, {"en"}, author, {},
|
||||||
rel.value("html_url", std::string{}), news_id);
|
html_url, news_id);
|
||||||
|
|
||||||
const std::string news_id_str = fmt::format("LA{:020}", news_id);
|
const std::string news_id_str = fmt::format("LA{:020}", rel.id);
|
||||||
|
|
||||||
GithubNewsMeta meta{
|
GithubNewsMeta meta{
|
||||||
.news_id = news_id_str,
|
.news_id = news_id_str,
|
||||||
@@ -565,15 +462,21 @@ void EnsureBuiltinNewsLoaded() {
|
|||||||
LoadDefaultLogos();
|
LoadDefaultLogos();
|
||||||
|
|
||||||
if (const auto cached = ReadCachedJson()) {
|
if (const auto cached = ReadCachedJson()) {
|
||||||
ImportReleases(*cached);
|
const std::string_view body = cached.value();
|
||||||
LOG_DEBUG(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size());
|
const auto releases = Common::Net::Release::ListFromJson(body, Common::g_build_auto_update_stable_api, Common::g_build_auto_update_stable_repo);
|
||||||
|
ImportReleases(releases);
|
||||||
|
|
||||||
|
LOG_INFO(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread([] {
|
std::thread([] {
|
||||||
if (const auto fresh = DownloadReleasesJson()) {
|
if (const auto fresh = Common::Net::GetReleasesBody()) {
|
||||||
WriteCachedJson(*fresh);
|
const std::string_view body = fresh.value();
|
||||||
ImportReleases(*fresh);
|
WriteCachedJson(body);
|
||||||
LOG_DEBUG(Service_BCAT, "news: {} entries updated from Forgejo", NewsStorage::Instance().ListAll().size());
|
const auto releases = Common::Net::Release::ListFromJson(body, Common::g_build_auto_update_stable_api, Common::g_build_auto_update_stable_repo);
|
||||||
|
ImportReleases(releases);
|
||||||
|
|
||||||
|
LOG_INFO(Service_BCAT, "news: {} entries updated from Forgejo", NewsStorage::Instance().ListAll().size());
|
||||||
}
|
}
|
||||||
}).detach();
|
}).detach();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
|
host1x.gmmu_manager.Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
|
||||||
handle_description.aligned_size);
|
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,12 +169,8 @@ 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) {
|
||||||
auto& gmmu_allocator = host1x.Allocator();
|
u32 address = host1x.Allocator().Allocate(u32(handle_description->aligned_size));
|
||||||
auto& gmmu = host1x.GMMU();
|
host1x.gmmu_manager.Map(GPUVAddr(address), handle_description->d_address, handle_description->aligned_size);
|
||||||
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,6 +10,7 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -130,12 +131,12 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
|
|||||||
|
|
||||||
const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
|
const auto 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 = std::make_shared<VM::Allocator>(start_pages, end_pages);
|
vm.small_page_allocator.emplace(start_pages, end_pages);
|
||||||
|
|
||||||
const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
|
const auto 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 = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
|
vm.big_page_allocator.emplace(start_big_pages, end_big_pages);
|
||||||
|
|
||||||
gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split,
|
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);
|
||||||
@@ -188,8 +189,8 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allocation_map[params.offset] = {
|
allocation_map[params.offset] = {
|
||||||
.size = size,
|
|
||||||
.mappings{},
|
.mappings{},
|
||||||
|
.size = size,
|
||||||
.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,
|
||||||
@@ -199,71 +200,52 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
|
void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
|
||||||
auto mapping{mapping_map.at(offset)};
|
auto const it = mapping_map.find(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,
|
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset, params.pages, params.page_size);
|
||||||
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()) {
|
||||||
try {
|
auto const allocation = it->second;
|
||||||
auto allocation{allocation_map[params.offset]};
|
if (allocation.page_size != params.page_size || allocation.size != (u64(params.pages) * params.page_size))
|
||||||
|
|
||||||
if (allocation.page_size != params.page_size ||
|
|
||||||
allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
|
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& mapping : allocation.mappings) {
|
for (const auto mapping_offset : allocation.mappings)
|
||||||
FreeMappingLocked(mapping->offset);
|
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
|
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator : *vm.big_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};
|
||||||
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),
|
allocator.Free(u32(params.offset >> page_size_bits), u32(allocation.size >> page_size_bits));
|
||||||
static_cast<u32>(allocation.size >> page_size_bits));
|
|
||||||
allocation_map.erase(params.offset);
|
allocation_map.erase(params.offset);
|
||||||
} catch (const std::out_of_range&) {
|
return NvResult::Success;
|
||||||
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) {
|
||||||
@@ -327,26 +309,18 @@ 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) {
|
||||||
try {
|
if (auto const it = mapping_map.find(params.offset); it != mapping_map.end()) {
|
||||||
auto mapping{mapping_map.at(params.offset)};
|
auto const mapping = it->second;
|
||||||
|
if (mapping.size < params.mapping_size) {
|
||||||
if (mapping->size < params.mapping_size) {
|
LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#X}", params.offset);
|
||||||
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);
|
||||||
u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
|
VAddr device_address{mapping.ptr + params.buffer_offset};
|
||||||
VAddr device_address{mapping->ptr + params.buffer_offset};
|
gmmu->Map(gpu_address, device_address, params.mapping_size, Tegra::PTEKind(params.kind), mapping.big_page);
|
||||||
|
|
||||||
gmmu->Map(gpu_address, device_address, params.mapping_size,
|
|
||||||
static_cast<Tegra::PTEKind>(params.kind), mapping->big_page);
|
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
} catch (const std::out_of_range&) {
|
} else {
|
||||||
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}",
|
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}", params.offset);
|
||||||
params.offset);
|
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,8 +330,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
|||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
DAddr device_address{
|
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset);
|
||||||
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{[&]() {
|
||||||
@@ -381,32 +354,22 @@ 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),
|
gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind), use_big_pages);
|
||||||
use_big_pages);
|
|
||||||
|
|
||||||
auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
|
alloc->second.mappings.push_back(params.offset);
|
||||||
true, use_big_pages, alloc->second.sparse)};
|
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, true, use_big_pages, alloc->second.sparse));
|
||||||
alloc->second.mappings.push_back(mapping);
|
|
||||||
mapping_map[params.offset] = mapping;
|
|
||||||
} 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 = static_cast<u64>(allocator.Allocate(
|
params.offset = u64(allocator.Allocate(u32(Common::AlignUp(size, page_size) >> page_size_bits))) << page_size_bits;
|
||||||
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);
|
||||||
gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size),
|
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false));
|
||||||
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);
|
||||||
@@ -415,37 +378,32 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
|
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
|
||||||
if (map_buffer_offsets.find(params.offset) != map_buffer_offsets.end()) {
|
std::scoped_lock lock(mutex);
|
||||||
|
if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) {
|
||||||
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
|
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 mapping{mapping_map.at(params.offset)};
|
auto const it = mapping_map.find(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);
|
||||||
mapping_map.erase(params.offset);
|
map_buffer_offsets.erase(offset_it);
|
||||||
map_buffer_offsets.erase(params.offset);
|
|
||||||
}
|
}
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
@@ -478,8 +436,7 @@ 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,
|
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr, params.buf_size);
|
||||||
params.buf_size);
|
|
||||||
|
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
|
|
||||||
|
|||||||
@@ -165,35 +165,36 @@ 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;
|
||||||
bool fixed;
|
NvCore::NvMap::Handle::Id handle;
|
||||||
bool big_page; // Only valid if fixed == false
|
bool fixed : 1;
|
||||||
bool sparse_alloc;
|
bool big_page : 1; // Only valid if fixed == false
|
||||||
|
bool sparse_alloc : 1;
|
||||||
|
|
||||||
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_,
|
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
|
||||||
bool big_page_, bool sparse_alloc_)
|
: ptr(ptr_), offset(offset_), size(size_), handle(handle_)
|
||||||
: handle(handle_), ptr(ptr_), offset(offset_), size(size_), fixed(fixed_),
|
, fixed(fixed_), big_page(big_page_), sparse_alloc(sparse_alloc_)
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<u64, std::shared_ptr<Mapping>>
|
//!< This maps the base addresses of mapped buffers to their total sizes and
|
||||||
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::mutex mutex; //!< Locks all AS operations
|
std::map<u64, Allocation> allocation_map;
|
||||||
|
std::mutex mutex; //!< Locks all AS operations
|
||||||
|
|
||||||
struct VM {
|
struct VM {
|
||||||
static constexpr u32 YUZU_PAGESIZE{0x1000};
|
static constexpr u32 YUZU_PAGESIZE{0x1000};
|
||||||
@@ -213,9 +214,8 @@ private:
|
|||||||
|
|
||||||
using Allocator = Common::FlatAllocator<u32, 0, 32>;
|
using Allocator = Common::FlatAllocator<u32, 0, 32>;
|
||||||
|
|
||||||
std::unique_ptr<Allocator> big_page_allocator;
|
std::optional<Allocator> big_page_allocator;
|
||||||
std::shared_ptr<Allocator>
|
std::optional<Allocator> small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
|
||||||
small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
|
|
||||||
|
|
||||||
bool initialised{};
|
bool initialised{};
|
||||||
} vm;
|
} vm;
|
||||||
|
|||||||
@@ -228,9 +228,8 @@ 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
|
||||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||||
? ::Settings::values.rng_seed.GetValue()
|
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||||
: 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()) {
|
||||||
|
|||||||
@@ -89,9 +89,8 @@ 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
|
||||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||||
? ::Settings::values.rng_seed.GetValue()
|
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||||
: 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()) {
|
||||||
|
|||||||
@@ -242,9 +242,8 @@ 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
|
||||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||||
? ::Settings::values.rng_seed.GetValue()
|
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||||
: 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) {
|
||||||
(void)code;
|
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
|
||||||
(void)ctx;
|
code.CMGT(Qresult->D2(), Qa->D2(), Qb->D2());
|
||||||
(void)inst;
|
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
|
||||||
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) {
|
||||||
(void)code;
|
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
|
||||||
(void)ctx;
|
code.CMHI(Qresult->D2(), Qa->D2(), Qb->D2());
|
||||||
(void)inst;
|
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
|
||||||
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) {
|
||||||
(void)code;
|
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
|
||||||
(void)ctx;
|
code.CMGT(Qresult->D2(), Qb->D2(), Qa->D2());
|
||||||
(void)inst;
|
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
|
||||||
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) {
|
||||||
(void)code;
|
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
|
||||||
(void)ctx;
|
code.CMHI(Qresult->D2(), Qb->D2(), Qa->D2());
|
||||||
(void)inst;
|
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
|
||||||
UNREACHABLE();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// 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::LinkBlock{ir.current_location->SetPC(target)});
|
ir.SetTerm(IR::Term::LinkBlockFast{ir.current_location->SetPC(target)});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,146 +5,45 @@
|
|||||||
// 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/logging.h"
|
#include "common/net/net.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "update_checker.h"
|
#include "update_checker.h"
|
||||||
|
|
||||||
#include "common/httplib.h"
|
#include "common/logging.h"
|
||||||
|
|
||||||
#ifdef YUZU_BUNDLED_OPENSSL
|
std::optional<Common::Net::Release> UpdateChecker::GetUpdate() {
|
||||||
#include <openssl/cert.h>
|
const auto latest = Common::Net::GetLatestRelease();
|
||||||
#endif
|
if (!latest) return std::nullopt;
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
LOG_INFO(Frontend, "Received update {}", latest->title);
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::string path)
|
#ifdef NIGHTLY_BUILD
|
||||||
{
|
std::vector<std::string> result;
|
||||||
try {
|
|
||||||
constexpr std::size_t timeout_seconds = 15;
|
|
||||||
|
|
||||||
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(url);
|
boost::split(result, latest->tag, boost::is_any_of("."));
|
||||||
client->set_connection_timeout(timeout_seconds);
|
if (result.size() != 2)
|
||||||
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;
|
return std::nullopt;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease() {
|
const std::string tag = result[1];
|
||||||
#ifdef YUZU_BUNDLED_OPENSSL
|
|
||||||
const auto update_check_url = fmt::format("https://{}", Common::g_build_auto_update_api);
|
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
|
||||||
|
if (result.empty())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
const std::string build = result[0];
|
||||||
#else
|
#else
|
||||||
const auto update_check_url = std::string{Common::g_build_auto_update_api};
|
const std::string tag = latest->tag;
|
||||||
|
const std::string build = Common::g_build_version;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto update_check_path = std::string{Common::g_build_auto_update_api_path};
|
if (tag != build)
|
||||||
try {
|
return latest;
|
||||||
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;
|
|
||||||
|
|
||||||
boost::split(result, latest_release_tag->tag, boost::is_any_of("."));
|
|
||||||
if (result.size() != 2)
|
|
||||||
goto empty;
|
|
||||||
tag = result[1];
|
|
||||||
|
|
||||||
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
|
|
||||||
if (result.empty())
|
|
||||||
goto empty;
|
|
||||||
build = result[0];
|
|
||||||
} else {
|
|
||||||
tag = latest_release_tag->tag;
|
|
||||||
build = Common::g_build_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag != build)
|
|
||||||
return latest_release_tag.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
empty:
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include "common/net/net.h"
|
||||||
|
|
||||||
namespace UpdateChecker {
|
namespace UpdateChecker {
|
||||||
|
|
||||||
typedef struct {
|
std::optional<Common::Net::Release> GetUpdate();
|
||||||
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
|
||||||
|
|||||||
@@ -157,11 +157,14 @@ void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t c
|
|||||||
if (!controller.device->IsConnected()) {
|
if (!controller.device->IsConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto* shared_memory = controller.shared_memory;
|
if (auto* shared_memory = controller.shared_memory; 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:
|
||||||
@@ -180,6 +183,10 @@ 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;
|
||||||
@@ -801,7 +808,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, "DisconnectNpad: shared_memory is null for npad_id={}", npad_id);
|
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id);
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
// Don't reset shared_memory->assignment_mode this value is persistent
|
// Don't reset shared_memory->assignment_mode this value is persistent
|
||||||
@@ -1195,11 +1202,10 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
|
|||||||
|
|
||||||
AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) {
|
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->applet_footer_type,
|
.footer = shared_memory ? shared_memory->applet_footer_type : AppletFooterUiType::None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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/ffmpeg.cpp
|
set_source_files_properties(host1x/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,7 +44,6 @@ 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
|
||||||
@@ -72,9 +71,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/codecs/vp9_types.h
|
host1x/codec_types.h
|
||||||
host1x/ffmpeg/ffmpeg.cpp
|
host1x/ffmpeg.cpp
|
||||||
host1x/ffmpeg/ffmpeg.h
|
host1x/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
|
||||||
|
|||||||
@@ -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->GetDrawState();
|
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||||
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,30 +740,25 @@ 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->GetDrawState();
|
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||||
if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
|
if (draw_state.inline_index_draw_indexes.empty()) {
|
||||||
|
SynchronizeBuffer(buffer, channel_state->index_buffer.device_addr, size);
|
||||||
|
} else {
|
||||||
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
|
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
|
||||||
auto upload_staging = runtime.UploadStagingBuffer(size);
|
auto upload_staging = runtime.UploadStagingBuffer(size);
|
||||||
std::array<BufferCopy, 1> copies{
|
std::array<BufferCopy, 1> copies{{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = 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);
|
||||||
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 =
|
const u32 new_offset = offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
|
||||||
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,
|
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format, draw_state.index_buffer.first, draw_state.index_buffer.count, buffer, offset, size);
|
||||||
draw_state.index_buffer.first, draw_state.index_buffer.count,
|
|
||||||
buffer, offset, size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,10 +940,9 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
|||||||
return alignment > 1 && (offset % alignment) != 0;
|
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 &&
|
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size
|
||||||
size <= channel_state->uniform_buffer_skip_cache_size &&
|
&& !memory_tracker.IsRegionGpuModified(device_addr, 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()) {
|
||||||
@@ -1229,7 +1223,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->GetDrawState();
|
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||||
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/draw_manager.h"
|
#include "video_core/engines/maxwell_3d.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::DrawManager::IndirectParams* current_draw_indirect_) {
|
const Tegra::Engines::Maxwell3D::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::DrawManager::IndirectParams* current_draw_indirect{};
|
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
|
||||||
|
|
||||||
u32 last_index_count = 0;
|
u32 last_index_count = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Ryujinx Team and Contributors
|
// SPDX-FileCopyrightText: Ryujinx Team and Contributors
|
||||||
@@ -20,109 +20,101 @@
|
|||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
|
|
||||||
CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id)
|
CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id)
|
||||||
: host1x{host1x_}, memory_manager{host1x.GMMU()},
|
: host_processor(host1x_)
|
||||||
host_processor{std::make_unique<Host1x::Control>(host1x_)}, current_class{
|
, host1x{host1x_}
|
||||||
static_cast<ChClassId>(id)} {
|
, current_class{ChClassId(id)}
|
||||||
thread = std::jthread([this](std::stop_token stop_token) { ProcessEntries(stop_token); });
|
{
|
||||||
|
thread = std::jthread([this](std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||||
|
ChCommandHeaderList command_list{host1x.System().ApplicationMemory(), 0, 0};
|
||||||
|
u32 count{};
|
||||||
|
u32 method_offset{};
|
||||||
|
u32 mask{};
|
||||||
|
bool incrementing{};
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
{
|
||||||
|
std::unique_lock l{command_mutex};
|
||||||
|
command_cv.wait(l, stop_token, [this]() { return command_lists.size() > 0; });
|
||||||
|
if (stop_token.stop_requested()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_list = std::move(command_lists.front());
|
||||||
|
command_lists.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
for (const auto value : command_list) {
|
||||||
|
i++;
|
||||||
|
if (mask != 0) {
|
||||||
|
const auto lbs = static_cast<u32>(std::countr_zero(mask));
|
||||||
|
mask &= ~(1U << lbs);
|
||||||
|
ExecuteCommand(method_offset + lbs, value.raw);
|
||||||
|
continue;
|
||||||
|
} else if (count != 0) {
|
||||||
|
--count;
|
||||||
|
ExecuteCommand(method_offset, value.raw);
|
||||||
|
if (incrementing) {
|
||||||
|
++method_offset;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto mode = value.submission_mode.Value();
|
||||||
|
switch (mode) {
|
||||||
|
case ChSubmissionMode::SetClass: {
|
||||||
|
mask = value.value & 0x3f;
|
||||||
|
method_offset = value.method_offset;
|
||||||
|
current_class = ChClassId((value.value >> 6) & 0x3ff);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ChSubmissionMode::Incrementing:
|
||||||
|
case ChSubmissionMode::NonIncrementing:
|
||||||
|
count = value.value;
|
||||||
|
method_offset = value.method_offset;
|
||||||
|
incrementing = mode == ChSubmissionMode::Incrementing;
|
||||||
|
break;
|
||||||
|
case ChSubmissionMode::Mask:
|
||||||
|
mask = value.value;
|
||||||
|
method_offset = value.method_offset;
|
||||||
|
break;
|
||||||
|
case ChSubmissionMode::Immediate: {
|
||||||
|
const u32 data = value.value & 0xfff;
|
||||||
|
method_offset = value.method_offset;
|
||||||
|
ExecuteCommand(method_offset, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1, (i - 1) * sizeof(u32), command_list.size());
|
||||||
|
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", u32(mode));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CDmaPusher::~CDmaPusher() = default;
|
CDmaPusher::~CDmaPusher() = default;
|
||||||
|
|
||||||
void CDmaPusher::ProcessEntries(std::stop_token stop_token) {
|
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
|
||||||
ChCommandHeaderList command_list{host1x.System().ApplicationMemory(), 0, 0};
|
|
||||||
u32 count{};
|
|
||||||
u32 method_offset{};
|
|
||||||
u32 mask{};
|
|
||||||
bool incrementing{};
|
|
||||||
|
|
||||||
while (!stop_token.stop_requested()) {
|
|
||||||
{
|
|
||||||
std::unique_lock l{command_mutex};
|
|
||||||
command_cv.wait(l, stop_token,
|
|
||||||
[this]() { return command_lists.size() > 0; });
|
|
||||||
if (stop_token.stop_requested()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
command_list = std::move(command_lists.front());
|
|
||||||
command_lists.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
for (const auto value : command_list) {
|
|
||||||
i++;
|
|
||||||
if (mask != 0) {
|
|
||||||
const auto lbs = static_cast<u32>(std::countr_zero(mask));
|
|
||||||
mask &= ~(1U << lbs);
|
|
||||||
ExecuteCommand(method_offset + lbs, value.raw);
|
|
||||||
continue;
|
|
||||||
} else if (count != 0) {
|
|
||||||
--count;
|
|
||||||
ExecuteCommand(method_offset, value.raw);
|
|
||||||
if (incrementing) {
|
|
||||||
++method_offset;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto mode = value.submission_mode.Value();
|
|
||||||
switch (mode) {
|
|
||||||
case ChSubmissionMode::SetClass: {
|
|
||||||
mask = value.value & 0x3f;
|
|
||||||
method_offset = value.method_offset;
|
|
||||||
current_class = static_cast<ChClassId>((value.value >> 6) & 0x3ff);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ChSubmissionMode::Incrementing:
|
|
||||||
case ChSubmissionMode::NonIncrementing:
|
|
||||||
count = value.value;
|
|
||||||
method_offset = value.method_offset;
|
|
||||||
incrementing = mode == ChSubmissionMode::Incrementing;
|
|
||||||
break;
|
|
||||||
case ChSubmissionMode::Mask:
|
|
||||||
mask = value.value;
|
|
||||||
method_offset = value.method_offset;
|
|
||||||
break;
|
|
||||||
case ChSubmissionMode::Immediate: {
|
|
||||||
const u32 data = value.value & 0xfff;
|
|
||||||
method_offset = value.method_offset;
|
|
||||||
ExecuteCommand(method_offset, data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1,
|
|
||||||
(i - 1) * sizeof(u32), command_list.size());
|
|
||||||
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!",
|
|
||||||
static_cast<u32>(mode));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CDmaPusher::ExecuteCommand(u32 method, u32 arg) {
|
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}",
|
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), method, arg);
|
||||||
static_cast<u32>(current_class), method, arg);
|
host_processor.ProcessMethod(Host1x::Control::Method(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 = static_cast<u32>(arg & 0xFF);
|
const auto syncpoint_id = u32(arg & 0xFF);
|
||||||
[[maybe_unused]] const auto cond = static_cast<u32>((arg >> 8) & 0xFF);
|
[[maybe_unused]] const auto cond = u32((arg >> 8) & 0xFF);
|
||||||
LOG_TRACE(Service_NVDRV, "Class {} IncSyncpt Method, syncpt {} cond {}",
|
LOG_TRACE(Service_NVDRV, "Class {} IncSyncpt Method, syncpt {} cond {}", u32(current_class), syncpoint_id, 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}",
|
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), u32(thi_regs.method_0), arg);
|
||||||
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:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -15,11 +18,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;
|
||||||
@@ -117,25 +120,19 @@ 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);
|
||||||
|
|
||||||
std::unique_ptr<Host1x::Control> host_processor;
|
protected:
|
||||||
|
|
||||||
std::mutex command_mutex;
|
|
||||||
std::condition_variable_any command_cv;
|
|
||||||
std::deque<ChCommandHeaderList> command_lists;
|
|
||||||
std::jthread thread;
|
|
||||||
|
|
||||||
ThiRegisters thi_regs{};
|
ThiRegisters thi_regs{};
|
||||||
|
std::deque<ChCommandHeaderList> command_lists;
|
||||||
|
std::condition_variable_any command_cv;
|
||||||
|
Host1x::Control host_processor;
|
||||||
|
std::mutex command_mutex;
|
||||||
|
Host1x::Host1x& host1x;
|
||||||
ChClassId current_class;
|
ChClassId current_class;
|
||||||
|
std::jthread thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-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/draw_manager.h"
|
#include "video_core/engines/maxwell_3d.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 DrawManager::ProcessMethodCall(u32 method, u32 argument) {
|
void Maxwell3D::DrawManager::ProcessMethodCall(Maxwell3D& maxwell3d, 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(1);
|
return Clear(maxwell3d, 1);
|
||||||
case MAXWELL3D_REG_INDEX(draw.begin):
|
case MAXWELL3D_REG_INDEX(draw.begin):
|
||||||
return DrawBegin();
|
return DrawBegin(maxwell3d);
|
||||||
case MAXWELL3D_REG_INDEX(draw.end):
|
case MAXWELL3D_REG_INDEX(draw.end):
|
||||||
return DrawEnd();
|
return DrawEnd(maxwell3d);
|
||||||
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):
|
||||||
@@ -33,33 +34,29 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
|
|||||||
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(argument);
|
return DrawIndexSmall(maxwell3d, argument);
|
||||||
case MAXWELL3D_REG_INDEX(draw_inline_index):
|
case MAXWELL3D_REG_INDEX(draw_inline_index):
|
||||||
SetInlineIndexBuffer(argument);
|
SetInlineIndexBuffer(maxwell3d, argument);
|
||||||
break;
|
break;
|
||||||
case MAXWELL3D_REG_INDEX(inline_index_2x16.even):
|
case MAXWELL3D_REG_INDEX(inline_index_2x16.even):
|
||||||
SetInlineIndexBuffer(regs.inline_index_2x16.even);
|
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_2x16.even);
|
||||||
SetInlineIndexBuffer(regs.inline_index_2x16.odd);
|
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_2x16.odd);
|
||||||
break;
|
break;
|
||||||
case MAXWELL3D_REG_INDEX(inline_index_4x8.index0):
|
case MAXWELL3D_REG_INDEX(inline_index_4x8.index0):
|
||||||
SetInlineIndexBuffer(regs.inline_index_4x8.index0);
|
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index0);
|
||||||
SetInlineIndexBuffer(regs.inline_index_4x8.index1);
|
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index1);
|
||||||
SetInlineIndexBuffer(regs.inline_index_4x8.index2);
|
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index2);
|
||||||
SetInlineIndexBuffer(regs.inline_index_4x8.index3);
|
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index3);
|
||||||
break;
|
break;
|
||||||
case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
|
case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
|
||||||
DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
|
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);
|
||||||
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(regs.vertex_array_instance_subsequent.topology.Value(),
|
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);
|
||||||
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();
|
DrawTexture(maxwell3d);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -67,101 +64,87 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::Clear(u32 layer_count) {
|
void Maxwell3D::DrawManager::Clear(Maxwell3D& maxwell3d, u32 layer_count) {
|
||||||
if (maxwell3d->ShouldExecute()) {
|
if (maxwell3d.ShouldExecute()) {
|
||||||
maxwell3d->rasterizer->Clear(layer_count);
|
maxwell3d.rasterizer->Clear(layer_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawDeferred() {
|
void Maxwell3D::DrawManager::DrawDeferred(Maxwell3D& maxwell3d) {
|
||||||
if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0) {
|
if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DrawEnd(draw_state.instance_count + 1, true);
|
DrawEnd(maxwell3d, draw_state.instance_count + 1, true);
|
||||||
draw_state.instance_count = 0;
|
draw_state.instance_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
|
void Maxwell3D::DrawManager::DrawArray(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, u32 base_instance, u32 num_instances) {
|
||||||
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(false, num_instances);
|
ProcessDraw(maxwell3d, false, num_instances);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
|
void Maxwell3D::DrawManager::DrawArrayInstanced(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, bool subsequent) {
|
||||||
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(false, 1);
|
ProcessDraw(maxwell3d, false, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
|
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) {
|
||||||
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 = regs.index_buffer;
|
draw_state.index_buffer = maxwell3d.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(true, num_instances);
|
ProcessDraw(maxwell3d, true, num_instances);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawArrayIndirect(PrimitiveTopology topology) {
|
void Maxwell3D::DrawManager::DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology) {
|
||||||
draw_state.topology = topology;
|
draw_state.topology = topology;
|
||||||
|
ProcessDrawIndirect(maxwell3d);
|
||||||
ProcessDrawIndirect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawIndexedIndirect(PrimitiveTopology topology, u32 index_first,
|
void Maxwell3D::DrawManager::DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count) {
|
||||||
u32 index_count) {
|
|
||||||
const auto& regs{maxwell3d->regs};
|
|
||||||
draw_state.topology = topology;
|
draw_state.topology = topology;
|
||||||
draw_state.index_buffer = regs.index_buffer;
|
draw_state.index_buffer = maxwell3d.regs.index_buffer;
|
||||||
draw_state.index_buffer.first = index_first;
|
draw_state.index_buffer.first = index_first;
|
||||||
draw_state.index_buffer.count = index_count;
|
draw_state.index_buffer.count = index_count;
|
||||||
|
ProcessDrawIndirect(maxwell3d);
|
||||||
ProcessDrawIndirect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::SetInlineIndexBuffer(u32 index) {
|
void Maxwell3D::DrawManager::SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index) {
|
||||||
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>(index & 0x000000ff));
|
draw_state.inline_index_draw_indexes.push_back(u8(index & 0x000000ff));
|
||||||
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x0000ff00) >> 8));
|
draw_state.inline_index_draw_indexes.push_back(u8((index & 0x0000ff00) >> 8));
|
||||||
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x00ff0000) >> 16));
|
draw_state.inline_index_draw_indexes.push_back(u8((index & 0x00ff0000) >> 16));
|
||||||
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0xff000000) >> 24));
|
draw_state.inline_index_draw_indexes.push_back(u8((index & 0xff000000) >> 24));
|
||||||
draw_state.draw_mode = DrawMode::InlineIndex;
|
draw_state.draw_mode = DrawMode::InlineIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawBegin() {
|
void Maxwell3D::DrawManager::DrawBegin(Maxwell3D& maxwell3d) {
|
||||||
const auto& regs{maxwell3d->regs};
|
auto reset_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First;
|
||||||
auto reset_instance_count = regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First;
|
auto increment_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent;
|
||||||
auto increment_instance_count =
|
|
||||||
regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent;
|
|
||||||
if (reset_instance_count) {
|
if (reset_instance_count) {
|
||||||
DrawDeferred();
|
DrawDeferred(maxwell3d);
|
||||||
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 DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
|
void Maxwell3D::DrawManager::DrawEnd(Maxwell3D& maxwell3d, 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) {
|
||||||
@@ -169,119 +152,100 @@ void DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
|
|||||||
}
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case DrawMode::General:
|
case DrawMode::General:
|
||||||
draw_state.base_instance = regs.global_base_instance_index;
|
draw_state.base_instance = maxwell3d.regs.global_base_instance_index;
|
||||||
draw_state.base_index = regs.global_base_vertex_index;
|
draw_state.base_index = maxwell3d.regs.global_base_vertex_index;
|
||||||
if (draw_state.draw_indexed) {
|
if (draw_state.draw_indexed) {
|
||||||
draw_state.index_buffer = regs.index_buffer;
|
draw_state.index_buffer = maxwell3d.regs.index_buffer;
|
||||||
ProcessDraw(true, instance_count);
|
ProcessDraw(maxwell3d, true, instance_count);
|
||||||
} else {
|
} else {
|
||||||
draw_state.vertex_buffer = regs.vertex_buffer;
|
draw_state.vertex_buffer = maxwell3d.regs.vertex_buffer;
|
||||||
ProcessDraw(false, instance_count);
|
ProcessDraw(maxwell3d, 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 = regs.global_base_instance_index;
|
draw_state.base_instance = maxwell3d.regs.global_base_instance_index;
|
||||||
draw_state.base_index = regs.global_base_vertex_index;
|
draw_state.base_index = maxwell3d.regs.global_base_vertex_index;
|
||||||
draw_state.index_buffer = regs.index_buffer;
|
draw_state.index_buffer = maxwell3d.regs.index_buffer;
|
||||||
draw_state.index_buffer.count =
|
draw_state.index_buffer.count = u32(draw_state.inline_index_draw_indexes.size() / 4);
|
||||||
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(true, instance_count);
|
ProcessDraw(maxwell3d, true, instance_count);
|
||||||
draw_state.inline_index_draw_indexes.clear();
|
draw_state.inline_index_draw_indexes.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawIndexSmall(u32 argument) {
|
void Maxwell3D::DrawManager::DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument) {
|
||||||
const auto& regs{maxwell3d->regs};
|
Maxwell3D::Regs::IndexBufferSmall index_small_params{argument};
|
||||||
IndexBufferSmall index_small_params{argument};
|
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.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(true, 1);
|
ProcessDraw(maxwell3d, true, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::DrawTexture() {
|
void Maxwell3D::DrawManager::DrawTexture(Maxwell3D& maxwell3d) {
|
||||||
const auto& regs{maxwell3d->regs};
|
draw_texture_state.dst_x0 = f32(maxwell3d.regs.draw_texture.dst_x0) / 4096.f;
|
||||||
draw_texture_state.dst_x0 = static_cast<float>(regs.draw_texture.dst_x0) / 4096.f;
|
draw_texture_state.dst_y0 = f32(maxwell3d.regs.draw_texture.dst_y0) / 4096.f;
|
||||||
draw_texture_state.dst_y0 = static_cast<float>(regs.draw_texture.dst_y0) / 4096.f;
|
const auto dst_width = f32(maxwell3d.regs.draw_texture.dst_width) / 4096.f;
|
||||||
const auto dst_width = static_cast<float>(regs.draw_texture.dst_width) / 4096.f;
|
const auto dst_height = f32(maxwell3d.regs.draw_texture.dst_height) / 4096.f;
|
||||||
const auto dst_height = static_cast<float>(regs.draw_texture.dst_height) / 4096.f;
|
const bool lower_left{maxwell3d.regs.window_origin.mode != Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft};
|
||||||
const bool lower_left{regs.window_origin.mode !=
|
|
||||||
Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft};
|
|
||||||
if (lower_left) {
|
if (lower_left) {
|
||||||
draw_texture_state.dst_y0 =
|
draw_texture_state.dst_y0 = f32(maxwell3d.regs.surface_clip.height) - 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 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f;
|
draw_texture_state.src_x0 = f32(maxwell3d.regs.draw_texture.src_x0) / 4096.f;
|
||||||
draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f;
|
draw_texture_state.src_y0 = f32(maxwell3d.regs.draw_texture.src_y0) / 4096.f;
|
||||||
draw_texture_state.src_x1 =
|
draw_texture_state.src_x1 = (f32(maxwell3d.regs.draw_texture.dx_du) / 4294967296.f) * dst_width + draw_texture_state.src_x0;
|
||||||
(static_cast<float>(regs.draw_texture.dx_du) / 4294967296.f) * dst_width +
|
draw_texture_state.src_y1 = (f32(maxwell3d.regs.draw_texture.dy_dv) / 4294967296.f) * dst_height + draw_texture_state.src_y0;
|
||||||
draw_texture_state.src_x0;
|
draw_texture_state.src_sampler = maxwell3d.regs.draw_texture.src_sampler;
|
||||||
draw_texture_state.src_y1 =
|
draw_texture_state.src_texture = maxwell3d.regs.draw_texture.src_texture;
|
||||||
(static_cast<float>(regs.draw_texture.dy_dv) / 4294967296.f) * dst_height +
|
maxwell3d.rasterizer->DrawTexture();
|
||||||
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 DrawManager::UpdateTopology() {
|
void Maxwell3D::DrawManager::UpdateTopology(Maxwell3D& maxwell3d) {
|
||||||
const auto& regs{maxwell3d->regs};
|
switch (maxwell3d.regs.primitive_topology_control) {
|
||||||
switch (regs.primitive_topology_control) {
|
case Maxwell3D::Regs::PrimitiveTopologyControl::UseInBeginMethods:
|
||||||
case PrimitiveTopologyControl::UseInBeginMethods:
|
|
||||||
break;
|
break;
|
||||||
case PrimitiveTopologyControl::UseSeparateState:
|
case Maxwell3D::Regs::PrimitiveTopologyControl::UseSeparateState:
|
||||||
switch (regs.topology_override) {
|
switch (maxwell3d.regs.topology_override) {
|
||||||
case PrimitiveTopologyOverride::None:
|
case Maxwell3D::Regs::PrimitiveTopologyOverride::None:
|
||||||
break;
|
break;
|
||||||
case PrimitiveTopologyOverride::Points:
|
case Maxwell3D::Regs::PrimitiveTopologyOverride::Points:
|
||||||
draw_state.topology = PrimitiveTopology::Points;
|
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Points;
|
||||||
break;
|
break;
|
||||||
case PrimitiveTopologyOverride::Lines:
|
case Maxwell3D::Regs::PrimitiveTopologyOverride::Lines:
|
||||||
draw_state.topology = PrimitiveTopology::Lines;
|
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Lines;
|
||||||
break;
|
break;
|
||||||
case PrimitiveTopologyOverride::LineStrip:
|
case Maxwell3D::Regs::PrimitiveTopologyOverride::LineStrip:
|
||||||
draw_state.topology = PrimitiveTopology::LineStrip;
|
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::LineStrip;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
draw_state.topology = static_cast<PrimitiveTopology>(regs.topology_override);
|
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology(maxwell3d.regs.topology_override);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::ProcessDraw(bool draw_indexed, u32 instance_count) {
|
void Maxwell3D::DrawManager::ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed, u32 instance_count) {
|
||||||
LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology,
|
LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology, draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
|
||||||
draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
|
UpdateTopology(maxwell3d);
|
||||||
|
if (maxwell3d.ShouldExecute()) {
|
||||||
UpdateTopology();
|
maxwell3d.rasterizer->Draw(draw_indexed, instance_count);
|
||||||
|
|
||||||
if (maxwell3d->ShouldExecute()) {
|
|
||||||
maxwell3d->rasterizer->Draw(draw_indexed, instance_count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawManager::ProcessDrawIndirect() {
|
void Maxwell3D::DrawManager::ProcessDrawIndirect(Maxwell3D& maxwell3d) {
|
||||||
LOG_TRACE(
|
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);
|
||||||
HW_GPU,
|
UpdateTopology(maxwell3d);
|
||||||
"called, topology={}, is_indexed={}, includes_count={}, buffer_size={}, max_draw_count={}",
|
if (maxwell3d.ShouldExecute()) {
|
||||||
draw_state.topology, indirect_state.is_indexed, indirect_state.include_count,
|
maxwell3d.rasterizer->DrawIndirect();
|
||||||
indirect_state.buffer_size, indirect_state.max_draw_counts);
|
|
||||||
|
|
||||||
UpdateTopology();
|
|
||||||
|
|
||||||
if (maxwell3d->ShouldExecute()) {
|
|
||||||
maxwell3d->rasterizer->DrawIndirect();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace Tegra::Engines
|
} // namespace Tegra::Engines
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "video_core/engines/maxwell_3d.h"
|
|
||||||
|
|
||||||
namespace VideoCore {
|
|
||||||
class RasterizerInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Tegra::Engines {
|
|
||||||
using PrimitiveTopologyControl = Maxwell3D::Regs::PrimitiveTopologyControl;
|
|
||||||
using PrimitiveTopology = Maxwell3D::Regs::PrimitiveTopology;
|
|
||||||
using PrimitiveTopologyOverride = Maxwell3D::Regs::PrimitiveTopologyOverride;
|
|
||||||
using IndexBuffer = Maxwell3D::Regs::IndexBuffer;
|
|
||||||
using VertexBuffer = Maxwell3D::Regs::VertexBuffer;
|
|
||||||
using IndexBufferSmall = Maxwell3D::Regs::IndexBufferSmall;
|
|
||||||
|
|
||||||
class DrawManager {
|
|
||||||
public:
|
|
||||||
enum class DrawMode : u32 { General = 0, Instance, InlineIndex };
|
|
||||||
struct State {
|
|
||||||
PrimitiveTopology topology{};
|
|
||||||
DrawMode draw_mode{};
|
|
||||||
bool draw_indexed{};
|
|
||||||
u32 base_index{};
|
|
||||||
VertexBuffer vertex_buffer;
|
|
||||||
IndexBuffer index_buffer;
|
|
||||||
u32 base_instance{};
|
|
||||||
u32 instance_count{};
|
|
||||||
std::vector<u8> inline_index_draw_indexes;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DrawTextureState {
|
|
||||||
f32 dst_x0;
|
|
||||||
f32 dst_y0;
|
|
||||||
f32 dst_x1;
|
|
||||||
f32 dst_y1;
|
|
||||||
f32 src_x0;
|
|
||||||
f32 src_y0;
|
|
||||||
f32 src_x1;
|
|
||||||
f32 src_y1;
|
|
||||||
u32 src_sampler;
|
|
||||||
u32 src_texture;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IndirectParams {
|
|
||||||
bool is_byte_count;
|
|
||||||
bool is_indexed;
|
|
||||||
bool include_count;
|
|
||||||
GPUVAddr count_start_address;
|
|
||||||
GPUVAddr indirect_start_address;
|
|
||||||
size_t buffer_size;
|
|
||||||
size_t max_draw_counts;
|
|
||||||
size_t stride;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit DrawManager(Maxwell3D* maxwell_3d);
|
|
||||||
|
|
||||||
void ProcessMethodCall(u32 method, u32 argument);
|
|
||||||
|
|
||||||
void Clear(u32 layer_count);
|
|
||||||
|
|
||||||
void DrawDeferred();
|
|
||||||
|
|
||||||
void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
|
|
||||||
u32 base_instance, u32 num_instances);
|
|
||||||
void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
|
|
||||||
bool subsequent);
|
|
||||||
|
|
||||||
void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
|
|
||||||
u32 base_instance, u32 num_instances);
|
|
||||||
|
|
||||||
void DrawArrayIndirect(PrimitiveTopology topology);
|
|
||||||
|
|
||||||
void DrawIndexedIndirect(PrimitiveTopology topology, u32 index_first, u32 index_count);
|
|
||||||
|
|
||||||
const State& GetDrawState() const {
|
|
||||||
return draw_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DrawTextureState& GetDrawTextureState() const {
|
|
||||||
return draw_texture_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
IndirectParams& GetIndirectParams() {
|
|
||||||
return indirect_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const IndirectParams& GetIndirectParams() const {
|
|
||||||
return indirect_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void SetInlineIndexBuffer(u32 index);
|
|
||||||
|
|
||||||
void DrawBegin();
|
|
||||||
|
|
||||||
void DrawEnd(u32 instance_count = 1, bool force_draw = false);
|
|
||||||
|
|
||||||
void DrawIndexSmall(u32 argument);
|
|
||||||
|
|
||||||
void DrawTexture();
|
|
||||||
|
|
||||||
void UpdateTopology();
|
|
||||||
|
|
||||||
void ProcessDraw(bool draw_indexed, u32 instance_count);
|
|
||||||
|
|
||||||
void ProcessDrawIndirect();
|
|
||||||
|
|
||||||
Maxwell3D* maxwell3d{};
|
|
||||||
State draw_state{};
|
|
||||||
DrawTextureState draw_texture_state{};
|
|
||||||
IndirectParams indirect_state{};
|
|
||||||
};
|
|
||||||
} // namespace Tegra::Engines
|
|
||||||
@@ -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/draw_manager.h"
|
#include "video_core/engines/maxwell_3d.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,7 +26,8 @@ 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{std::make_unique<DrawManager>(this)}, system{system_}
|
: draw_manager()
|
||||||
|
, 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))
|
||||||
@@ -373,8 +374,7 @@ 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() ==
|
return upload_state.ProcessExec(regs.launch_dma.memory_layout.Value() == Regs::LaunchDMA::Layout::Pitch);
|
||||||
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(method, argument);
|
draw_manager.ProcessMethodCall(*this, method, argument);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,8 +401,7 @@ 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) {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#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;
|
||||||
@@ -40,8 +41,6 @@ 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
|
||||||
@@ -543,7 +542,7 @@ public:
|
|||||||
}
|
}
|
||||||
GPUVAddr StorageLimitAddress() const {
|
GPUVAddr StorageLimitAddress() const {
|
||||||
return (GPUVAddr{storage_limit_address_high} << 32) |
|
return (GPUVAddr{storage_limit_address_high} << 32) |
|
||||||
GPUVAddr{storage_limit_address_low};
|
GPUVAddr{storage_limit_address_low};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -819,7 +818,7 @@ public:
|
|||||||
|
|
||||||
u32 Map(std::size_t index) const {
|
u32 Map(std::size_t index) const {
|
||||||
const std::array<u32, NumRenderTargets> maps{target0, target1, target2, target3,
|
const std::array<u32, NumRenderTargets> maps{target0, target1, target2, target3,
|
||||||
target4, target5, target6, target7};
|
target4, target5, target6, target7};
|
||||||
ASSERT(index < maps.size());
|
ASSERT(index < maps.size());
|
||||||
return maps[index];
|
return maps[index];
|
||||||
}
|
}
|
||||||
@@ -1831,7 +1830,7 @@ public:
|
|||||||
|
|
||||||
bool AnyEnabled() const {
|
bool AnyEnabled() const {
|
||||||
return output0_enable || output1_enable || output2_enable || output3_enable ||
|
return output0_enable || output1_enable || output2_enable || output3_enable ||
|
||||||
output4_enable || output5_enable || output6_enable || output7_enable;
|
output4_enable || output5_enable || output6_enable || output7_enable;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1870,7 +1869,7 @@ public:
|
|||||||
|
|
||||||
bool AnyEnabled() const {
|
bool AnyEnabled() const {
|
||||||
return plane0 || plane1 || plane2 || plane3 || plane4 || plane5 || plane6 ||
|
return plane0 || plane1 || plane2 || plane3 || plane4 || plane5 || plane6 ||
|
||||||
plane7;
|
plane7;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3023,8 +3022,7 @@ 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>
|
std::array<std::array<StreamOutLayout, 32>, NumTransformFeedbackBuffers> stream_out_layout; ///< 0x2800
|
||||||
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);
|
||||||
@@ -3035,6 +3033,62 @@ 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
|
||||||
@@ -3102,8 +3156,7 @@ public:
|
|||||||
Tables tables{};
|
Tables tables{};
|
||||||
} dirty;
|
} dirty;
|
||||||
|
|
||||||
std::unique_ptr<DrawManager> draw_manager;
|
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];
|
||||||
|
|||||||
@@ -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().BindRasterizer(rasterizer);
|
host1x.gmmu_manager.BindRasterizer(rasterizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flush all current written commands into the host GPU for execution.
|
/// Flush all current written commands into the host GPU for execution.
|
||||||
|
|||||||
@@ -19,56 +19,45 @@
|
|||||||
|
|
||||||
namespace VideoCommon::GPUThread {
|
namespace VideoCommon::GPUThread {
|
||||||
|
|
||||||
/// Runs the GPU thread
|
|
||||||
static void RunThread(std::stop_token stop_token, Core::System& system,
|
|
||||||
VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
|
|
||||||
Tegra::Control::Scheduler& scheduler, SynchState& state) {
|
|
||||||
Common::SetCurrentThreadName("GPU");
|
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
|
||||||
system.RegisterHostThread();
|
|
||||||
|
|
||||||
auto current_context = context.Acquire();
|
|
||||||
VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
|
|
||||||
|
|
||||||
CommandDataContainer next;
|
|
||||||
|
|
||||||
while (!stop_token.stop_requested()) {
|
|
||||||
state.queue.PopWait(next, stop_token);
|
|
||||||
if (stop_token.stop_requested()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
|
||||||
scheduler.Push(submit_list->channel, std::move(submit_list->entries));
|
|
||||||
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
|
|
||||||
system.GPU().TickWork();
|
|
||||||
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
|
|
||||||
rasterizer->FlushRegion(flush->addr, flush->size);
|
|
||||||
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
|
|
||||||
rasterizer->OnCacheInvalidation(invalidate->addr, invalidate->size);
|
|
||||||
} else {
|
|
||||||
ASSERT(false);
|
|
||||||
}
|
|
||||||
state.signaled_fence.store(next.fence);
|
|
||||||
if (next.block) {
|
|
||||||
// We have to lock the write_lock to ensure that the condition_variable wait not get a
|
|
||||||
// race between the check and the lock itself.
|
|
||||||
std::scoped_lock lk{state.write_lock};
|
|
||||||
state.cv.notify_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadManager::ThreadManager(Core::System& system_, bool is_async_)
|
ThreadManager::ThreadManager(Core::System& system_, bool is_async_)
|
||||||
: system{system_}, is_async{is_async_} {}
|
: system{system_}, is_async{is_async_} {}
|
||||||
|
|
||||||
ThreadManager::~ThreadManager() = default;
|
ThreadManager::~ThreadManager() = default;
|
||||||
|
|
||||||
void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
|
void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, Tegra::Control::Scheduler& scheduler) {
|
||||||
Core::Frontend::GraphicsContext& context,
|
|
||||||
Tegra::Control::Scheduler& scheduler) {
|
|
||||||
rasterizer = renderer.ReadRasterizer();
|
rasterizer = renderer.ReadRasterizer();
|
||||||
thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
|
thread = std::jthread([&](std::stop_token stop_token) {
|
||||||
std::ref(scheduler), std::ref(state));
|
Common::SetCurrentThreadName("GPU");
|
||||||
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||||
|
system.RegisterHostThread();
|
||||||
|
|
||||||
|
auto current_context = context.Acquire();
|
||||||
|
CommandDataContainer next;
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
state.queue.PopWait(next, stop_token);
|
||||||
|
if (stop_token.stop_requested()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
||||||
|
scheduler.Push(submit_list->channel, std::move(submit_list->entries));
|
||||||
|
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
|
||||||
|
system.GPU().TickWork();
|
||||||
|
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
|
||||||
|
renderer.ReadRasterizer()->FlushRegion(flush->addr, flush->size);
|
||||||
|
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
|
||||||
|
renderer.ReadRasterizer()->OnCacheInvalidation(invalidate->addr, invalidate->size);
|
||||||
|
} else {
|
||||||
|
ASSERT(false);
|
||||||
|
}
|
||||||
|
state.signaled_fence.store(next.fence);
|
||||||
|
if (next.block) {
|
||||||
|
// We have to lock the write_lock to ensure that the condition_variable wait not get a
|
||||||
|
// race between the check and the lock itself.
|
||||||
|
std::scoped_lock lk{state.write_lock};
|
||||||
|
state.cv.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
|
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -6,12 +9,192 @@
|
|||||||
#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,
|
||||||
@@ -287,10 +470,7 @@ struct RefPoolElement {
|
|||||||
bool refresh{};
|
bool refresh{};
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ASSERT_POSITION(field_name, position) \
|
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(Vp9EntropyProbs, 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);
|
||||||
@@ -298,10 +478,7 @@ 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) \
|
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(PictureInfo, 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);
|
||||||
@@ -309,9 +486,7 @@ 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) \
|
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(EntropyProbs, 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);
|
||||||
@@ -321,5 +496,47 @@ 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
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -13,9 +16,12 @@
|
|||||||
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_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
|
: host1x(host1x_)
|
||||||
vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
|
, state{regs}
|
||||||
vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
|
, h264_decoder(host1x_)
|
||||||
|
, vp8_decoder(host1x_)
|
||||||
|
, vp9_decoder(host1x_)
|
||||||
|
{}
|
||||||
|
|
||||||
Codec::~Codec() = default;
|
Codec::~Codec() = default;
|
||||||
|
|
||||||
@@ -32,13 +38,11 @@ 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;
|
||||||
@@ -46,13 +50,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>{};
|
||||||
@@ -81,19 +85,13 @@ 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:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -8,17 +11,14 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "video_core/host1x/ffmpeg/ffmpeg.h"
|
#include "video_core/host1x/codecs/h264.h"
|
||||||
|
#include "video_core/host1x/codecs/vp8.h"
|
||||||
|
#include "video_core/host1x/codecs/vp9.h"
|
||||||
|
#include "video_core/host1x/ffmpeg.h"
|
||||||
#include "video_core/host1x/nvdec_common.h"
|
#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,9 +40,6 @@ 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;
|
||||||
|
|
||||||
@@ -53,10 +50,9 @@ private:
|
|||||||
|
|
||||||
Host1x::Host1x& host1x;
|
Host1x::Host1x& host1x;
|
||||||
const Host1x::NvdecCommon::NvdecRegisters& state;
|
const Host1x::NvdecCommon::NvdecRegisters& state;
|
||||||
std::unique_ptr<Decoder::H264> h264_decoder;
|
Decoders::H264 h264_decoder;
|
||||||
std::unique_ptr<Decoder::VP8> vp8_decoder;
|
Decoders::VP8 vp8_decoder;
|
||||||
std::unique_ptr<Decoder::VP9> vp9_decoder;
|
Decoders::VP9 vp9_decoder;
|
||||||
|
|
||||||
std::queue<std::unique_ptr<FFmpeg::Frame>> frames{};
|
std::queue<std::unique_ptr<FFmpeg::Frame>> frames{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+12
-11
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -12,10 +12,11 @@
|
|||||||
|
|
||||||
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::FrameQueue& frame_queue_)
|
: host1x(host1x_)
|
||||||
: host1x(host1x_), memory_manager{host1x.GMMU()}, regs{regs_}, id{id_}, frame_queue{
|
, regs{regs_}
|
||||||
frame_queue_} {}
|
, id{id_}
|
||||||
|
{}
|
||||||
|
|
||||||
Decoder::~Decoder() = default;
|
Decoder::~Decoder() = default;
|
||||||
|
|
||||||
@@ -53,11 +54,11 @@ void Decoder::Decode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (UsingDecodeOrder()) {
|
if (UsingDecodeOrder()) {
|
||||||
frame_queue.PushDecodeOrder(id, luma_top, std::move(frame));
|
host1x.frame_queue.PushDecodeOrder(id, luma_top, std::move(frame));
|
||||||
frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy));
|
host1x.frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy));
|
||||||
} else {
|
} else {
|
||||||
frame_queue.PushPresentOrder(id, luma_top, std::move(frame));
|
host1x.frame_queue.PushPresentOrder(id, luma_top, std::move(frame));
|
||||||
frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy));
|
host1x.frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto [luma_offset, chroma_offset] = GetProgressiveOffsets();
|
auto [luma_offset, chroma_offset] = GetProgressiveOffsets();
|
||||||
@@ -68,9 +69,9 @@ void Decoder::Decode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (UsingDecodeOrder()) {
|
if (UsingDecodeOrder()) {
|
||||||
frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame));
|
host1x.frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame));
|
||||||
} else {
|
} else {
|
||||||
frame_queue.PushPresentOrder(id, luma_offset, std::move(frame));
|
host1x.frame_queue.PushPresentOrder(id, luma_offset, std::move(frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "video_core/host1x/ffmpeg/ffmpeg.h"
|
#include "video_core/host1x/ffmpeg.h"
|
||||||
#include "video_core/host1x/nvdec_common.h"
|
#include "video_core/host1x/nvdec_common.h"
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
@@ -35,33 +35,23 @@ 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,
|
explicit Decoder(Host1x::Host1x& host1x, s32 id, const Host1x::NvdecCommon::NvdecRegisters& regs);
|
||||||
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;
|
||||||
Host1x::FrameQueue& frame_queue;
|
bool initialized : 1 = false;
|
||||||
Host1x::NvdecCommon::VideoCodec codec;
|
bool vp9_hidden_frame : 1 = false;
|
||||||
FFmpeg::DecodeApi decode_api;
|
|
||||||
bool initialized{};
|
|
||||||
bool vp9_hidden_frame{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -14,25 +14,11 @@
|
|||||||
#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,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr std::array<u8, 16> zig_zag_scan{
|
H264::H264(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_)
|
||||||
0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
|
: Decoder{host1x_, id_, regs_}
|
||||||
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;
|
||||||
@@ -65,14 +51,11 @@ bool H264::IsInterlaced() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::span<const u8> H264::ComposeFrame() {
|
std::span<const u8> H264::ComposeFrame() {
|
||||||
memory_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context,
|
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(H264DecoderContext));
|
||||||
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);
|
||||||
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data(),
|
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data(), frame_scratch.size());
|
||||||
frame_scratch.size());
|
|
||||||
return frame_scratch;
|
return frame_scratch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,15 +157,13 @@ 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);
|
||||||
std::span<const u8> matrix{current_context.weight_scale_4x4};
|
writer.WriteScalingList(current_context.weight_scale_4x4, index * 16, 16);
|
||||||
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);
|
||||||
std::span<const u8> matrix{current_context.weight_scale_8x8};
|
writer.WriteScalingList(current_context.weight_scale_8x8, index * 64, 64);
|
||||||
writer.WriteScalingList(scan_scratch, matrix, index * 64, 64);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,11 +177,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,23 +206,37 @@ void H264BitWriter::WriteBit(bool state) {
|
|||||||
WriteBits(state ? 1 : 0, 1);
|
WriteBits(state ? 1 : 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void H264BitWriter::WriteScalingList(Common::ScratchBuffer<u8>& scan, std::span<const u8> list,
|
void H264BitWriter::WriteScalingList(std::span<const u8> list, s32 start, s32 count) {
|
||||||
s32 start, s32 count) {
|
|
||||||
scan.resize_destructive(count);
|
|
||||||
if (count == 16) {
|
if (count == 16) {
|
||||||
std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());
|
u8 last_scale = 8;
|
||||||
|
for (s32 index = 0; index < count; index++) {
|
||||||
|
// libavcodec has a zig zag LUT, but we dont need it, just use a magic
|
||||||
|
// constant which is a packing of 4 bits for each component of the table
|
||||||
|
const u8 value = list[start + ((0xfeb7adc963258410 >> (index * 4)) & 0xf)];
|
||||||
|
const s32 delta_scale = s32(value - last_scale);
|
||||||
|
WriteSe(delta_scale);
|
||||||
|
last_scale = value;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
std::memcpy(scan.data(), zig_zag_direct.data(), scan.size());
|
// ZigZag LUTs from libavcodec: this is the famous zigzag pattern found in the ffmpeg logo itself!
|
||||||
}
|
static constexpr std::array<u8, 64> scan{
|
||||||
u8 last_scale = 8;
|
0, 1, 8, 16, 9, 2, 3, 10,
|
||||||
|
17, 24, 32, 25, 18, 11, 4,
|
||||||
for (s32 index = 0; index < count; index++) {
|
5, 12, 19, 26, 33, 40, 48,
|
||||||
const u8 value = list[start + scan[index]];
|
41, 34, 27, 20, 13, 6, 7,
|
||||||
const s32 delta_scale = static_cast<s32>(value - last_scale);
|
14, 21, 28, 35, 42, 49, 56,
|
||||||
|
57, 50, 43, 36, 29, 22, 15,
|
||||||
WriteSe(delta_scale);
|
23, 30, 37, 44, 51, 58, 59,
|
||||||
|
52, 45, 38, 31, 39, 46, 53,
|
||||||
last_scale = value;
|
60, 61, 54, 47, 55, 62, 63,
|
||||||
|
};
|
||||||
|
u8 last_scale = 8;
|
||||||
|
for (s32 index = 0; index < count; index++) {
|
||||||
|
const u8 value = list[start + scan[index]];
|
||||||
|
const s32 delta_scale = s32(value - last_scale);
|
||||||
|
WriteSe(delta_scale);
|
||||||
|
last_scale = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,19 +277,15 @@ void H264BitWriter::WriteBits(s32 value, s32 bit_count) {
|
|||||||
|
|
||||||
void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
|
void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
|
||||||
const s32 sign = value <= 0 ? 0 : 1;
|
const s32 sign = value <= 0 ? 0 : 1;
|
||||||
if (value < 0) {
|
if (!sign) value = -value;
|
||||||
value = -value;
|
WriteExpGolombCodedUInt((value << 1) - sign);
|
||||||
}
|
|
||||||
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(static_cast<s32>(value), size - 1);
|
WriteBits(s32(value), size - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 H264BitWriter::GetFreeBufferBits() {
|
s32 H264BitWriter::GetFreeBufferBits() {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -6,11 +9,10 @@
|
|||||||
#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 {
|
||||||
@@ -40,8 +42,7 @@ 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(Common::ScratchBuffer<u8>& scan, std::span<const u8> list, s32 start,
|
void WriteScalingList(std::span<const u8> list, s32 start, s32 count);
|
||||||
s32 count);
|
|
||||||
|
|
||||||
/// Return the bitstream as a vector.
|
/// Return the bitstream as a vector.
|
||||||
[[nodiscard]] std::vector<u8>& GetByteArray();
|
[[nodiscard]] std::vector<u8>& GetByteArray();
|
||||||
@@ -61,188 +62,9 @@ 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;
|
||||||
@@ -263,10 +85,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool is_first_frame{true};
|
|
||||||
Common::ScratchBuffer<u8> frame_scratch;
|
|
||||||
Common::ScratchBuffer<u8> scan_scratch;
|
|
||||||
H264DecoderContext current_context{};
|
H264DecoderContext current_context{};
|
||||||
|
std::array<u8, 64> scan_scratch;
|
||||||
|
Common::ScratchBuffer<u8> frame_scratch;
|
||||||
|
bool is_first_frame{true};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Decoders
|
} // namespace Decoders
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -8,11 +11,10 @@
|
|||||||
#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_)
|
||||||
Host1x::FrameQueue& frame_queue_)
|
: Decoder{host1x_, id_, regs_}
|
||||||
: Decoder{host1x_, id_, regs_, frame_queue_} {
|
{
|
||||||
codec = Host1x::NvdecCommon::VideoCodec::VP8;
|
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::VP8);
|
||||||
initialized = decode_api.Initialize(codec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VP8::~VP8() = default;
|
VP8::~VP8() = default;
|
||||||
@@ -25,35 +27,30 @@ 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{
|
auto luma_bottom = regs.surface_luma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
|
||||||
regs.surface_luma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
|
auto chroma_top = regs.surface_chroma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
|
||||||
auto chroma_top{
|
auto chroma_bottom = regs.surface_chroma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
|
||||||
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() {
|
||||||
memory_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context,
|
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(VP8PictureInfo));
|
||||||
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 = static_cast<size_t>(current_context.vld_buffer_size);
|
const auto bitstream_size = size_t(current_context.vld_buffer_size);
|
||||||
const size_t header_size = is_key_frame ? 10u : 3u;
|
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] |=
|
frame_scratch[0] |= u8((current_context.version & 7u) << 1u); // 3-bit version number
|
||||||
static_cast<u8>((current_context.version & 7u) << 1u); // 3-bit version number
|
frame_scratch[0] |= u8(1u << 4u); // 1-bit show_frame flag
|
||||||
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] |= static_cast<u8>((current_context.first_part_size & 7u) << 5u);
|
frame_scratch[0] |= u8((current_context.first_part_size & 7u) << 5u);
|
||||||
frame_scratch[1] = static_cast<u8>((current_context.first_part_size & 0x7f8u) >> 3u);
|
frame_scratch[1] = u8((current_context.first_part_size & 0x7f8u) >> 3u);
|
||||||
frame_scratch[2] = static_cast<u8>((current_context.first_part_size & 0x7f800u) >> 11u);
|
frame_scratch[2] = u8((current_context.first_part_size & 0x7f800u) >> 11u);
|
||||||
|
|
||||||
if (is_key_frame) {
|
if (is_key_frame) {
|
||||||
frame_scratch[3] = 0x9du;
|
frame_scratch[3] = 0x9du;
|
||||||
@@ -61,15 +58,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] = static_cast<u8>(current_context.frame_width & 0xff);
|
frame_scratch[6] = u8(current_context.frame_width & 0xff);
|
||||||
frame_scratch[7] = static_cast<u8>(((current_context.frame_width >> 8) & 0x3f));
|
frame_scratch[7] = 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] = static_cast<u8>(current_context.frame_height & 0xff);
|
frame_scratch[8] = u8(current_context.frame_height & 0xff);
|
||||||
frame_scratch[9] = static_cast<u8>(((current_context.frame_height >> 8) & 0x3f));
|
frame_scratch[9] = u8(((current_context.frame_height >> 8) & 0x3f));
|
||||||
}
|
}
|
||||||
const u64 bitstream_offset = regs.frame_bitstream_offset.Address();
|
|
||||||
memory_manager.ReadBlock(bitstream_offset, frame_scratch.data() + header_size, bitstream_size);
|
|
||||||
|
|
||||||
|
const u64 bitstream_offset = regs.frame_bitstream_offset.Address();
|
||||||
|
host1x.gmmu_manager.ReadBlock(bitstream_offset, frame_scratch.data() + header_size, bitstream_size);
|
||||||
return frame_scratch;
|
return frame_scratch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -11,6 +14,7 @@
|
|||||||
#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 {
|
||||||
|
|
||||||
@@ -28,8 +32,7 @@ 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;
|
||||||
@@ -52,51 +55,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Common::ScratchBuffer<u8> frame_scratch;
|
|
||||||
|
|
||||||
struct VP8PictureInfo {
|
|
||||||
INSERT_PADDING_WORDS_NOINIT(14);
|
|
||||||
u16 frame_width; // actual frame width
|
|
||||||
u16 frame_height; // actual frame height
|
|
||||||
u8 key_frame;
|
|
||||||
u8 version;
|
|
||||||
union {
|
|
||||||
u8 raw;
|
|
||||||
BitField<0, 2, u8> tile_format;
|
|
||||||
BitField<2, 3, u8> gob_height;
|
|
||||||
BitField<5, 3, u8> reserved_surface_format;
|
|
||||||
};
|
|
||||||
u8 error_conceal_on; // 1: error conceal on; 0: off
|
|
||||||
u32 first_part_size; // the size of first partition(frame header and mb header partition)
|
|
||||||
u32 hist_buffer_size; // in units of 256
|
|
||||||
u32 vld_buffer_size; // in units of 1
|
|
||||||
// Current frame buffers
|
|
||||||
std::array<u32, 2> frame_stride; // [y_c]
|
|
||||||
u32 luma_top_offset; // offset of luma top field in units of 256
|
|
||||||
u32 luma_bot_offset; // offset of luma bottom field in units of 256
|
|
||||||
u32 luma_frame_offset; // offset of luma frame in units of 256
|
|
||||||
u32 chroma_top_offset; // offset of chroma top field in units of 256
|
|
||||||
u32 chroma_bot_offset; // offset of chroma bottom field in units of 256
|
|
||||||
u32 chroma_frame_offset; // offset of chroma frame in units of 256
|
|
||||||
|
|
||||||
INSERT_PADDING_BYTES_NOINIT(0x1c); // NvdecDisplayParams
|
|
||||||
|
|
||||||
// Decode picture buffer related
|
|
||||||
s8 current_output_memory_layout;
|
|
||||||
// output NV12/NV24 setting. index 0: golden; 1: altref; 2: last
|
|
||||||
std::array<s8, 3> output_memory_layout;
|
|
||||||
|
|
||||||
u8 segmentation_feature_data_update;
|
|
||||||
INSERT_PADDING_BYTES_NOINIT(3);
|
|
||||||
|
|
||||||
// ucode return result
|
|
||||||
u32 result_value;
|
|
||||||
std::array<u32, 8> partition_offset;
|
|
||||||
INSERT_PADDING_WORDS_NOINIT(3);
|
|
||||||
};
|
|
||||||
static_assert(sizeof(VP8PictureInfo) == 0xc0, "PictureInfo is an invalid size");
|
|
||||||
|
|
||||||
VP8PictureInfo current_context{};
|
VP8PictureInfo current_context{};
|
||||||
|
Common::ScratchBuffer<u8> frame_scratch;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Decoders
|
} // namespace Decoders
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-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"
|
||||||
@@ -15,11 +16,11 @@
|
|||||||
|
|
||||||
namespace Tegra::Decoders {
|
namespace Tegra::Decoders {
|
||||||
namespace {
|
namespace {
|
||||||
constexpr u32 diff_update_probability = 252;
|
static constexpr u32 diff_update_probability = 252;
|
||||||
constexpr u32 frame_sync_code = 0x498342;
|
static constexpr u32 frame_sync_code = 0x498342;
|
||||||
|
|
||||||
// Default compressed header probabilities once frame context resets
|
// Default compressed header probabilities once frame context resets
|
||||||
constexpr Vp9EntropyProbs default_probs{
|
static 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,
|
||||||
@@ -158,34 +159,6 @@ 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) {
|
||||||
@@ -227,25 +200,17 @@ constexpr std::array<u8, 254> map_lut{
|
|||||||
[[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
|
||||||
std::size_t index{};
|
? u8((std::max)(0, RecenterNonNeg(new_prob, old_prob) - 1))
|
||||||
|
: u8((std::max)(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
|
||||||
if (old_prob * 2 <= 0xff) {
|
return s32((i + 7) % 13 == 0 ? (i + 7) / 13 - 1 : i + 20 - (i + 7) / 13);
|
||||||
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_)
|
||||||
Host1x::FrameQueue& frame_queue_)
|
: Decoder{host1x_, id_, regs_}
|
||||||
: Decoder{host1x_, id_, regs_, frame_queue_} {
|
{
|
||||||
codec = Host1x::NvdecCommon::VideoCodec::VP9;
|
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::VP9);
|
||||||
initialized = decode_api.Initialize(codec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VP9::~VP9() = default;
|
VP9::~VP9() = default;
|
||||||
@@ -377,8 +342,7 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
|
|||||||
|
|
||||||
if (update_map) {
|
if (update_map) {
|
||||||
EntropyProbs entropy_probs{};
|
EntropyProbs entropy_probs{};
|
||||||
memory_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs,
|
host1x.gmmu_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs, sizeof(entropy_probs));
|
||||||
sizeof(entropy_probs));
|
|
||||||
|
|
||||||
auto WriteProb = [&](u8 prob) {
|
auto WriteProb = [&](u8 prob) {
|
||||||
bool coded = prob != 255;
|
bool coded = prob != 255;
|
||||||
@@ -442,8 +406,7 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vp9PictureInfo VP9::GetVp9PictureInfo() {
|
Vp9PictureInfo VP9::GetVp9PictureInfo() {
|
||||||
memory_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_picture_info,
|
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_picture_info, sizeof(PictureInfo));
|
||||||
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);
|
||||||
@@ -459,7 +422,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo() {
|
|||||||
|
|
||||||
void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
|
void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
|
||||||
EntropyProbs entropy;
|
EntropyProbs entropy;
|
||||||
memory_manager.ReadBlock(offset, &entropy, sizeof(EntropyProbs));
|
host1x.gmmu_manager.ReadBlock(offset, &entropy, sizeof(EntropyProbs));
|
||||||
entropy.Convert(dst);
|
entropy.Convert(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,9 +432,7 @@ 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);
|
||||||
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(),
|
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), current_frame.bit_stream.data(), current_frame.info.bitstream_size);
|
||||||
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{
|
||||||
@@ -893,13 +854,9 @@ 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(),
|
std::copy(compressed_header.begin(), compressed_header.end(), frame_scratch.begin() + uncompressed_header.size());
|
||||||
frame_scratch.begin() + uncompressed_header.size());
|
std::copy(bitstream.begin(), bitstream.end(), frame_scratch.begin() + uncompressed_header.size() + compressed_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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,7 +886,7 @@ void VpxRangeEncoder::Write(bool bit, s32 probability) {
|
|||||||
local_range = range - split;
|
local_range = range - split;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 shift = static_cast<s32>(norm_lut[local_range]);
|
s32 shift = s32(local_range == 0 ? 0 : (std::countl_zero<uint32_t>(local_range) - 24));
|
||||||
local_range <<= shift;
|
local_range <<= shift;
|
||||||
count += shift;
|
count += shift;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -11,7 +14,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/codecs/vp9_types.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 {
|
||||||
@@ -113,8 +116,7 @@ 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;
|
||||||
@@ -193,11 +195,10 @@ 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{};
|
||||||
|
|||||||
@@ -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/ffmpeg.h"
|
#include "video_core/host1x/ffmpeg.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -34,7 +34,9 @@ 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)
|
||||||
@@ -105,18 +107,17 @@ Frame::~Frame() {
|
|||||||
Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
|
Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
|
||||||
const AVCodecID av_codec = [&] {
|
const AVCodecID av_codec = [&] {
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
|
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
|
||||||
return AV_CODEC_ID_H264;
|
return AV_CODEC_ID_H264;
|
||||||
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
|
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
|
||||||
return AV_CODEC_ID_VP8;
|
return AV_CODEC_ID_VP8;
|
||||||
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
|
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
|
||||||
return AV_CODEC_ID_VP9;
|
return AV_CODEC_ID_VP9;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Unknown codec {}", codec);
|
UNIMPLEMENTED_MSG("Unknown codec {}", 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 2025 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
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
@@ -12,9 +12,12 @@
|
|||||||
namespace Tegra::Host1x {
|
namespace Tegra::Host1x {
|
||||||
|
|
||||||
Host1x::Host1x(Core::System& system_)
|
Host1x::Host1x(Core::System& system_)
|
||||||
: system{system_}, syncpoint_manager{},
|
: system{system_}
|
||||||
memory_manager(system.DeviceMemory()), gmmu_manager{system, memory_manager, 32, 0, 12},
|
, syncpoint_manager{}
|
||||||
allocator{std::make_unique<Common::FlatAllocator<u32, 0, 32>>(1 << 12)} {}
|
, memory_manager(system.DeviceMemory())
|
||||||
|
, gmmu_manager{system, memory_manager, 32, 0, 12}
|
||||||
|
, allocator{1 << 12}
|
||||||
|
{}
|
||||||
|
|
||||||
Host1x::~Host1x() = default;
|
Host1x::~Host1x() = default;
|
||||||
|
|
||||||
@@ -24,13 +27,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, frame_queue);
|
devices[fd] = std::make_unique<Tegra::Host1x::Nvdec>(*this, fd, syncpt);
|
||||||
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, frame_queue);
|
devices[fd] = std::make_unique<Tegra::Host1x::Vic>(*this, fd, syncpt);
|
||||||
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));
|
||||||
|
|||||||
@@ -184,20 +184,12 @@ public:
|
|||||||
return memory_manager;
|
return memory_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tegra::MemoryManager& GMMU() {
|
|
||||||
return gmmu_manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Tegra::MemoryManager& GMMU() const {
|
|
||||||
return gmmu_manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
Common::FlatAllocator<u32, 0, 32>& Allocator() {
|
Common::FlatAllocator<u32, 0, 32>& Allocator() {
|
||||||
return *allocator;
|
return allocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Common::FlatAllocator<u32, 0, 32>& Allocator() const {
|
const Common::FlatAllocator<u32, 0, 32>& Allocator() const {
|
||||||
return *allocator;
|
return allocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartDevice(s32 fd, ChannelType type, u32 syncpt);
|
void StartDevice(s32 fd, ChannelType type, u32 syncpt);
|
||||||
@@ -211,12 +203,11 @@ public:
|
|||||||
it->second->PushEntries(std::move(entries));
|
it->second->PushEntries(std::move(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
SyncpointManager syncpoint_manager;
|
SyncpointManager syncpoint_manager;
|
||||||
Tegra::MaxwellDeviceMemoryManager memory_manager;
|
Tegra::MaxwellDeviceMemoryManager memory_manager;
|
||||||
Tegra::MemoryManager gmmu_manager;
|
Tegra::MemoryManager gmmu_manager;
|
||||||
std::unique_ptr<Common::FlatAllocator<u32, 0, 32>> allocator;
|
Common::FlatAllocator<u32, 0, 32> allocator;
|
||||||
FrameQueue frame_queue;
|
FrameQueue frame_queue;
|
||||||
ankerl::unordered_dense::map<s32, std::unique_ptr<CDmaPusher>> devices;
|
ankerl::unordered_dense::map<s32, std::unique_ptr<CDmaPusher>> devices;
|
||||||
#ifdef YUZU_LEGACY
|
#ifdef YUZU_LEGACY
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-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 <variant>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
|
||||||
#include "common/polyfill_thread.h"
|
#include "common/polyfill_thread.h"
|
||||||
@@ -19,10 +20,13 @@ namespace Tegra::Host1x {
|
|||||||
#define NVDEC_REG_INDEX(field_name) \
|
#define NVDEC_REG_INDEX(field_name) \
|
||||||
(offsetof(NvdecCommon::NvdecRegisters, field_name) / sizeof(u64))
|
(offsetof(NvdecCommon::NvdecRegisters, field_name) / sizeof(u64))
|
||||||
|
|
||||||
Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt, FrameQueue& frame_queue_)
|
Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt)
|
||||||
: CDmaPusher{host1x_, id_}, id{id_}, syncpoint{syncpt}, frame_queue{frame_queue_} {
|
: CDmaPusher{host1x_, id_}
|
||||||
|
, id{id_}
|
||||||
|
, syncpoint{syncpt}
|
||||||
|
{
|
||||||
LOG_INFO(HW_GPU, "Created nvdec {}", id);
|
LOG_INFO(HW_GPU, "Created nvdec {}", id);
|
||||||
frame_queue.Open(id);
|
host1x.frame_queue.Open(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Nvdec::~Nvdec() {
|
Nvdec::~Nvdec() {
|
||||||
@@ -43,24 +47,22 @@ void Nvdec::ProcessMethod(u32 method, u32 argument) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Nvdec::CreateDecoder(NvdecCommon::VideoCodec codec) {
|
void Nvdec::CreateDecoder(NvdecCommon::VideoCodec codec) {
|
||||||
if (decoder.get()) {
|
if (std::holds_alternative<std::monostate>(decoder)) {
|
||||||
return;
|
switch (codec) {
|
||||||
|
case NvdecCommon::VideoCodec::H264:
|
||||||
|
decoder.emplace<Decoders::H264>(host1x, regs, id);
|
||||||
|
break;
|
||||||
|
case NvdecCommon::VideoCodec::VP8:
|
||||||
|
decoder.emplace<Decoders::VP8>(host1x, regs, id);
|
||||||
|
break;
|
||||||
|
case NvdecCommon::VideoCodec::VP9:
|
||||||
|
decoder.emplace<Decoders::VP9>(host1x, regs, id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LOG_INFO(HW_GPU, "Created decoder {} for id {}", codec, id);
|
||||||
}
|
}
|
||||||
switch (codec) {
|
|
||||||
case NvdecCommon::VideoCodec::H264:
|
|
||||||
decoder = std::make_unique<Decoders::H264>(host1x, regs, id, frame_queue);
|
|
||||||
break;
|
|
||||||
case NvdecCommon::VideoCodec::VP8:
|
|
||||||
decoder = std::make_unique<Decoders::VP8>(host1x, regs, id, frame_queue);
|
|
||||||
break;
|
|
||||||
case NvdecCommon::VideoCodec::VP9:
|
|
||||||
decoder = std::make_unique<Decoders::VP9>(host1x, regs, id, frame_queue);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNIMPLEMENTED_MSG("Codec {}", decoder->GetCurrentCodecName());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
LOG_INFO(HW_GPU, "Created decoder {} for id {}", decoder->GetCurrentCodecName(), id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nvdec::Execute() {
|
void Nvdec::Execute() {
|
||||||
@@ -70,15 +72,14 @@ void Nvdec::Execute() {
|
|||||||
std::this_thread::sleep_for(std::chrono::milliseconds(8));
|
std::this_thread::sleep_for(std::chrono::milliseconds(8));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (decoder->GetCurrentCodec()) {
|
if (auto* h264 = std::get_if<Decoders::H264>(&decoder)) {
|
||||||
case NvdecCommon::VideoCodec::H264:
|
h264->Decode();
|
||||||
case NvdecCommon::VideoCodec::VP8:
|
} else if (auto* vp8 = std::get_if<Decoders::VP8>(&decoder)) {
|
||||||
case NvdecCommon::VideoCodec::VP9:
|
vp8->Decode();
|
||||||
decoder->Decode();
|
} else if (auto* vp9 = std::get_if<Decoders::VP9>(&decoder)) {
|
||||||
break;
|
vp9->Decode();
|
||||||
default:
|
} else {
|
||||||
UNIMPLEMENTED_MSG("Codec {}", decoder->GetCurrentCodecName());
|
LOG_ERROR(HW_GPU, "Unrecognized codec executed?");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user