mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-14 04:24:31 +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} "")
|
||||
endif()
|
||||
|
||||
if (YUZU_LEGACY)
|
||||
message(WARNING "Making legacy build. Performance may suffer.")
|
||||
add_compile_definitions(YUZU_LEGACY)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
|
||||
set(HAS_NCE 1)
|
||||
add_compile_definitions(HAS_NCE=1)
|
||||
@@ -424,10 +419,10 @@ if (zstd_ADDED)
|
||||
add_library(zstd::libzstd ALIAS libzstd_static)
|
||||
endif()
|
||||
|
||||
if (NOT YUZU_STATIC_ROOM)
|
||||
# nlohmann
|
||||
AddJsonPackage(nlohmann)
|
||||
# nlohmann
|
||||
AddJsonPackage(nlohmann)
|
||||
|
||||
if (NOT YUZU_STATIC_ROOM)
|
||||
# zlib
|
||||
AddJsonPackage(zlib)
|
||||
|
||||
@@ -485,7 +480,7 @@ endfunction()
|
||||
# =============================================
|
||||
|
||||
if (APPLE)
|
||||
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia)
|
||||
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security)
|
||||
find_library(${fw}_LIBRARY ${fw} REQUIRED)
|
||||
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
|
||||
endforeach()
|
||||
|
||||
@@ -33,9 +33,11 @@ endif()
|
||||
set(GIT_DESC ${BUILD_VERSION})
|
||||
|
||||
# Generate cpp with Git revision from template
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||
|
||||
# Auto-updater metadata! Must somewhat mirror GitHub/Forgejo API endpoint
|
||||
# TODO(crueter): Stable releases feed.
|
||||
set(BUILD_AUTO_UPDATE_STABLE_REPO "eden-emu/eden")
|
||||
set(BUILD_AUTO_UPDATE_STABLE_API "git.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_STABLE_API_PATH "/api/v1/repos/")
|
||||
|
||||
set(BUILD_AUTO_UPDATE_API_PATH "/latest/release.json")
|
||||
if (NIGHTLY_BUILD)
|
||||
|
||||
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)
|
||||
endif()
|
||||
|
||||
if (YUZU_LEGACY)
|
||||
message(WARNING "Making legacy build. Performance may suffer.")
|
||||
add_compile_definitions(YUZU_LEGACY)
|
||||
endif()
|
||||
|
||||
if (GENSHIN_SPOOF)
|
||||
message(WARNING "Making Genshin spoof build")
|
||||
add_compile_definitions(GENSHIN_SPOOF)
|
||||
endif()
|
||||
|
||||
# Set compilation flags
|
||||
if (MSVC AND NOT CXX_CLANG)
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||
|
||||
@@ -203,6 +203,12 @@ android {
|
||||
resValue("string", "app_name_suffixed", "Eden Optimized")
|
||||
applicationId = "com.miHoYo.Yuanshen"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments.add("-DGENSHIN_SPOOF=ON")
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a")
|
||||
}
|
||||
|
||||
@@ -33,6 +33,19 @@ import org.yuzu.yuzu_emu.applets.web.WebBrowser
|
||||
* with the native side of the Yuzu code.
|
||||
*/
|
||||
object NativeLibrary {
|
||||
@Keep
|
||||
data class UpdateResult(
|
||||
var tag: String = "",
|
||||
var title: String = "",
|
||||
var body: String = "",
|
||||
var url: String = "",
|
||||
var assets: MutableList<String> = mutableListOf()
|
||||
) {
|
||||
fun addAsset(asset: String) {
|
||||
assets.add(asset)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||
|
||||
@@ -240,17 +253,7 @@ object NativeLibrary {
|
||||
/**
|
||||
* Checks for available updates.
|
||||
*/
|
||||
external fun checkForUpdate(): Array<String>?
|
||||
|
||||
/**
|
||||
* Return the URL to the release page
|
||||
*/
|
||||
external fun getUpdateUrl(version: String): String
|
||||
|
||||
/**
|
||||
* Return the URL to download the APK for the given version
|
||||
*/
|
||||
external fun getUpdateApkUrl(tag: String, artifact: String, packageId: String): String
|
||||
external fun checkForUpdate(): UpdateResult?
|
||||
|
||||
/**
|
||||
* Returns whether the update checker is enabled through CMAKE options.
|
||||
|
||||
@@ -175,25 +175,25 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
val latestVersion = NativeLibrary.checkForUpdate()
|
||||
if (latestVersion != null) {
|
||||
runOnUiThread {
|
||||
val tag: String = latestVersion[0]
|
||||
val name: String = latestVersion[1]
|
||||
showUpdateDialog(tag, name)
|
||||
showUpdateDialog(latestVersion)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun showUpdateDialog(tag: String, name: String) {
|
||||
// TODO(crueter): body, "View on Forgejo" button
|
||||
private fun showUpdateDialog(release: NativeLibrary.UpdateResult) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.update_available)
|
||||
.setMessage(getString(R.string.update_available_description, name))
|
||||
.setMessage(getString(R.string.update_available_description, release.title))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
var artifact = tag
|
||||
// Nightly builds have a slightly different format
|
||||
if (NativeLibrary.isNightlyBuild()) {
|
||||
artifact = tag.substringAfter('.', tag)
|
||||
val assets = release.assets
|
||||
|
||||
if (assets.isEmpty()) {
|
||||
openLink(release.url)
|
||||
} else {
|
||||
downloadAndInstallUpdate(release)
|
||||
}
|
||||
downloadAndInstallUpdate(tag, artifact)
|
||||
}
|
||||
.setNeutralButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
@@ -206,17 +206,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun downloadAndInstallUpdate(version: String, artifact: String) {
|
||||
private fun openLink(link: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, link.toUri())
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun downloadAndInstallUpdate(release: NativeLibrary.UpdateResult) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val packageId = applicationContext.packageName
|
||||
val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
|
||||
val asset = release.assets[0]
|
||||
val artifact = asset.split("/").last()
|
||||
val apkFile = File(cacheDir, "update-$artifact.apk")
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
showDownloadProgressDialog()
|
||||
}
|
||||
|
||||
val downloader = APKDownloader(apkUrl, apkFile)
|
||||
val downloader = APKDownloader(asset, apkFile)
|
||||
downloader.download(
|
||||
onProgress = { progress ->
|
||||
runOnUiThread {
|
||||
@@ -248,7 +254,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@MainActivity,
|
||||
getString(R.string.update_download_failed) + "\n\nURL: $apkUrl",
|
||||
getString(R.string.update_download_failed) + "\n\nURL: $asset",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
@@ -277,7 +283,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
private fun updateDownloadProgress(progress: Int) {
|
||||
progressBar?.progress = progress
|
||||
progressMessage?.text = "$progress%"
|
||||
progressMessage?.text = getString(R.string.percent, progress)
|
||||
}
|
||||
|
||||
private fun dismissDownloadProgressDialog() {
|
||||
|
||||
@@ -1699,76 +1699,76 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isNightlyBuild(
|
||||
#ifdef ENABLE_UPDATE_CHECKER
|
||||
|
||||
|
||||
JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
||||
JNIEXPORT jobject JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
||||
JNIEnv* env,
|
||||
jobject obj) {
|
||||
std::optional<UpdateChecker::Update> release = UpdateChecker::GetUpdate();
|
||||
std::optional<Common::Net::Release> release = UpdateChecker::GetUpdate();
|
||||
if (!release) return nullptr;
|
||||
|
||||
const std::string tag = release->tag;
|
||||
const std::string name = release->name;
|
||||
const std::string title = release->title;
|
||||
const std::string body = release->body;
|
||||
const std::string url = release->html_url;
|
||||
|
||||
jobjectArray result = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr);
|
||||
// Android *should* only ever define a single asset.
|
||||
// If not, something has gone wrong, but the Kotlin side can handle it.
|
||||
const auto assets = release->GetPlatformAssets();
|
||||
|
||||
const jstring jtag = env->NewStringUTF(tag.c_str());
|
||||
const jstring jname = env->NewStringUTF(name.c_str());
|
||||
|
||||
env->SetObjectArrayElement(result, 0, jtag);
|
||||
env->SetObjectArrayElement(result, 1, jname);
|
||||
env->DeleteLocalRef(jtag);
|
||||
env->DeleteLocalRef(jname);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
||||
JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring version) {
|
||||
const char* version_str = env->GetStringUTFChars(version, nullptr);
|
||||
const std::string url = fmt::format("{}/{}",
|
||||
std::string{Common::g_build_auto_update_api},
|
||||
version_str);
|
||||
env->ReleaseStringUTFChars(version, version_str);
|
||||
return env->NewStringUTF(url.c_str());
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
||||
JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring tag,
|
||||
jstring artifact,
|
||||
jstring packageId) {
|
||||
const char* version_str = env->GetStringUTFChars(tag, nullptr);
|
||||
const char* artifact_str = env->GetStringUTFChars(artifact, nullptr);
|
||||
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
|
||||
|
||||
std::string variant;
|
||||
std::string package_id(package_id_str);
|
||||
|
||||
if (package_id.find("dev.legacy.eden_emulator") != std::string::npos) {
|
||||
variant = "legacy";
|
||||
} else if (package_id.find("com.miHoYo.Yuanshen") != std::string::npos) {
|
||||
variant = "optimized";
|
||||
} else {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
variant = "standard";
|
||||
#else
|
||||
variant = "chromeos";
|
||||
#endif
|
||||
jclass updateResultClass = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary$UpdateResult");
|
||||
if (!updateResultClass) {
|
||||
LOG_ERROR(Frontend, "Could not find UpdateResult class");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", artifact_str, variant);
|
||||
jmethodID updateResultCtor = env->GetMethodID(updateResultClass, "<init>", "()V");
|
||||
|
||||
const std::string url = fmt::format("https://{}/{}/{}",
|
||||
std::string{Common::g_build_auto_update_api},
|
||||
version_str, apk_filename);
|
||||
if (!updateResultCtor) {
|
||||
LOG_ERROR(Frontend, "Could not find UpdateResult ctor");
|
||||
env->DeleteLocalRef(updateResultClass);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
env->ReleaseStringUTFChars(tag, version_str);
|
||||
env->ReleaseStringUTFChars(artifact, artifact_str);
|
||||
env->ReleaseStringUTFChars(packageId, package_id_str);
|
||||
return env->NewStringUTF(url.c_str());
|
||||
jmethodID setTag = env->GetMethodID(updateResultClass, "setTag", "(Ljava/lang/String;)V");
|
||||
jmethodID setTitle = env->GetMethodID(updateResultClass, "setTitle", "(Ljava/lang/String;)V");
|
||||
jmethodID setBody = env->GetMethodID(updateResultClass, "setBody", "(Ljava/lang/String;)V");
|
||||
jmethodID setUrl = env->GetMethodID(updateResultClass, "setUrl", "(Ljava/lang/String;)V");
|
||||
jmethodID addAsset = env->GetMethodID(updateResultClass, "addAsset", "(Ljava/lang/String;)V");
|
||||
|
||||
jobject updateResult = env->NewObject(updateResultClass, updateResultCtor);
|
||||
|
||||
LOG_DEBUG(Frontend, "Tag: {}", tag);
|
||||
LOG_DEBUG(Frontend, "Title: {}", title);
|
||||
LOG_DEBUG(Frontend, "Body: {}", body);
|
||||
LOG_DEBUG(Frontend, "Url: {}", url);
|
||||
|
||||
const auto jtag = env->NewStringUTF(tag.c_str());
|
||||
const auto jtitle = env->NewStringUTF(title.c_str());
|
||||
const auto jbody = env->NewStringUTF(body.c_str());
|
||||
const auto jurl = env->NewStringUTF(url.c_str());
|
||||
|
||||
env->CallVoidMethod(updateResult, setTag, jtag);
|
||||
env->CallVoidMethod(updateResult, setTitle, jtitle);
|
||||
env->CallVoidMethod(updateResult, setBody, jbody);
|
||||
env->CallVoidMethod(updateResult, setUrl, jurl);
|
||||
|
||||
// TODO(crueter): Handling for multiple assets?
|
||||
// Maybe another data class x(
|
||||
for (const Common::Net::Asset &a : assets) {
|
||||
const auto jaurl = env->NewStringUTF(a.path.c_str());
|
||||
env->CallVoidMethod(updateResult, addAsset, jaurl);
|
||||
env->DeleteLocalRef(jaurl);
|
||||
}
|
||||
|
||||
env->DeleteLocalRef(jtag);
|
||||
env->DeleteLocalRef(jtitle);
|
||||
env->DeleteLocalRef(jbody);
|
||||
env->DeleteLocalRef(jurl);
|
||||
|
||||
env->DeleteLocalRef(updateResultClass);
|
||||
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getBuildVersion(
|
||||
|
||||
@@ -1159,5 +1159,6 @@
|
||||
<string name="license_fidelityfx_fsr_description">تحسين الجودة بدرجة عالية من AMD</string>
|
||||
<string name="external_content">محتوى خارجي</string>
|
||||
<string name="add_folders">إضافة مجلد</string>
|
||||
<string name="percent">%1$d%%</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -767,7 +767,7 @@
|
||||
<string name="version">Versión</string>
|
||||
<string name="copy_details">Copiar detalles</string>
|
||||
<string name="add_ons">Complementos</string>
|
||||
<string name="add_ons_description">Activa/desactiva mods, actualizaciones y contenidos descargables</string>
|
||||
<string name="add_ons_description">Alternar mods, actualizaciones y contenido descargable</string>
|
||||
<string name="playtime">Tiempo jugado:</string>
|
||||
<string name="reset_playtime">Borrar tiempo de juego</string>
|
||||
<string name="reset_playtime_description">Restablecer el tiempo de juego actual a 0 segundos</string>
|
||||
@@ -1153,5 +1153,6 @@
|
||||
<string name="license_fidelityfx_fsr_description">Upscaling de alta calidad de AMD</string>
|
||||
<string name="external_content">Contenido externo</string>
|
||||
<string name="add_folders">Añadir carpeta</string>
|
||||
<string name="percent">%1$d%%</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1150,5 +1150,4 @@
|
||||
<string name="license_fidelityfx_fsr_description">Высококачественное масштабирование от AMD</string>
|
||||
<string name="external_content">Дополнительный контент</string>
|
||||
<string name="add_folders">Добавить папку</string>
|
||||
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -1155,5 +1155,6 @@
|
||||
<string name="license_fidelityfx_fsr_description">Високоякісне масштабування від AMD</string>
|
||||
<string name="external_content">Зовнішній вміст</string>
|
||||
<string name="add_folders">Додати теку</string>
|
||||
<string name="percent">%1$d%%</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -483,6 +483,8 @@
|
||||
<string name="enable_buffer_history">启用缓冲区历史</string>
|
||||
<string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string>
|
||||
<string name="use_optimized_vertex_buffers">优化顶点缓冲区</string>
|
||||
<string name="use_optimized_vertex_buffers_description">启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动则会导致崩溃。</string>
|
||||
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
<string name="fast_gpu_time">GPU 超频频率</string>
|
||||
@@ -490,6 +492,9 @@
|
||||
<string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">在内存更新期间跳过某些CPU端缓存无效化,减少CPU使用率并提高其性能。可能会导致某些游戏出现故障或崩溃。</string>
|
||||
<string name="fix_bloom_effects">修复 Bloom 效果</string>
|
||||
<string name="fix_bloom_effects_description">减少《塞尔达传说:智慧的再现》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。</string>
|
||||
<string name="emulate_bgr565">模拟 BGR565</string>
|
||||
<string name="emulate_bgr565_description">修复了游戏中的颜色反转以及出现的异常画面瑕疵或奇怪阴影问题</string>
|
||||
<string name="renderer_asynchronous_shaders">使用异步着色器</string>
|
||||
<string name="renderer_asynchronous_shaders_description">异步编译着色器。这可能会减少卡顿,但也可能会导致图形错误。</string>
|
||||
<string name="gpu_unswizzle_settings">GPU 还原设置</string>
|
||||
@@ -1144,5 +1149,6 @@
|
||||
<string name="license_fidelityfx_fsr_description">AMD 的高品质画面增强技术</string>
|
||||
<string name="external_content">外部内容</string>
|
||||
<string name="add_folders">添加文件夹</string>
|
||||
<string name="percent">%1$d%%</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1783,5 +1783,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
<string name="external_content">External Content</string>
|
||||
<string name="add_folders">Add Folder</string>
|
||||
<string name="percent">%1$d%%</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -147,7 +147,8 @@ add_library(
|
||||
zstd_compression.h
|
||||
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
|
||||
fs/symlink.h fs/symlink.cpp
|
||||
httplib.h)
|
||||
httplib.h
|
||||
net/net.h net/net.cpp)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(common PRIVATE windows/timer_resolution.cpp
|
||||
@@ -245,7 +246,7 @@ else()
|
||||
target_link_libraries(common PUBLIC Boost::headers)
|
||||
endif()
|
||||
|
||||
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib)
|
||||
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json)
|
||||
|
||||
if (lz4_ADDED)
|
||||
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
namespace Common {
|
||||
|
||||
template <typename BaseAddr>
|
||||
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
|
||||
std::size_t first_level_bits_,
|
||||
std::size_t page_bits_)
|
||||
: address_space_bits{address_space_bits_},
|
||||
first_level_bits{first_level_bits_}, page_bits{page_bits_} {
|
||||
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_, std::size_t first_level_bits_, std::size_t page_bits_)
|
||||
: address_space_bits{address_space_bits_}
|
||||
, first_level_bits{first_level_bits_}
|
||||
, page_bits{page_bits_}
|
||||
{
|
||||
if (page_bits == 0) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
first_level_shift = address_space_bits - first_level_bits;
|
||||
first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
|
||||
@@ -30,12 +30,9 @@ MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bit
|
||||
void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
|
||||
#else
|
||||
void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
|
||||
|
||||
if (base == MAP_FAILED) {
|
||||
if (base == MAP_FAILED)
|
||||
base = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
ASSERT(base);
|
||||
base_ptr = reinterpret_cast<BaseAddr*>(base);
|
||||
}
|
||||
@@ -56,29 +53,21 @@ template <typename BaseAddr>
|
||||
void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
|
||||
const u64 new_start = start >> first_level_shift;
|
||||
const u64 new_end = (start + size) >> first_level_shift;
|
||||
for (u64 i = new_start; i <= new_end; i++) {
|
||||
if (!first_level_map[i]) {
|
||||
for (u64 i = new_start; i <= new_end; i++)
|
||||
if (!first_level_map[i])
|
||||
AllocateLevel(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BaseAddr>
|
||||
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
|
||||
void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
|
||||
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 index) {
|
||||
void* ptr = reinterpret_cast<char *>(base_ptr) + index * first_level_chunk_size;
|
||||
#ifdef _WIN32
|
||||
void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
|
||||
#else
|
||||
void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
|
||||
|
||||
if (base == MAP_FAILED) {
|
||||
base = nullptr;
|
||||
}
|
||||
#endif
|
||||
void* base = VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE);
|
||||
ASSERT(base);
|
||||
|
||||
first_level_map[level] = base;
|
||||
#else
|
||||
void* base = ptr;
|
||||
#endif
|
||||
first_level_map[index] = base;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -142,10 +142,8 @@ struct PageTable {
|
||||
VirtualBuffer<PageEntryData> entries;
|
||||
static_assert(sizeof(PageEntryData) == 32);
|
||||
|
||||
std::size_t current_address_space_width_in_bits{};
|
||||
|
||||
u8* fastmem_arena{};
|
||||
|
||||
std::size_t current_address_space_width_in_bits{};
|
||||
std::size_t page_size{};
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#define BUILD_AUTO_UPDATE_API "@BUILD_AUTO_UPDATE_API@"
|
||||
#define BUILD_AUTO_UPDATE_API_PATH "@BUILD_AUTO_UPDATE_API_PATH@"
|
||||
#define BUILD_AUTO_UPDATE_REPO "@BUILD_AUTO_UPDATE_REPO@"
|
||||
#define BUILD_AUTO_UPDATE_STABLE_API "@BUILD_AUTO_UPDATE_STABLE_API@"
|
||||
#define BUILD_AUTO_UPDATE_STABLE_API_PATH "@BUILD_AUTO_UPDATE_STABLE_API_PATH@"
|
||||
#define BUILD_AUTO_UPDATE_STABLE_REPO "@BUILD_AUTO_UPDATE_STABLE_REPO@"
|
||||
#define IS_NIGHTLY_BUILD @IS_NIGHTLY_BUILD@
|
||||
|
||||
namespace Common {
|
||||
@@ -45,5 +48,8 @@ constexpr const char g_build_auto_update_website[] = BUILD_AUTO_UPDATE_WEBSITE;
|
||||
constexpr const char g_build_auto_update_api[] = BUILD_AUTO_UPDATE_API;
|
||||
constexpr const char g_build_auto_update_api_path[] = BUILD_AUTO_UPDATE_API_PATH;
|
||||
constexpr const char g_build_auto_update_repo[] = BUILD_AUTO_UPDATE_REPO;
|
||||
constexpr const char g_build_auto_update_stable_api[] = BUILD_AUTO_UPDATE_STABLE_API;
|
||||
constexpr const char g_build_auto_update_stable_api_path[] = BUILD_AUTO_UPDATE_STABLE_API_PATH;
|
||||
constexpr const char g_build_auto_update_stable_repo[] = BUILD_AUTO_UPDATE_STABLE_REPO;
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -28,5 +28,8 @@ extern const char g_build_auto_update_website[];
|
||||
extern const char g_build_auto_update_api[];
|
||||
extern const char g_build_auto_update_api_path[];
|
||||
extern const char g_build_auto_update_repo[];
|
||||
extern const char g_build_auto_update_stable_api[];
|
||||
extern const char g_build_auto_update_stable_api_path[];
|
||||
extern const char g_build_auto_update_stable_repo[];
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+18
-17
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -21,7 +24,9 @@ public:
|
||||
// "with the current allocator");
|
||||
|
||||
constexpr VirtualBuffer() = default;
|
||||
explicit VirtualBuffer(std::size_t count) : alloc_size{count * sizeof(T)} {
|
||||
explicit VirtualBuffer(std::size_t count) noexcept
|
||||
: alloc_size{count * sizeof(T)}
|
||||
{
|
||||
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
||||
}
|
||||
|
||||
@@ -33,8 +38,8 @@ public:
|
||||
VirtualBuffer& operator=(const VirtualBuffer&) = delete;
|
||||
|
||||
VirtualBuffer(VirtualBuffer&& other) noexcept
|
||||
: alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr),
|
||||
nullptr} {}
|
||||
: alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr), nullptr}
|
||||
{}
|
||||
|
||||
VirtualBuffer& operator=(VirtualBuffer&& other) noexcept {
|
||||
alloc_size = std::exchange(other.alloc_size, 0);
|
||||
@@ -42,35 +47,31 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
void resize(std::size_t count) {
|
||||
const auto new_size = count * sizeof(T);
|
||||
if (new_size == alloc_size) {
|
||||
return;
|
||||
void resize(std::size_t count) noexcept {
|
||||
if (auto const new_size = count * sizeof(T); new_size != alloc_size) {
|
||||
FreeMemoryPages(base_ptr, alloc_size);
|
||||
alloc_size = new_size;
|
||||
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
||||
}
|
||||
|
||||
FreeMemoryPages(base_ptr, alloc_size);
|
||||
|
||||
alloc_size = new_size;
|
||||
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const T& operator[](std::size_t index) const {
|
||||
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept {
|
||||
return base_ptr[index];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr T& operator[](std::size_t index) {
|
||||
[[nodiscard]] constexpr T& operator[](std::size_t index) noexcept {
|
||||
return base_ptr[index];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr T* data() {
|
||||
[[nodiscard]] constexpr T* data() noexcept {
|
||||
return base_ptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const T* data() const {
|
||||
[[nodiscard]] constexpr const T* data() const noexcept {
|
||||
return base_ptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::size_t size() const {
|
||||
[[nodiscard]] constexpr std::size_t size() const noexcept {
|
||||
return alloc_size / sizeof(T);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
@@ -283,7 +283,7 @@ std::string_view GDBStubA32::GetTargetXML() const {
|
||||
<reg name="r11" bitsize="32" type="uint32"/>
|
||||
<reg name="r12" bitsize="32" type="uint32"/>
|
||||
<reg name="sp" bitsize="32" type="data_ptr"/>
|
||||
<reg name="lr" bitsize="32" type="code_ptr"/>
|
||||
<reg name="lr" bitsize="32"/>
|
||||
<reg name="pc" bitsize="32" type="code_ptr"/>
|
||||
<!-- The CPSR is register 25, rather than register 16, because
|
||||
the FPA registers historically were placed between the PC
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -109,7 +109,7 @@ Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_e
|
||||
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
|
||||
|
||||
// Check that our range is valid.
|
||||
BucketTree::Offsets table_offsets;
|
||||
BucketTree::Offsets table_offsets{};
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
|
||||
@@ -167,7 +167,7 @@ size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset
|
||||
ASSERT(Common::IsAligned(offset, BlockSize));
|
||||
ASSERT(Common::IsAligned(size, BlockSize));
|
||||
|
||||
BucketTree::Offsets table_offsets;
|
||||
BucketTree::Offsets table_offsets{};
|
||||
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets))));
|
||||
|
||||
ASSERT(table_offsets.IsInclude(offset, size));
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -89,7 +92,7 @@ public:
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
BucketTree::Offsets offsets;
|
||||
BucketTree::Offsets offsets{};
|
||||
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets))));
|
||||
|
||||
return offsets.end_offset;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -270,7 +270,7 @@ Result BucketTree::Find(Visitor* visitor, s64 virtual_address) {
|
||||
R_UNLESS(virtual_address >= 0, ResultInvalidOffset);
|
||||
R_UNLESS(!this->IsEmpty(), ResultOutOfRange);
|
||||
|
||||
BucketTree::Offsets offsets;
|
||||
BucketTree::Offsets offsets{};
|
||||
R_TRY(this->GetOffsets(std::addressof(offsets)));
|
||||
|
||||
R_TRY(visitor->Initialize(this, offsets));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -125,7 +125,7 @@ private:
|
||||
}
|
||||
|
||||
// Get the table offsets.
|
||||
BucketTree::Offsets table_offsets;
|
||||
BucketTree::Offsets table_offsets{};
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
// Validate arguments.
|
||||
@@ -177,7 +177,7 @@ private:
|
||||
ASSERT(out != nullptr);
|
||||
|
||||
// Get our table offsets.
|
||||
BucketTree::Offsets offsets;
|
||||
BucketTree::Offsets offsets{};
|
||||
R_TRY(m_table.GetOffsets(std::addressof(offsets)));
|
||||
|
||||
// Set the output.
|
||||
@@ -195,7 +195,7 @@ private:
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// Get the table offsets.
|
||||
BucketTree::Offsets table_offsets;
|
||||
BucketTree::Offsets table_offsets{};
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
// Validate arguments.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -52,7 +55,7 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s
|
||||
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
|
||||
|
||||
// Check that our range is valid.
|
||||
BucketTree::Offsets table_offsets;
|
||||
BucketTree::Offsets table_offsets{};
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -164,7 +167,7 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// Get the table offsets.
|
||||
BucketTree::Offsets table_offsets;
|
||||
BucketTree::Offsets table_offsets{};
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
// Validate arguments.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -18,7 +21,7 @@ size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
SparseStorage* self = const_cast<SparseStorage*>(this);
|
||||
|
||||
if (self->GetEntryTable().IsEmpty()) {
|
||||
BucketTree::Offsets table_offsets;
|
||||
BucketTree::Offsets table_offsets{};
|
||||
ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets))));
|
||||
ASSERT(table_offsets.IsInclude(offset, size));
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -191,8 +191,7 @@ Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start
|
||||
m_cached_physical_heap_region = nullptr;
|
||||
|
||||
// Initialize our implementation.
|
||||
m_impl = std::make_unique<Common::PageTable>();
|
||||
m_impl->Resize(m_address_space_width, PageBits);
|
||||
m_impl.Resize(m_address_space_width, PageBits);
|
||||
|
||||
// Set the tracking memory.
|
||||
m_memory = std::addressof(memory);
|
||||
@@ -202,13 +201,7 @@ Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start
|
||||
m_memory_block_slab_manager));
|
||||
}
|
||||
|
||||
Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr,
|
||||
bool enable_das_merge, bool from_back,
|
||||
KMemoryManager::Pool pool, KProcessAddress code_address,
|
||||
size_t code_size, KSystemResource* system_resource,
|
||||
KResourceLimit* resource_limit,
|
||||
Core::Memory::Memory& memory,
|
||||
KProcessAddress aslr_space_start) {
|
||||
Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr, bool enable_das_merge, bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address, size_t code_size, KSystemResource* system_resource, KResourceLimit* resource_limit, Core::Memory::Memory& memory, KProcessAddress aslr_space_start) {
|
||||
// Calculate region extents.
|
||||
const size_t as_width = GetAddressSpaceWidth(as_type);
|
||||
const KProcessAddress start = 0;
|
||||
@@ -319,14 +312,10 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
|
||||
// Determine random placements for each region.
|
||||
size_t alias_rnd = 0, heap_rnd = 0, stack_rnd = 0, kmap_rnd = 0;
|
||||
if (enable_aslr) {
|
||||
alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
|
||||
RegionAlignment;
|
||||
heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
|
||||
RegionAlignment;
|
||||
stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
|
||||
RegionAlignment;
|
||||
kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
|
||||
RegionAlignment;
|
||||
alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
|
||||
heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
|
||||
stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
|
||||
kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment;
|
||||
}
|
||||
|
||||
// Setup heap and alias regions.
|
||||
@@ -445,15 +434,13 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
|
||||
ASSERT(heap_last < kmap_start || kmap_last < heap_start);
|
||||
|
||||
// Initialize our implementation.
|
||||
m_impl = std::make_unique<Common::PageTable>();
|
||||
m_impl->Resize(m_address_space_width, PageBits);
|
||||
m_impl.Resize(m_address_space_width, PageBits);
|
||||
|
||||
// Set the tracking memory.
|
||||
m_memory = std::addressof(memory);
|
||||
|
||||
// Initialize our memory block manager.
|
||||
R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end,
|
||||
m_memory_block_slab_manager));
|
||||
R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, m_memory_block_slab_manager));
|
||||
}
|
||||
|
||||
Result KPageTableBase::FinalizeProcess() {
|
||||
@@ -476,7 +463,7 @@ void KPageTableBase::Finalize() {
|
||||
this->FinalizeProcess();
|
||||
|
||||
auto BlockCallback = [&](KProcessAddress addr, u64 size) {
|
||||
if (m_impl->fastmem_arena) {
|
||||
if (m_impl.fastmem_arena) {
|
||||
m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
|
||||
}
|
||||
|
||||
@@ -514,9 +501,6 @@ void KPageTableBase::Finalize() {
|
||||
m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
|
||||
m_mapped_ipc_server_memory);
|
||||
}
|
||||
|
||||
// Close the backing page table, as the destructor is not called for guest objects.
|
||||
m_impl.reset();
|
||||
}
|
||||
|
||||
KProcessAddress KPageTableBase::GetRegionAddress(Svc::MemoryState state) const {
|
||||
@@ -2349,7 +2333,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
|
||||
TraversalContext context;
|
||||
TraversalEntry next_entry;
|
||||
bool traverse_valid =
|
||||
m_impl->BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr);
|
||||
m_impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr);
|
||||
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
||||
|
||||
// Set tracking variables.
|
||||
@@ -2360,7 +2344,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
|
||||
while (true) {
|
||||
// Continue the traversal.
|
||||
traverse_valid =
|
||||
m_impl->ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
||||
m_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
||||
if (!traverse_valid) {
|
||||
break;
|
||||
}
|
||||
@@ -5730,14 +5714,14 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
|
||||
this->MakePageGroup(pages_to_close, virt_addr, num_pages);
|
||||
|
||||
// Unmap.
|
||||
m_memory->UnmapRegion(*m_impl, virt_addr, num_pages * PageSize, separate_heap);
|
||||
m_memory->UnmapRegion(m_impl, virt_addr, num_pages * PageSize, separate_heap);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
case OperationType::Map: {
|
||||
ASSERT(virt_addr != 0);
|
||||
ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
|
||||
m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr,
|
||||
m_memory->MapMemoryRegion(m_impl, virt_addr, num_pages * PageSize, phys_addr,
|
||||
ConvertToMemoryPermission(properties.perm), false);
|
||||
|
||||
// Open references to pages, if we should.
|
||||
@@ -5754,7 +5738,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
|
||||
case OperationType::ChangePermissions:
|
||||
case OperationType::ChangePermissionsAndRefresh:
|
||||
case OperationType::ChangePermissionsAndRefreshAndFlush: {
|
||||
m_memory->ProtectRegion(*m_impl, virt_addr, num_pages * PageSize,
|
||||
m_memory->ProtectRegion(m_impl, virt_addr, num_pages * PageSize,
|
||||
ConvertToMemoryPermission(properties.perm));
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -5788,7 +5772,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
|
||||
const size_t size{node.GetNumPages() * PageSize};
|
||||
|
||||
// Map the pages.
|
||||
m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress(),
|
||||
m_memory->MapMemoryRegion(m_impl, virt_addr, size, node.GetAddress(),
|
||||
ConvertToMemoryPermission(properties.perm), separate_heap);
|
||||
|
||||
virt_addr += size;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -215,7 +215,7 @@ private:
|
||||
mutable KLightLock m_general_lock;
|
||||
mutable KLightLock m_map_physical_memory_lock;
|
||||
KLightLock m_device_map_lock;
|
||||
std::unique_ptr<Common::PageTable> m_impl{};
|
||||
Common::PageTable m_impl{};
|
||||
Core::Memory::Memory* m_memory{};
|
||||
KMemoryBlockManager m_memory_block_manager{};
|
||||
u32 m_allocate_option{};
|
||||
@@ -300,26 +300,11 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
Core::Memory::Memory& GetMemory() {
|
||||
return *m_memory;
|
||||
}
|
||||
|
||||
Core::Memory::Memory& GetMemory() const {
|
||||
return *m_memory;
|
||||
}
|
||||
|
||||
Common::PageTable& GetImpl() {
|
||||
return *m_impl;
|
||||
}
|
||||
|
||||
Common::PageTable& GetImpl() const {
|
||||
return *m_impl;
|
||||
}
|
||||
|
||||
size_t GetNumGuardPages() const {
|
||||
return this->IsKernel() ? 1 : 4;
|
||||
}
|
||||
|
||||
[[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return *m_memory; }
|
||||
[[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return *m_memory; }
|
||||
[[nodiscard]] Common::PageTable& GetImpl() noexcept { return m_impl; }
|
||||
[[nodiscard]] Common::PageTable const& GetImpl() const noexcept { return m_impl; }
|
||||
[[nodiscard]] size_t GetNumGuardPages() const noexcept { return this->IsKernel() ? 1 : 4; }
|
||||
protected:
|
||||
// NOTE: These three functions (Operate, Operate, FinalizeUpdate) are virtual functions
|
||||
// in Nintendo's kernel. We devirtualize them, since KPageTable is the only derived
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -33,21 +33,10 @@ public:
|
||||
m_page_table.Finalize();
|
||||
}
|
||||
|
||||
Core::Memory::Memory& GetMemory() {
|
||||
return m_page_table.GetMemory();
|
||||
}
|
||||
|
||||
Core::Memory::Memory& GetMemory() const {
|
||||
return m_page_table.GetMemory();
|
||||
}
|
||||
|
||||
Common::PageTable& GetImpl() {
|
||||
return m_page_table.GetImpl();
|
||||
}
|
||||
|
||||
Common::PageTable& GetImpl() const {
|
||||
return m_page_table.GetImpl();
|
||||
}
|
||||
[[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return m_page_table.GetMemory(); }
|
||||
[[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return m_page_table.GetMemory(); }
|
||||
[[nodiscard]] Common::PageTable& GetImpl() noexcept { return m_page_table.GetImpl(); }
|
||||
[[nodiscard]] Common::PageTable const& GetImpl() const noexcept { return m_page_table.GetImpl(); }
|
||||
|
||||
size_t GetNumGuardPages() const {
|
||||
return m_page_table.GetNumGuardPages();
|
||||
|
||||
@@ -50,6 +50,14 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
|
||||
};
|
||||
|
||||
const auto ExitContext = [&]() {
|
||||
// Save the JIT context back to the thread so the debugger can
|
||||
// read the current register state. Debug halt paths (step,
|
||||
// breakpoint, watchpoint) return from RunThread without going
|
||||
// through the scheduler's Unload/SaveContext.
|
||||
if (system.DebuggerEnabled()) {
|
||||
interface->GetContext(thread->GetContext());
|
||||
}
|
||||
|
||||
// Unlock the thread.
|
||||
interface->UnlockThread(thread);
|
||||
|
||||
@@ -97,11 +105,16 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
|
||||
}
|
||||
|
||||
// Determine why we stopped.
|
||||
const bool supervisor_call = True(hr & Core::HaltReason::SupervisorCall);
|
||||
const bool prefetch_abort = True(hr & Core::HaltReason::PrefetchAbort);
|
||||
const bool breakpoint = True(hr & Core::HaltReason::InstructionBreakpoint);
|
||||
const bool data_abort = True(hr & Core::HaltReason::DataAbort);
|
||||
const bool interrupt = True(hr & Core::HaltReason::BreakLoop);
|
||||
// If a step completed successfully, skip other halt reason handlers —
|
||||
// the step takes priority (e.g. step may also set InstructionBreakpoint
|
||||
// if the next instruction happens to be a breakpoint).
|
||||
const bool step_completed = True(hr & Core::HaltReason::StepThread)
|
||||
&& thread->GetStepState() == StepState::StepPerformed;
|
||||
const bool supervisor_call = !step_completed && True(hr & Core::HaltReason::SupervisorCall);
|
||||
const bool prefetch_abort = !step_completed && True(hr & Core::HaltReason::PrefetchAbort);
|
||||
const bool breakpoint = !step_completed && True(hr & Core::HaltReason::InstructionBreakpoint);
|
||||
const bool data_abort = !step_completed && True(hr & Core::HaltReason::DataAbort);
|
||||
const bool interrupt = !step_completed && True(hr & Core::HaltReason::BreakLoop);
|
||||
|
||||
// Since scheduling may occur here, we cannot use any cached
|
||||
// state after returning from calls we make.
|
||||
@@ -111,6 +124,11 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
|
||||
if (breakpoint || prefetch_abort) {
|
||||
if (breakpoint) {
|
||||
interface->RewindBreakpointInstruction();
|
||||
// RewindBreakpointInstruction sets the JIT state to the
|
||||
// saved breakpoint context. Update the thread context to
|
||||
// match, since ExitContext already saved the post-execution
|
||||
// state.
|
||||
interface->GetContext(thread->GetContext());
|
||||
}
|
||||
if (system.DebuggerEnabled()) {
|
||||
system.GetDebugger().NotifyThreadStopped(thread);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/net/net.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "core/hle/service/bcat/news/builtin_news.h"
|
||||
#include "core/hle/service/bcat/news/msgpack.h"
|
||||
#include "core/hle/service/bcat/news/news_storage.h"
|
||||
@@ -22,10 +24,8 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <iomanip>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
@@ -35,9 +35,6 @@
|
||||
namespace Service::News {
|
||||
namespace {
|
||||
|
||||
// TODO(crueter): COMPILE DEFINITION
|
||||
constexpr const char* GitHubAPI_EdenReleases = "/api/v1/repos/eden-emu/eden/releases";
|
||||
|
||||
// Cached logo data
|
||||
std::vector<u8> default_logo_small;
|
||||
std::vector<u8> default_logo_large;
|
||||
@@ -66,24 +63,6 @@ u32 HashToNewsId(std::string_view key) {
|
||||
return static_cast<u32>(std::hash<std::string_view>{}(key) & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
u64 ParseIsoTimestamp(const std::string& iso) {
|
||||
if (iso.empty()) return 0;
|
||||
|
||||
std::string buf = iso;
|
||||
if (buf.back() == 'Z') buf.pop_back();
|
||||
|
||||
std::tm tm{};
|
||||
std::istringstream ss(buf);
|
||||
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
|
||||
if (ss.fail()) return 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
return static_cast<u64>(_mkgmtime(&tm));
|
||||
#else
|
||||
return static_cast<u64>(timegm(&tm));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> TryLoadFromDisk(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::exists(path)) return {};
|
||||
|
||||
@@ -100,8 +79,9 @@ std::vector<u8> TryLoadFromDisk(const std::filesystem::path& path) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// TODO(crueter): Migrate to use Common::Net
|
||||
std::vector<u8> DownloadImage(const std::string& url_path, const std::filesystem::path& cache_path) {
|
||||
LOG_INFO(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path);
|
||||
LOG_DEBUG(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path);
|
||||
try {
|
||||
httplib::Client cli("https://eden-emu.dev");
|
||||
cli.set_follow_location(true);
|
||||
@@ -226,67 +206,6 @@ void WriteCachedJson(std::string_view json) {
|
||||
(void)Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, json);
|
||||
}
|
||||
|
||||
std::optional<std::string> DownloadReleasesJson() {
|
||||
try {
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
const auto url = "https://git.eden-emu.dev";
|
||||
#else
|
||||
const auto url = "git.eden-emu.dev";
|
||||
#endif
|
||||
|
||||
// TODO(crueter): This is duplicated between frontend and here.
|
||||
constexpr auto path = GitHubAPI_EdenReleases;
|
||||
|
||||
constexpr std::size_t timeout_seconds = 15;
|
||||
|
||||
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(url);
|
||||
client->set_connection_timeout(timeout_seconds);
|
||||
client->set_read_timeout(timeout_seconds);
|
||||
client->set_write_timeout(timeout_seconds);
|
||||
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
client->load_ca_cert_store(kCert, sizeof(kCert));
|
||||
#endif
|
||||
|
||||
if (client == nullptr) {
|
||||
LOG_ERROR(Service_BCAT, "Invalid URL {}{}", url, path);
|
||||
return {};
|
||||
}
|
||||
|
||||
httplib::Request request{
|
||||
.method = "GET",
|
||||
.path = path,
|
||||
};
|
||||
|
||||
client->set_follow_location(true);
|
||||
httplib::Result result = client->send(request);
|
||||
|
||||
if (!result) {
|
||||
LOG_ERROR(Service_BCAT, "GET to {}{} returned null", url, path);
|
||||
return {};
|
||||
} else if (result->status < 400) {
|
||||
return result->body;
|
||||
}
|
||||
|
||||
if (result->status >= 400) {
|
||||
LOG_ERROR(Service_BCAT,
|
||||
"GET to {}{} returned error status code: {}",
|
||||
url,
|
||||
path,
|
||||
result->status);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!result->headers.contains("content-type")) {
|
||||
LOG_ERROR(Service_BCAT, "GET to {}{} returned no content", url, path);
|
||||
return {};
|
||||
}
|
||||
} catch (...) {
|
||||
LOG_WARNING(Service_BCAT, " failed to download releases");
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// idk but News App does not render Markdown or HTML, so remove some formatting.
|
||||
std::string SanitizeMarkdown(std::string_view markdown) {
|
||||
std::string result;
|
||||
@@ -342,9 +261,7 @@ std::string SanitizeMarkdown(std::string_view markdown) {
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string FormatBody(const nlohmann::json& release, std::string_view title) {
|
||||
std::string body = release.value("body", std::string{});
|
||||
|
||||
std::string FormatBody(std::string body, const std::string_view &title) {
|
||||
if (body.empty()) {
|
||||
return std::string(title);
|
||||
}
|
||||
@@ -375,52 +292,32 @@ std::string FormatBody(const nlohmann::json& release, std::string_view title) {
|
||||
return body;
|
||||
}
|
||||
|
||||
void ImportReleases(std::string_view json_text) {
|
||||
nlohmann::json root;
|
||||
try {
|
||||
root = nlohmann::json::parse(json_text);
|
||||
} catch (...) {
|
||||
LOG_WARNING(Service_BCAT, "failed to parse JSON");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!root.is_array()) return;
|
||||
|
||||
void ImportReleases(const std::vector<Common::Net::Release> &releases) {
|
||||
std::vector<u32> news_ids;
|
||||
for (const auto& rel : root) {
|
||||
if (!rel.is_object()) continue;
|
||||
std::string title = rel.value("name", rel.value("tag_name", std::string{}));
|
||||
if (title.empty()) continue;
|
||||
|
||||
const u64 release_id = rel.value("id", 0);
|
||||
const u32 news_id = release_id ? static_cast<u32>(release_id & 0x7FFFFFFF) : HashToNewsId(title);
|
||||
for (const auto& rel : releases) {
|
||||
const u32 news_id = u32(rel.id & 0x7FFFFFFF);
|
||||
news_ids.push_back(news_id);
|
||||
}
|
||||
|
||||
PreloadNewsImages(news_ids);
|
||||
|
||||
for (const auto& rel : root) {
|
||||
if (!rel.is_object()) continue;
|
||||
for (const auto& rel : releases) {
|
||||
const std::string title = rel.title;
|
||||
const std::string body = rel.body;
|
||||
const std::string html_url = rel.html_url;
|
||||
|
||||
std::string title = rel.value("name", rel.value("tag_name", std::string{}));
|
||||
if (title.empty()) continue;
|
||||
|
||||
const u64 release_id = rel.value("id", 0);
|
||||
const u32 news_id = release_id ? static_cast<u32>(release_id & 0x7FFFFFFF) : HashToNewsId(title);
|
||||
const u64 published = ParseIsoTimestamp(rel.value("published_at", std::string{}));
|
||||
const u32 news_id = u32(rel.id & 0x7FFFFFFF);
|
||||
const u64 published = rel.published;
|
||||
const u64 pickup_limit = published + 600000000;
|
||||
const u32 priority = rel.value("prerelease", false) ? 1500 : 2500;
|
||||
const u32 priority = rel.prerelease ? 1500 : 2500;
|
||||
|
||||
std::string author = "eden";
|
||||
if (rel.contains("author") && rel["author"].is_object()) {
|
||||
author = rel["author"].value("login", "eden");
|
||||
}
|
||||
std::string author = "Eden";
|
||||
|
||||
auto payload = BuildMsgpack(title, FormatBody(rel, title), title, published,
|
||||
auto payload = BuildMsgpack(title, FormatBody(body, title), title, published,
|
||||
pickup_limit, priority, {"en"}, author, {},
|
||||
rel.value("html_url", std::string{}), news_id);
|
||||
html_url, news_id);
|
||||
|
||||
const std::string news_id_str = fmt::format("LA{:020}", news_id);
|
||||
const std::string news_id_str = fmt::format("LA{:020}", rel.id);
|
||||
|
||||
GithubNewsMeta meta{
|
||||
.news_id = news_id_str,
|
||||
@@ -565,15 +462,21 @@ void EnsureBuiltinNewsLoaded() {
|
||||
LoadDefaultLogos();
|
||||
|
||||
if (const auto cached = ReadCachedJson()) {
|
||||
ImportReleases(*cached);
|
||||
LOG_DEBUG(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size());
|
||||
const std::string_view body = cached.value();
|
||||
const auto releases = Common::Net::Release::ListFromJson(body, Common::g_build_auto_update_stable_api, Common::g_build_auto_update_stable_repo);
|
||||
ImportReleases(releases);
|
||||
|
||||
LOG_INFO(Service_BCAT, "news: {} entries loaded from cache", NewsStorage::Instance().ListAll().size());
|
||||
}
|
||||
|
||||
std::thread([] {
|
||||
if (const auto fresh = DownloadReleasesJson()) {
|
||||
WriteCachedJson(*fresh);
|
||||
ImportReleases(*fresh);
|
||||
LOG_DEBUG(Service_BCAT, "news: {} entries updated from Forgejo", NewsStorage::Instance().ListAll().size());
|
||||
if (const auto fresh = Common::Net::GetReleasesBody()) {
|
||||
const std::string_view body = fresh.value();
|
||||
WriteCachedJson(body);
|
||||
const auto releases = Common::Net::Release::ListFromJson(body, Common::g_build_auto_update_stable_api, Common::g_build_auto_update_stable_repo);
|
||||
ImportReleases(releases);
|
||||
|
||||
LOG_INFO(Service_BCAT, "news: {} entries updated from Forgejo", NewsStorage::Instance().ListAll().size());
|
||||
}
|
||||
}).detach();
|
||||
});
|
||||
|
||||
@@ -89,7 +89,7 @@ void NvMap::UnmapHandle(Handle& handle_description) {
|
||||
|
||||
// Free and unmap the handle from Host1x GMMU
|
||||
if (handle_description.pin_virt_address) {
|
||||
host1x.GMMU().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
|
||||
host1x.gmmu_manager.Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
|
||||
handle_description.aligned_size);
|
||||
host1x.Allocator().Free(handle_description.pin_virt_address,
|
||||
static_cast<u32>(handle_description.aligned_size));
|
||||
@@ -169,12 +169,8 @@ DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
|
||||
std::scoped_lock lock(handle_description->mutex);
|
||||
const auto map_low_area = [&] {
|
||||
if (handle_description->pin_virt_address == 0) {
|
||||
auto& gmmu_allocator = host1x.Allocator();
|
||||
auto& gmmu = host1x.GMMU();
|
||||
u32 address =
|
||||
gmmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size));
|
||||
gmmu.Map(static_cast<GPUVAddr>(address), handle_description->d_address,
|
||||
handle_description->aligned_size);
|
||||
u32 address = host1x.Allocator().Allocate(u32(handle_description->aligned_size));
|
||||
host1x.gmmu_manager.Map(GPUVAddr(address), handle_description->d_address, handle_description->aligned_size);
|
||||
handle_description->pin_virt_address = address;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
@@ -130,12 +131,12 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
|
||||
|
||||
const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
|
||||
const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
|
||||
vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
|
||||
vm.small_page_allocator.emplace(start_pages, end_pages);
|
||||
|
||||
const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
|
||||
const auto end_big_pages{
|
||||
static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
|
||||
vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
|
||||
vm.big_page_allocator.emplace(start_big_pages, end_big_pages);
|
||||
|
||||
gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split,
|
||||
vm.big_page_size_bits, VM::PAGE_SIZE_BITS);
|
||||
@@ -188,8 +189,8 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
|
||||
}
|
||||
|
||||
allocation_map[params.offset] = {
|
||||
.size = size,
|
||||
.mappings{},
|
||||
.size = size,
|
||||
.page_size = params.page_size,
|
||||
.sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
|
||||
.big_pages = params.page_size != VM::YUZU_PAGESIZE,
|
||||
@@ -199,71 +200,52 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
|
||||
}
|
||||
|
||||
void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
|
||||
auto mapping{mapping_map.at(offset)};
|
||||
|
||||
if (!mapping->fixed) {
|
||||
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
|
||||
u64 aligned_size{Common::AlignUp(mapping->size, page_size)};
|
||||
|
||||
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
|
||||
static_cast<u32>(aligned_size >> page_size_bits));
|
||||
auto const it = mapping_map.find(offset);
|
||||
auto const mapping = it->second;
|
||||
if (!mapping.fixed) {
|
||||
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
u32 page_size{mapping.big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
|
||||
u64 aligned_size{Common::AlignUp(mapping.size, page_size)};
|
||||
allocator.Free(u32(mapping.offset >> page_size_bits), u32(aligned_size >> page_size_bits));
|
||||
}
|
||||
|
||||
nvmap.UnpinHandle(mapping->handle);
|
||||
|
||||
nvmap.UnpinHandle(mapping.handle);
|
||||
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
|
||||
// Only FreeSpace can unmap them fully
|
||||
if (mapping->sparse_alloc) {
|
||||
gmmu->MapSparse(offset, mapping->size, mapping->big_page);
|
||||
if (mapping.sparse_alloc) {
|
||||
gmmu->MapSparse(offset, mapping.size, mapping.big_page);
|
||||
} else {
|
||||
gmmu->Unmap(offset, mapping->size);
|
||||
gmmu->Unmap(offset, mapping.size);
|
||||
}
|
||||
|
||||
mapping_map.erase(offset);
|
||||
mapping_map.erase(it);
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
|
||||
params.pages, params.page_size);
|
||||
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset, params.pages, params.page_size);
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
try {
|
||||
auto allocation{allocation_map[params.offset]};
|
||||
|
||||
if (allocation.page_size != params.page_size ||
|
||||
allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
|
||||
if (auto const it = allocation_map.find(params.offset); it != allocation_map.end()) {
|
||||
auto const allocation = it->second;
|
||||
if (allocation.page_size != params.page_size || allocation.size != (u64(params.pages) * params.page_size))
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
for (const auto& mapping : allocation.mappings) {
|
||||
FreeMappingLocked(mapping->offset);
|
||||
}
|
||||
for (const auto mapping_offset : allocation.mappings)
|
||||
FreeMappingLocked(mapping_offset);
|
||||
|
||||
// Unset sparse flag if required
|
||||
if (allocation.sparse) {
|
||||
if (allocation.sparse)
|
||||
gmmu->Unmap(params.offset, allocation.size);
|
||||
}
|
||||
|
||||
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
|
||||
: *vm.big_page_allocator};
|
||||
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
|
||||
: vm.big_page_size_bits};
|
||||
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator : *vm.big_page_allocator};
|
||||
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS : vm.big_page_size_bits};
|
||||
|
||||
allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
|
||||
static_cast<u32>(allocation.size >> page_size_bits));
|
||||
allocator.Free(u32(params.offset >> page_size_bits), u32(allocation.size >> page_size_bits));
|
||||
allocation_map.erase(params.offset);
|
||||
} catch (const std::out_of_range&) {
|
||||
return NvResult::BadValue;
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
return NvResult::Success;
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) {
|
||||
@@ -327,26 +309,18 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
|
||||
// Remaps a subregion of an existing mapping to a different PA
|
||||
if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
|
||||
try {
|
||||
auto mapping{mapping_map.at(params.offset)};
|
||||
|
||||
if (mapping->size < params.mapping_size) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"Cannot remap a partially mapped GPU address space region: {:#X}",
|
||||
params.offset);
|
||||
if (auto const it = mapping_map.find(params.offset); it != mapping_map.end()) {
|
||||
auto const mapping = it->second;
|
||||
if (mapping.size < params.mapping_size) {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#X}", params.offset);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
|
||||
VAddr device_address{mapping->ptr + params.buffer_offset};
|
||||
|
||||
gmmu->Map(gpu_address, device_address, params.mapping_size,
|
||||
static_cast<Tegra::PTEKind>(params.kind), mapping->big_page);
|
||||
|
||||
u64 gpu_address = u64(params.offset + params.buffer_offset);
|
||||
VAddr device_address{mapping.ptr + params.buffer_offset};
|
||||
gmmu->Map(gpu_address, device_address, params.mapping_size, Tegra::PTEKind(params.kind), mapping.big_page);
|
||||
return NvResult::Success;
|
||||
} catch (const std::out_of_range&) {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}",
|
||||
params.offset);
|
||||
} else {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}", params.offset);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
}
|
||||
@@ -356,8 +330,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
DAddr device_address{
|
||||
static_cast<DAddr>(nvmap.PinHandle(params.handle, false) + params.buffer_offset)};
|
||||
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset);
|
||||
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
|
||||
|
||||
bool big_page{[&]() {
|
||||
@@ -381,32 +354,22 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
}
|
||||
|
||||
const bool use_big_pages = alloc->second.big_pages && big_page;
|
||||
gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind),
|
||||
use_big_pages);
|
||||
gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind), use_big_pages);
|
||||
|
||||
auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
|
||||
true, use_big_pages, alloc->second.sparse)};
|
||||
alloc->second.mappings.push_back(mapping);
|
||||
mapping_map[params.offset] = mapping;
|
||||
alloc->second.mappings.push_back(params.offset);
|
||||
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, true, use_big_pages, alloc->second.sparse));
|
||||
} else {
|
||||
auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
|
||||
u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
|
||||
params.offset = static_cast<u64>(allocator.Allocate(
|
||||
static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
|
||||
<< page_size_bits;
|
||||
params.offset = u64(allocator.Allocate(u32(Common::AlignUp(size, page_size) >> page_size_bits))) << page_size_bits;
|
||||
if (!params.offset) {
|
||||
ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
|
||||
return NvResult::InsufficientMemory;
|
||||
}
|
||||
|
||||
gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size),
|
||||
static_cast<Tegra::PTEKind>(params.kind), big_page);
|
||||
|
||||
auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
|
||||
false, big_page, false)};
|
||||
mapping_map[params.offset] = mapping;
|
||||
gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size), Tegra::PTEKind(params.kind), big_page);
|
||||
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false));
|
||||
}
|
||||
|
||||
map_buffer_offsets.insert(params.offset);
|
||||
@@ -415,37 +378,32 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
|
||||
if (map_buffer_offsets.find(params.offset) != map_buffer_offsets.end()) {
|
||||
std::scoped_lock lock(mutex);
|
||||
if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
auto mapping{mapping_map.at(params.offset)};
|
||||
|
||||
if (!mapping->fixed) {
|
||||
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
|
||||
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
|
||||
static_cast<u32>(mapping->size >> page_size_bits));
|
||||
auto const it = mapping_map.find(params.offset);
|
||||
auto const mapping = it->second;
|
||||
if (!mapping.fixed) {
|
||||
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
allocator.Free(u32(mapping.offset >> page_size_bits), u32(mapping.size >> page_size_bits));
|
||||
}
|
||||
|
||||
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
|
||||
// Only FreeSpace can unmap them fully
|
||||
if (mapping->sparse_alloc) {
|
||||
gmmu->MapSparse(params.offset, mapping->size, mapping->big_page);
|
||||
if (mapping.sparse_alloc) {
|
||||
gmmu->MapSparse(params.offset, mapping.size, mapping.big_page);
|
||||
} else {
|
||||
gmmu->Unmap(params.offset, mapping->size);
|
||||
gmmu->Unmap(params.offset, mapping.size);
|
||||
}
|
||||
|
||||
nvmap.UnpinHandle(mapping->handle);
|
||||
|
||||
mapping_map.erase(params.offset);
|
||||
map_buffer_offsets.erase(params.offset);
|
||||
nvmap.UnpinHandle(mapping.handle);
|
||||
mapping_map.erase(it);
|
||||
map_buffer_offsets.erase(offset_it);
|
||||
}
|
||||
return NvResult::Success;
|
||||
}
|
||||
@@ -478,8 +436,7 @@ void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::GetVARegions1(IoctlGetVaRegions& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
||||
params.buf_size);
|
||||
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr, params.buf_size);
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
|
||||
@@ -165,35 +165,36 @@ private:
|
||||
NvCore::NvMap& nvmap;
|
||||
|
||||
struct Mapping {
|
||||
NvCore::NvMap::Handle::Id handle;
|
||||
DAddr ptr;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
bool fixed;
|
||||
bool big_page; // Only valid if fixed == false
|
||||
bool sparse_alloc;
|
||||
NvCore::NvMap::Handle::Id handle;
|
||||
bool fixed : 1;
|
||||
bool big_page : 1; // Only valid if fixed == false
|
||||
bool sparse_alloc : 1;
|
||||
|
||||
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_,
|
||||
bool big_page_, bool sparse_alloc_)
|
||||
: handle(handle_), ptr(ptr_), offset(offset_), size(size_), fixed(fixed_),
|
||||
big_page(big_page_), sparse_alloc(sparse_alloc_) {}
|
||||
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
|
||||
: ptr(ptr_), offset(offset_), size(size_), handle(handle_)
|
||||
, fixed(fixed_), big_page(big_page_), sparse_alloc(sparse_alloc_)
|
||||
{}
|
||||
};
|
||||
|
||||
struct Allocation {
|
||||
std::vector<u64> mappings;
|
||||
u64 size;
|
||||
std::list<std::shared_ptr<Mapping>> mappings;
|
||||
u32 page_size;
|
||||
bool sparse;
|
||||
bool big_pages;
|
||||
};
|
||||
|
||||
std::map<u64, std::shared_ptr<Mapping>>
|
||||
mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
|
||||
//!< mapping type, this is needed as what was originally a single buffer may
|
||||
//!< have been split into multiple GPU side buffers with the remap flag.
|
||||
std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
|
||||
//!< which fixed buffers can be mapped into
|
||||
std::mutex mutex; //!< Locks all AS operations
|
||||
//!< This maps the base addresses of mapped buffers to their total sizes and
|
||||
//!< mapping type, this is needed as what was originally a single buffer may
|
||||
//!< have been split into multiple GPU side buffers with the remap flag.
|
||||
std::map<u64, Mapping> mapping_map;
|
||||
//!< Holds allocations created by AllocSpace from
|
||||
//!< which fixed buffers can be mapped into
|
||||
std::map<u64, Allocation> allocation_map;
|
||||
std::mutex mutex; //!< Locks all AS operations
|
||||
|
||||
struct VM {
|
||||
static constexpr u32 YUZU_PAGESIZE{0x1000};
|
||||
@@ -213,9 +214,8 @@ private:
|
||||
|
||||
using Allocator = Common::FlatAllocator<u32, 0, 32>;
|
||||
|
||||
std::unique_ptr<Allocator> big_page_allocator;
|
||||
std::shared_ptr<Allocator>
|
||||
small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
|
||||
std::optional<Allocator> big_page_allocator;
|
||||
std::optional<Allocator> small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
|
||||
|
||||
bool initialised{};
|
||||
} vm;
|
||||
|
||||
@@ -228,9 +228,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
code_size += patch_ctx.GetTotalPatchSize();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
|
||||
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) {
|
||||
|
||||
@@ -89,9 +89,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
|
||||
codeset.DataSegment().size += kip->GetBSSSize();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
|
||||
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) {
|
||||
|
||||
@@ -242,9 +242,8 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
}();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
|
||||
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process
|
||||
|
||||
@@ -866,10 +866,10 @@ void EmitIR<IR::Opcode::VectorMaxS32>(oaknut::CodeGenerator& code, EmitContext&
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::VectorMaxS64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
|
||||
(void)code;
|
||||
(void)ctx;
|
||||
(void)inst;
|
||||
UNREACHABLE();
|
||||
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
|
||||
code.CMGT(Qresult->D2(), Qa->D2(), Qb->D2());
|
||||
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
|
||||
});
|
||||
}
|
||||
|
||||
template<>
|
||||
@@ -889,10 +889,10 @@ void EmitIR<IR::Opcode::VectorMaxU32>(oaknut::CodeGenerator& code, EmitContext&
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::VectorMaxU64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
|
||||
(void)code;
|
||||
(void)ctx;
|
||||
(void)inst;
|
||||
UNREACHABLE();
|
||||
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
|
||||
code.CMHI(Qresult->D2(), Qa->D2(), Qb->D2());
|
||||
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
|
||||
});
|
||||
}
|
||||
|
||||
template<>
|
||||
@@ -912,10 +912,10 @@ void EmitIR<IR::Opcode::VectorMinS32>(oaknut::CodeGenerator& code, EmitContext&
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::VectorMinS64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
|
||||
(void)code;
|
||||
(void)ctx;
|
||||
(void)inst;
|
||||
UNREACHABLE();
|
||||
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
|
||||
code.CMGT(Qresult->D2(), Qb->D2(), Qa->D2());
|
||||
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
|
||||
});
|
||||
}
|
||||
|
||||
template<>
|
||||
@@ -935,10 +935,10 @@ void EmitIR<IR::Opcode::VectorMinU32>(oaknut::CodeGenerator& code, EmitContext&
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::VectorMinU64>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) {
|
||||
(void)code;
|
||||
(void)ctx;
|
||||
(void)inst;
|
||||
UNREACHABLE();
|
||||
EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) {
|
||||
code.CMHI(Qresult->D2(), Qb->D2(), Qa->D2());
|
||||
code.BSL(Qresult->B16(), Qa->B16(), Qb->B16());
|
||||
});
|
||||
}
|
||||
|
||||
template<>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -39,7 +39,7 @@ bool TranslatorVisitor::BL(Imm<26> imm26) {
|
||||
ir.PushRSB(ir.current_location->AdvancePC(4));
|
||||
|
||||
const u64 target = ir.PC() + offset;
|
||||
ir.SetTerm(IR::Term::LinkBlock{ir.current_location->SetPC(target)});
|
||||
ir.SetTerm(IR::Term::LinkBlockFast{ir.current_location->SetPC(target)});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,146 +5,45 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#endif
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/logging.h"
|
||||
#include "common/net/net.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "update_checker.h"
|
||||
|
||||
#include "common/httplib.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
#include <openssl/cert.h>
|
||||
#endif
|
||||
std::optional<Common::Net::Release> UpdateChecker::GetUpdate() {
|
||||
const auto latest = Common::Net::GetLatestRelease();
|
||||
if (!latest) return std::nullopt;
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
LOG_INFO(Frontend, "Received update {}", latest->title);
|
||||
|
||||
std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::string path)
|
||||
{
|
||||
try {
|
||||
constexpr std::size_t timeout_seconds = 15;
|
||||
#ifdef NIGHTLY_BUILD
|
||||
std::vector<std::string> result;
|
||||
|
||||
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(url);
|
||||
client->set_connection_timeout(timeout_seconds);
|
||||
client->set_read_timeout(timeout_seconds);
|
||||
client->set_write_timeout(timeout_seconds);
|
||||
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
client->load_ca_cert_store(kCert, sizeof(kCert));
|
||||
#endif
|
||||
|
||||
if (client == nullptr) {
|
||||
LOG_ERROR(Frontend, "Invalid URL {}{}", url, path);
|
||||
return {};
|
||||
}
|
||||
|
||||
httplib::Request request{
|
||||
.method = "GET",
|
||||
.path = path,
|
||||
};
|
||||
|
||||
client->set_follow_location(true);
|
||||
httplib::Result result = client->send(request);
|
||||
|
||||
if (!result) {
|
||||
LOG_ERROR(Frontend, "GET to {}{} returned null", url, path);
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto &response = result.value();
|
||||
if (response.status >= 400) {
|
||||
LOG_ERROR(Frontend,
|
||||
"GET to {}{} returned error status code: {}",
|
||||
url,
|
||||
path,
|
||||
response.status);
|
||||
return {};
|
||||
}
|
||||
if (!response.headers.contains("content-type")) {
|
||||
LOG_ERROR(Frontend, "GET to {}{} returned no content", url, path);
|
||||
return {};
|
||||
}
|
||||
|
||||
return response.body;
|
||||
} catch (std::exception &e) {
|
||||
LOG_ERROR(Frontend,
|
||||
"GET to {}{} failed during update check: {}",
|
||||
url,
|
||||
path,
|
||||
e.what());
|
||||
boost::split(result, latest->tag, boost::is_any_of("."));
|
||||
if (result.size() != 2)
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease() {
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
const auto update_check_url = fmt::format("https://{}", Common::g_build_auto_update_api);
|
||||
const std::string tag = result[1];
|
||||
|
||||
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
|
||||
if (result.empty())
|
||||
return std::nullopt;
|
||||
|
||||
const std::string build = result[0];
|
||||
#else
|
||||
const auto update_check_url = std::string{Common::g_build_auto_update_api};
|
||||
const std::string tag = latest->tag;
|
||||
const std::string build = Common::g_build_version;
|
||||
#endif
|
||||
|
||||
auto update_check_path = std::string{Common::g_build_auto_update_api_path};
|
||||
try {
|
||||
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
|
||||
if (tag != build)
|
||||
return latest;
|
||||
|
||||
if (!response)
|
||||
return {};
|
||||
|
||||
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
|
||||
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
|
||||
|
||||
return Update{latest_tag, latest_name};
|
||||
} catch (nlohmann::detail::out_of_range&) {
|
||||
LOG_ERROR(Frontend,
|
||||
"Parsing JSON response from {}{} failed during update check: "
|
||||
"nlohmann::detail::out_of_range",
|
||||
update_check_url,
|
||||
update_check_path);
|
||||
return {};
|
||||
} catch (nlohmann::detail::type_error&) {
|
||||
LOG_ERROR(Frontend,
|
||||
"Parsing JSON response from {}{} failed during update check: "
|
||||
"nlohmann::detail::type_error",
|
||||
update_check_url,
|
||||
update_check_path);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<UpdateChecker::Update> UpdateChecker::GetUpdate() {
|
||||
const std::optional<UpdateChecker::Update> latest_release_tag =
|
||||
UpdateChecker::GetLatestRelease();
|
||||
|
||||
if (!latest_release_tag)
|
||||
goto empty;
|
||||
|
||||
{
|
||||
std::string tag, build;
|
||||
if (Common::g_is_nightly_build) {
|
||||
std::vector<std::string> result;
|
||||
|
||||
boost::split(result, latest_release_tag->tag, boost::is_any_of("."));
|
||||
if (result.size() != 2)
|
||||
goto empty;
|
||||
tag = result[1];
|
||||
|
||||
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
|
||||
if (result.empty())
|
||||
goto empty;
|
||||
build = result[0];
|
||||
} else {
|
||||
tag = latest_release_tag->tag;
|
||||
build = Common::g_build_version;
|
||||
}
|
||||
|
||||
if (tag != build)
|
||||
return latest_release_tag.value();
|
||||
}
|
||||
|
||||
empty:
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -8,16 +8,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "common/net/net.h"
|
||||
|
||||
namespace UpdateChecker {
|
||||
|
||||
typedef struct {
|
||||
std::string tag;
|
||||
std::string name;
|
||||
} Update;
|
||||
std::optional<Common::Net::Release> GetUpdate();
|
||||
|
||||
std::optional<std::string> GetResponse(std::string url, std::string path);
|
||||
std::optional<Update> GetLatestRelease();
|
||||
std::optional<Update> GetUpdate();
|
||||
} // namespace UpdateChecker
|
||||
|
||||
@@ -157,11 +157,14 @@ void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t c
|
||||
if (!controller.device->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
auto* shared_memory = controller.shared_memory;
|
||||
const auto& battery_level = controller.device->GetBattery();
|
||||
shared_memory->battery_level_dual = battery_level.dual.battery_level;
|
||||
shared_memory->battery_level_left = battery_level.left.battery_level;
|
||||
shared_memory->battery_level_right = battery_level.right.battery_level;
|
||||
if (auto* shared_memory = controller.shared_memory; shared_memory) {
|
||||
const auto& battery_level = controller.device->GetBattery();
|
||||
shared_memory->battery_level_dual = battery_level.dual.battery_level;
|
||||
shared_memory->battery_level_left = battery_level.left.battery_level;
|
||||
shared_memory->battery_level_right = battery_level.right.battery_level;
|
||||
} else {
|
||||
LOG_WARNING(Service_HID, "shared_memory is null {}", controller_idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -180,6 +183,10 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) {
|
||||
const auto& body_colors = controller.device->GetColors();
|
||||
const auto& battery_level = controller.device->GetBattery();
|
||||
auto* shared_memory = controller.shared_memory;
|
||||
if (!shared_memory) {
|
||||
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id);
|
||||
return;
|
||||
}
|
||||
if (controller_type == Core::HID::NpadStyleIndex::None) {
|
||||
npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id);
|
||||
return;
|
||||
@@ -801,7 +808,7 @@ Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) {
|
||||
|
||||
auto* shared_memory = controller.shared_memory;
|
||||
if (!shared_memory) {
|
||||
LOG_WARNING(Service_HID, "DisconnectNpad: shared_memory is null for npad_id={}", npad_id);
|
||||
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id);
|
||||
return ResultSuccess;
|
||||
}
|
||||
// Don't reset shared_memory->assignment_mode this value is persistent
|
||||
@@ -1195,11 +1202,10 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
|
||||
|
||||
AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) {
|
||||
const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid();
|
||||
const auto& shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory;
|
||||
|
||||
const auto* shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory;
|
||||
return {
|
||||
.ui_variant = 0,
|
||||
.footer = shared_memory->applet_footer_type,
|
||||
.footer = shared_memory ? shared_memory->applet_footer_type : AppletFooterUiType::None,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ add_subdirectory(host_shaders)
|
||||
add_subdirectory(gpu_logging)
|
||||
|
||||
if(LIBVA_FOUND)
|
||||
set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
|
||||
set_source_files_properties(host1x/ffmpeg.cpp
|
||||
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
|
||||
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
|
||||
endif()
|
||||
@@ -44,7 +44,6 @@ add_library(video_core STATIC
|
||||
engines/sw_blitter/converter.h
|
||||
engines/const_buffer_info.h
|
||||
engines/draw_manager.cpp
|
||||
engines/draw_manager.h
|
||||
engines/engine_interface.h
|
||||
engines/engine_upload.cpp
|
||||
engines/engine_upload.h
|
||||
@@ -72,9 +71,9 @@ add_library(video_core STATIC
|
||||
host1x/codecs/vp8.h
|
||||
host1x/codecs/vp9.cpp
|
||||
host1x/codecs/vp9.h
|
||||
host1x/codecs/vp9_types.h
|
||||
host1x/ffmpeg/ffmpeg.cpp
|
||||
host1x/ffmpeg/ffmpeg.h
|
||||
host1x/codec_types.h
|
||||
host1x/ffmpeg.cpp
|
||||
host1x/ffmpeg.h
|
||||
host1x/control.cpp
|
||||
host1x/control.h
|
||||
host1x/gpu_device_memory_manager.cpp
|
||||
|
||||
@@ -356,7 +356,7 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
|
||||
if (is_indexed) {
|
||||
BindHostIndexBuffer();
|
||||
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
|
||||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
|
||||
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
|
||||
@@ -740,30 +740,25 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
||||
TouchBuffer(buffer, channel_state->index_buffer.buffer_id);
|
||||
const u32 offset = buffer.Offset(channel_state->index_buffer.device_addr);
|
||||
const u32 size = channel_state->index_buffer.size;
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
|
||||
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||
if (draw_state.inline_index_draw_indexes.empty()) {
|
||||
SynchronizeBuffer(buffer, channel_state->index_buffer.device_addr, size);
|
||||
} else {
|
||||
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
|
||||
auto upload_staging = runtime.UploadStagingBuffer(size);
|
||||
std::array<BufferCopy, 1> copies{
|
||||
{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
|
||||
std::memcpy(upload_staging.mapped_span.data(),
|
||||
draw_state.inline_index_draw_indexes.data(), size);
|
||||
std::array<BufferCopy, 1> copies{{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
|
||||
std::memcpy(upload_staging.mapped_span.data(), draw_state.inline_index_draw_indexes.data(), size);
|
||||
runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true);
|
||||
} else {
|
||||
buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes);
|
||||
}
|
||||
} else {
|
||||
SynchronizeBuffer(buffer, channel_state->index_buffer.device_addr, size);
|
||||
}
|
||||
if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||
const u32 new_offset =
|
||||
offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
|
||||
const u32 new_offset = offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
|
||||
runtime.BindIndexBuffer(buffer, new_offset, size);
|
||||
} else {
|
||||
buffer.MarkUsage(offset, size);
|
||||
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
|
||||
draw_state.index_buffer.first, draw_state.index_buffer.count,
|
||||
buffer, offset, size);
|
||||
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format, draw_state.index_buffer.first, draw_state.index_buffer.count, buffer, offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,10 +940,9 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
||||
return alignment > 1 && (offset % alignment) != 0;
|
||||
}
|
||||
}();
|
||||
const bool use_fast_buffer = needs_alignment_stream ||
|
||||
(has_host_buffer &&
|
||||
size <= channel_state->uniform_buffer_skip_cache_size &&
|
||||
!memory_tracker.IsRegionGpuModified(device_addr, size));
|
||||
const bool use_fast_buffer = needs_alignment_stream
|
||||
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size
|
||||
&& !memory_tracker.IsRegionGpuModified(device_addr, size));
|
||||
if (use_fast_buffer) {
|
||||
if constexpr (IS_OPENGL) {
|
||||
if (runtime.HasFastBufferSubData()) {
|
||||
@@ -1229,7 +1223,7 @@ template <class P>
|
||||
void BufferCache<P>::UpdateIndexBuffer() {
|
||||
// We have to check for the dirty flags and index count
|
||||
// The index count is currently changed without updating the dirty flags
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||
const auto& index_buffer_ref = draw_state.index_buffer;
|
||||
auto& flags = maxwell3d->dirty.flags;
|
||||
if (!flags[Dirty::IndexBuffer]) {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "video_core/control/channel_state_cache.h"
|
||||
#include "video_core/delayed_destruction_ring.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
@@ -305,7 +305,7 @@ public:
|
||||
[[nodiscard]] bool IsRegionCpuModified(DAddr addr, size_t size);
|
||||
|
||||
void SetDrawIndirect(
|
||||
const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect_) {
|
||||
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect_) {
|
||||
current_draw_indirect = current_draw_indirect_;
|
||||
}
|
||||
|
||||
@@ -480,7 +480,7 @@ private:
|
||||
#endif
|
||||
DelayedDestructionRing<Buffer, TICKS_TO_DESTROY> delayed_destruction_ring;
|
||||
|
||||
const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect{};
|
||||
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
|
||||
|
||||
u32 last_index_count = 0;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Ryujinx Team and Contributors
|
||||
@@ -20,109 +20,101 @@
|
||||
namespace Tegra {
|
||||
|
||||
CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id)
|
||||
: host1x{host1x_}, memory_manager{host1x.GMMU()},
|
||||
host_processor{std::make_unique<Host1x::Control>(host1x_)}, current_class{
|
||||
static_cast<ChClassId>(id)} {
|
||||
thread = std::jthread([this](std::stop_token stop_token) { ProcessEntries(stop_token); });
|
||||
: host_processor(host1x_)
|
||||
, host1x{host1x_}
|
||||
, current_class{ChClassId(id)}
|
||||
{
|
||||
thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
ChCommandHeaderList command_list{host1x.System().ApplicationMemory(), 0, 0};
|
||||
u32 count{};
|
||||
u32 method_offset{};
|
||||
u32 mask{};
|
||||
bool incrementing{};
|
||||
while (!stop_token.stop_requested()) {
|
||||
{
|
||||
std::unique_lock l{command_mutex};
|
||||
command_cv.wait(l, stop_token, [this]() { return command_lists.size() > 0; });
|
||||
if (stop_token.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
command_list = std::move(command_lists.front());
|
||||
command_lists.pop_front();
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto value : command_list) {
|
||||
i++;
|
||||
if (mask != 0) {
|
||||
const auto lbs = static_cast<u32>(std::countr_zero(mask));
|
||||
mask &= ~(1U << lbs);
|
||||
ExecuteCommand(method_offset + lbs, value.raw);
|
||||
continue;
|
||||
} else if (count != 0) {
|
||||
--count;
|
||||
ExecuteCommand(method_offset, value.raw);
|
||||
if (incrementing) {
|
||||
++method_offset;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto mode = value.submission_mode.Value();
|
||||
switch (mode) {
|
||||
case ChSubmissionMode::SetClass: {
|
||||
mask = value.value & 0x3f;
|
||||
method_offset = value.method_offset;
|
||||
current_class = ChClassId((value.value >> 6) & 0x3ff);
|
||||
break;
|
||||
}
|
||||
case ChSubmissionMode::Incrementing:
|
||||
case ChSubmissionMode::NonIncrementing:
|
||||
count = value.value;
|
||||
method_offset = value.method_offset;
|
||||
incrementing = mode == ChSubmissionMode::Incrementing;
|
||||
break;
|
||||
case ChSubmissionMode::Mask:
|
||||
mask = value.value;
|
||||
method_offset = value.method_offset;
|
||||
break;
|
||||
case ChSubmissionMode::Immediate: {
|
||||
const u32 data = value.value & 0xfff;
|
||||
method_offset = value.method_offset;
|
||||
ExecuteCommand(method_offset, data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1, (i - 1) * sizeof(u32), command_list.size());
|
||||
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", u32(mode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CDmaPusher::~CDmaPusher() = default;
|
||||
|
||||
void CDmaPusher::ProcessEntries(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
ChCommandHeaderList command_list{host1x.System().ApplicationMemory(), 0, 0};
|
||||
u32 count{};
|
||||
u32 method_offset{};
|
||||
u32 mask{};
|
||||
bool incrementing{};
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
{
|
||||
std::unique_lock l{command_mutex};
|
||||
command_cv.wait(l, stop_token,
|
||||
[this]() { return command_lists.size() > 0; });
|
||||
if (stop_token.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
command_list = std::move(command_lists.front());
|
||||
command_lists.pop_front();
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto value : command_list) {
|
||||
i++;
|
||||
if (mask != 0) {
|
||||
const auto lbs = static_cast<u32>(std::countr_zero(mask));
|
||||
mask &= ~(1U << lbs);
|
||||
ExecuteCommand(method_offset + lbs, value.raw);
|
||||
continue;
|
||||
} else if (count != 0) {
|
||||
--count;
|
||||
ExecuteCommand(method_offset, value.raw);
|
||||
if (incrementing) {
|
||||
++method_offset;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto mode = value.submission_mode.Value();
|
||||
switch (mode) {
|
||||
case ChSubmissionMode::SetClass: {
|
||||
mask = value.value & 0x3f;
|
||||
method_offset = value.method_offset;
|
||||
current_class = static_cast<ChClassId>((value.value >> 6) & 0x3ff);
|
||||
break;
|
||||
}
|
||||
case ChSubmissionMode::Incrementing:
|
||||
case ChSubmissionMode::NonIncrementing:
|
||||
count = value.value;
|
||||
method_offset = value.method_offset;
|
||||
incrementing = mode == ChSubmissionMode::Incrementing;
|
||||
break;
|
||||
case ChSubmissionMode::Mask:
|
||||
mask = value.value;
|
||||
method_offset = value.method_offset;
|
||||
break;
|
||||
case ChSubmissionMode::Immediate: {
|
||||
const u32 data = value.value & 0xfff;
|
||||
method_offset = value.method_offset;
|
||||
ExecuteCommand(method_offset, data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1,
|
||||
(i - 1) * sizeof(u32), command_list.size());
|
||||
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!",
|
||||
static_cast<u32>(mode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CDmaPusher::ExecuteCommand(u32 method, u32 arg) {
|
||||
switch (current_class) {
|
||||
case ChClassId::Control:
|
||||
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}",
|
||||
static_cast<u32>(current_class), method, arg);
|
||||
host_processor->ProcessMethod(static_cast<Host1x::Control::Method>(method), arg);
|
||||
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), method, arg);
|
||||
host_processor.ProcessMethod(Host1x::Control::Method(method), arg);
|
||||
break;
|
||||
default:
|
||||
thi_regs.reg_array[method] = arg;
|
||||
switch (static_cast<ThiMethod>(method)) {
|
||||
case ThiMethod::IncSyncpt: {
|
||||
const auto syncpoint_id = static_cast<u32>(arg & 0xFF);
|
||||
[[maybe_unused]] const auto cond = static_cast<u32>((arg >> 8) & 0xFF);
|
||||
LOG_TRACE(Service_NVDRV, "Class {} IncSyncpt Method, syncpt {} cond {}",
|
||||
static_cast<u32>(current_class), syncpoint_id, cond);
|
||||
const auto syncpoint_id = u32(arg & 0xFF);
|
||||
[[maybe_unused]] const auto cond = u32((arg >> 8) & 0xFF);
|
||||
LOG_TRACE(Service_NVDRV, "Class {} IncSyncpt Method, syncpt {} cond {}", u32(current_class), syncpoint_id, cond);
|
||||
auto& syncpoint_manager = host1x.GetSyncpointManager();
|
||||
syncpoint_manager.IncrementGuest(syncpoint_id);
|
||||
syncpoint_manager.IncrementHost(syncpoint_id);
|
||||
break;
|
||||
}
|
||||
case ThiMethod::SetMethod1:
|
||||
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}",
|
||||
static_cast<u32>(current_class), static_cast<u32>(thi_regs.method_0), arg);
|
||||
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), u32(thi_regs.method_0), arg);
|
||||
ProcessMethod(thi_regs.method_0, arg);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -15,11 +18,11 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/host1x/control.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
namespace Host1x {
|
||||
class Control;
|
||||
class Host1x;
|
||||
class Nvdec;
|
||||
class SyncptIncrManager;
|
||||
@@ -117,25 +120,19 @@ protected:
|
||||
|
||||
virtual void ProcessMethod(u32 method, u32 arg) = 0;
|
||||
|
||||
Host1x::Host1x& host1x;
|
||||
Tegra::MemoryManager& memory_manager;
|
||||
|
||||
private:
|
||||
/// Process the command entry
|
||||
void ProcessEntries(std::stop_token stop_token);
|
||||
|
||||
/// Invoke command class devices to execute the command based on the current state
|
||||
void ExecuteCommand(u32 state_offset, u32 data);
|
||||
|
||||
std::unique_ptr<Host1x::Control> host_processor;
|
||||
|
||||
std::mutex command_mutex;
|
||||
std::condition_variable_any command_cv;
|
||||
std::deque<ChCommandHeaderList> command_lists;
|
||||
std::jthread thread;
|
||||
|
||||
protected:
|
||||
ThiRegisters thi_regs{};
|
||||
std::deque<ChCommandHeaderList> command_lists;
|
||||
std::condition_variable_any command_cv;
|
||||
Host1x::Control host_processor;
|
||||
std::mutex command_mutex;
|
||||
Host1x::Host1x& host1x;
|
||||
ChClassId current_class;
|
||||
std::jthread thread;
|
||||
};
|
||||
|
||||
} // namespace Tegra
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
DrawManager::DrawManager(Maxwell3D* maxwell3d_) : maxwell3d(maxwell3d_) {}
|
||||
|
||||
void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
void Maxwell3D::DrawManager::ProcessMethodCall(Maxwell3D& maxwell3d, u32 method, u32 argument) {
|
||||
switch (method) {
|
||||
case MAXWELL3D_REG_INDEX(clear_surface):
|
||||
return Clear(1);
|
||||
return Clear(maxwell3d, 1);
|
||||
case MAXWELL3D_REG_INDEX(draw.begin):
|
||||
return DrawBegin();
|
||||
return DrawBegin(maxwell3d);
|
||||
case MAXWELL3D_REG_INDEX(draw.end):
|
||||
return DrawEnd();
|
||||
return DrawEnd(maxwell3d);
|
||||
case MAXWELL3D_REG_INDEX(vertex_buffer.first):
|
||||
case MAXWELL3D_REG_INDEX(vertex_buffer.count):
|
||||
case MAXWELL3D_REG_INDEX(index_buffer.first):
|
||||
@@ -33,33 +34,29 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
|
||||
case MAXWELL3D_REG_INDEX(index_buffer32_first):
|
||||
case MAXWELL3D_REG_INDEX(index_buffer16_first):
|
||||
case MAXWELL3D_REG_INDEX(index_buffer8_first):
|
||||
return DrawIndexSmall(argument);
|
||||
return DrawIndexSmall(maxwell3d, argument);
|
||||
case MAXWELL3D_REG_INDEX(draw_inline_index):
|
||||
SetInlineIndexBuffer(argument);
|
||||
SetInlineIndexBuffer(maxwell3d, argument);
|
||||
break;
|
||||
case MAXWELL3D_REG_INDEX(inline_index_2x16.even):
|
||||
SetInlineIndexBuffer(regs.inline_index_2x16.even);
|
||||
SetInlineIndexBuffer(regs.inline_index_2x16.odd);
|
||||
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_2x16.even);
|
||||
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_2x16.odd);
|
||||
break;
|
||||
case MAXWELL3D_REG_INDEX(inline_index_4x8.index0):
|
||||
SetInlineIndexBuffer(regs.inline_index_4x8.index0);
|
||||
SetInlineIndexBuffer(regs.inline_index_4x8.index1);
|
||||
SetInlineIndexBuffer(regs.inline_index_4x8.index2);
|
||||
SetInlineIndexBuffer(regs.inline_index_4x8.index3);
|
||||
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index0);
|
||||
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index1);
|
||||
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index2);
|
||||
SetInlineIndexBuffer(maxwell3d, maxwell3d.regs.inline_index_4x8.index3);
|
||||
break;
|
||||
case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
|
||||
DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
|
||||
regs.vertex_array_instance_first.start.Value(),
|
||||
regs.vertex_array_instance_first.count.Value(), false);
|
||||
DrawArrayInstanced(maxwell3d, maxwell3d.regs.vertex_array_instance_first.topology.Value(), maxwell3d.regs.vertex_array_instance_first.start.Value(), maxwell3d.regs.vertex_array_instance_first.count.Value(), false);
|
||||
break;
|
||||
case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
|
||||
DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
|
||||
regs.vertex_array_instance_subsequent.start.Value(),
|
||||
regs.vertex_array_instance_subsequent.count.Value(), true);
|
||||
DrawArrayInstanced(maxwell3d, maxwell3d.regs.vertex_array_instance_subsequent.topology.Value(), maxwell3d.regs.vertex_array_instance_subsequent.start.Value(), maxwell3d.regs.vertex_array_instance_subsequent.count.Value(), true);
|
||||
break;
|
||||
}
|
||||
case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
|
||||
DrawTexture();
|
||||
DrawTexture(maxwell3d);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -67,101 +64,87 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
|
||||
}
|
||||
}
|
||||
|
||||
void DrawManager::Clear(u32 layer_count) {
|
||||
if (maxwell3d->ShouldExecute()) {
|
||||
maxwell3d->rasterizer->Clear(layer_count);
|
||||
void Maxwell3D::DrawManager::Clear(Maxwell3D& maxwell3d, u32 layer_count) {
|
||||
if (maxwell3d.ShouldExecute()) {
|
||||
maxwell3d.rasterizer->Clear(layer_count);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawManager::DrawDeferred() {
|
||||
void Maxwell3D::DrawManager::DrawDeferred(Maxwell3D& maxwell3d) {
|
||||
if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0) {
|
||||
return;
|
||||
}
|
||||
DrawEnd(draw_state.instance_count + 1, true);
|
||||
DrawEnd(maxwell3d, draw_state.instance_count + 1, true);
|
||||
draw_state.instance_count = 0;
|
||||
}
|
||||
|
||||
void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
|
||||
u32 base_instance, u32 num_instances) {
|
||||
void Maxwell3D::DrawManager::DrawArray(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, u32 base_instance, u32 num_instances) {
|
||||
draw_state.topology = topology;
|
||||
draw_state.vertex_buffer.first = vertex_first;
|
||||
draw_state.vertex_buffer.count = vertex_count;
|
||||
draw_state.base_instance = base_instance;
|
||||
ProcessDraw(false, num_instances);
|
||||
ProcessDraw(maxwell3d, false, num_instances);
|
||||
}
|
||||
|
||||
void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
|
||||
bool subsequent) {
|
||||
void Maxwell3D::DrawManager::DrawArrayInstanced(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, bool subsequent) {
|
||||
draw_state.topology = topology;
|
||||
draw_state.vertex_buffer.first = vertex_first;
|
||||
draw_state.vertex_buffer.count = vertex_count;
|
||||
|
||||
if (!subsequent) {
|
||||
draw_state.instance_count = 1;
|
||||
}
|
||||
|
||||
draw_state.base_instance = draw_state.instance_count - 1;
|
||||
draw_state.draw_mode = DrawMode::Instance;
|
||||
draw_state.instance_count++;
|
||||
ProcessDraw(false, 1);
|
||||
ProcessDraw(maxwell3d, false, 1);
|
||||
}
|
||||
|
||||
void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
|
||||
u32 base_index, u32 base_instance, u32 num_instances) {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
void Maxwell3D::DrawManager::DrawIndex(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, u32 base_instance, u32 num_instances) {
|
||||
draw_state.topology = topology;
|
||||
draw_state.index_buffer = regs.index_buffer;
|
||||
draw_state.index_buffer = maxwell3d.regs.index_buffer;
|
||||
draw_state.index_buffer.first = index_first;
|
||||
draw_state.index_buffer.count = index_count;
|
||||
draw_state.base_index = base_index;
|
||||
draw_state.base_instance = base_instance;
|
||||
ProcessDraw(true, num_instances);
|
||||
ProcessDraw(maxwell3d, true, num_instances);
|
||||
}
|
||||
|
||||
void DrawManager::DrawArrayIndirect(PrimitiveTopology topology) {
|
||||
void Maxwell3D::DrawManager::DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology) {
|
||||
draw_state.topology = topology;
|
||||
|
||||
ProcessDrawIndirect();
|
||||
ProcessDrawIndirect(maxwell3d);
|
||||
}
|
||||
|
||||
void DrawManager::DrawIndexedIndirect(PrimitiveTopology topology, u32 index_first,
|
||||
u32 index_count) {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
void Maxwell3D::DrawManager::DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count) {
|
||||
draw_state.topology = topology;
|
||||
draw_state.index_buffer = regs.index_buffer;
|
||||
draw_state.index_buffer = maxwell3d.regs.index_buffer;
|
||||
draw_state.index_buffer.first = index_first;
|
||||
draw_state.index_buffer.count = index_count;
|
||||
|
||||
ProcessDrawIndirect();
|
||||
ProcessDrawIndirect(maxwell3d);
|
||||
}
|
||||
|
||||
void DrawManager::SetInlineIndexBuffer(u32 index) {
|
||||
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>(index & 0x000000ff));
|
||||
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x0000ff00) >> 8));
|
||||
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x00ff0000) >> 16));
|
||||
draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0xff000000) >> 24));
|
||||
void Maxwell3D::DrawManager::SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index) {
|
||||
draw_state.inline_index_draw_indexes.push_back(u8(index & 0x000000ff));
|
||||
draw_state.inline_index_draw_indexes.push_back(u8((index & 0x0000ff00) >> 8));
|
||||
draw_state.inline_index_draw_indexes.push_back(u8((index & 0x00ff0000) >> 16));
|
||||
draw_state.inline_index_draw_indexes.push_back(u8((index & 0xff000000) >> 24));
|
||||
draw_state.draw_mode = DrawMode::InlineIndex;
|
||||
}
|
||||
|
||||
void DrawManager::DrawBegin() {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
auto reset_instance_count = regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First;
|
||||
auto increment_instance_count =
|
||||
regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent;
|
||||
void Maxwell3D::DrawManager::DrawBegin(Maxwell3D& maxwell3d) {
|
||||
auto reset_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First;
|
||||
auto increment_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent;
|
||||
if (reset_instance_count) {
|
||||
DrawDeferred();
|
||||
DrawDeferred(maxwell3d);
|
||||
draw_state.instance_count = 0;
|
||||
draw_state.draw_mode = DrawMode::General;
|
||||
} else if (increment_instance_count) {
|
||||
draw_state.instance_count++;
|
||||
draw_state.draw_mode = DrawMode::Instance;
|
||||
}
|
||||
|
||||
draw_state.topology = regs.draw.topology;
|
||||
draw_state.topology = maxwell3d.regs.draw.topology;
|
||||
}
|
||||
|
||||
void DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
void Maxwell3D::DrawManager::DrawEnd(Maxwell3D& maxwell3d, u32 instance_count, bool force_draw) {
|
||||
switch (draw_state.draw_mode) {
|
||||
case DrawMode::Instance:
|
||||
if (!force_draw) {
|
||||
@@ -169,119 +152,100 @@ void DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
|
||||
}
|
||||
[[fallthrough]];
|
||||
case DrawMode::General:
|
||||
draw_state.base_instance = regs.global_base_instance_index;
|
||||
draw_state.base_index = regs.global_base_vertex_index;
|
||||
draw_state.base_instance = maxwell3d.regs.global_base_instance_index;
|
||||
draw_state.base_index = maxwell3d.regs.global_base_vertex_index;
|
||||
if (draw_state.draw_indexed) {
|
||||
draw_state.index_buffer = regs.index_buffer;
|
||||
ProcessDraw(true, instance_count);
|
||||
draw_state.index_buffer = maxwell3d.regs.index_buffer;
|
||||
ProcessDraw(maxwell3d, true, instance_count);
|
||||
} else {
|
||||
draw_state.vertex_buffer = regs.vertex_buffer;
|
||||
ProcessDraw(false, instance_count);
|
||||
draw_state.vertex_buffer = maxwell3d.regs.vertex_buffer;
|
||||
ProcessDraw(maxwell3d, false, instance_count);
|
||||
}
|
||||
draw_state.draw_indexed = false;
|
||||
break;
|
||||
case DrawMode::InlineIndex:
|
||||
draw_state.base_instance = regs.global_base_instance_index;
|
||||
draw_state.base_index = regs.global_base_vertex_index;
|
||||
draw_state.index_buffer = regs.index_buffer;
|
||||
draw_state.index_buffer.count =
|
||||
static_cast<u32>(draw_state.inline_index_draw_indexes.size() / 4);
|
||||
draw_state.base_instance = maxwell3d.regs.global_base_instance_index;
|
||||
draw_state.base_index = maxwell3d.regs.global_base_vertex_index;
|
||||
draw_state.index_buffer = maxwell3d.regs.index_buffer;
|
||||
draw_state.index_buffer.count = u32(draw_state.inline_index_draw_indexes.size() / 4);
|
||||
draw_state.index_buffer.format = Maxwell3D::Regs::IndexFormat::UnsignedInt;
|
||||
maxwell3d->dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
ProcessDraw(true, instance_count);
|
||||
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
ProcessDraw(maxwell3d, true, instance_count);
|
||||
draw_state.inline_index_draw_indexes.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawManager::DrawIndexSmall(u32 argument) {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
IndexBufferSmall index_small_params{argument};
|
||||
draw_state.base_instance = regs.global_base_instance_index;
|
||||
draw_state.base_index = regs.global_base_vertex_index;
|
||||
draw_state.index_buffer = regs.index_buffer;
|
||||
void Maxwell3D::DrawManager::DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument) {
|
||||
Maxwell3D::Regs::IndexBufferSmall index_small_params{argument};
|
||||
draw_state.base_instance = maxwell3d.regs.global_base_instance_index;
|
||||
draw_state.base_index = maxwell3d.regs.global_base_vertex_index;
|
||||
draw_state.index_buffer = maxwell3d.regs.index_buffer;
|
||||
draw_state.index_buffer.first = index_small_params.first;
|
||||
draw_state.index_buffer.count = index_small_params.count;
|
||||
draw_state.topology = index_small_params.topology;
|
||||
maxwell3d->dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
ProcessDraw(true, 1);
|
||||
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
ProcessDraw(maxwell3d, true, 1);
|
||||
}
|
||||
|
||||
void DrawManager::DrawTexture() {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
draw_texture_state.dst_x0 = static_cast<float>(regs.draw_texture.dst_x0) / 4096.f;
|
||||
draw_texture_state.dst_y0 = static_cast<float>(regs.draw_texture.dst_y0) / 4096.f;
|
||||
const auto dst_width = static_cast<float>(regs.draw_texture.dst_width) / 4096.f;
|
||||
const auto dst_height = static_cast<float>(regs.draw_texture.dst_height) / 4096.f;
|
||||
const bool lower_left{regs.window_origin.mode !=
|
||||
Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft};
|
||||
void Maxwell3D::DrawManager::DrawTexture(Maxwell3D& maxwell3d) {
|
||||
draw_texture_state.dst_x0 = f32(maxwell3d.regs.draw_texture.dst_x0) / 4096.f;
|
||||
draw_texture_state.dst_y0 = f32(maxwell3d.regs.draw_texture.dst_y0) / 4096.f;
|
||||
const auto dst_width = f32(maxwell3d.regs.draw_texture.dst_width) / 4096.f;
|
||||
const auto dst_height = f32(maxwell3d.regs.draw_texture.dst_height) / 4096.f;
|
||||
const bool lower_left{maxwell3d.regs.window_origin.mode != Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft};
|
||||
if (lower_left) {
|
||||
draw_texture_state.dst_y0 =
|
||||
static_cast<f32>(regs.surface_clip.height) - draw_texture_state.dst_y0;
|
||||
draw_texture_state.dst_y0 = f32(maxwell3d.regs.surface_clip.height) - draw_texture_state.dst_y0;
|
||||
}
|
||||
draw_texture_state.dst_x1 = draw_texture_state.dst_x0 + dst_width;
|
||||
draw_texture_state.dst_y1 = draw_texture_state.dst_y0 + dst_height;
|
||||
draw_texture_state.src_x0 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f;
|
||||
draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f;
|
||||
draw_texture_state.src_x1 =
|
||||
(static_cast<float>(regs.draw_texture.dx_du) / 4294967296.f) * dst_width +
|
||||
draw_texture_state.src_x0;
|
||||
draw_texture_state.src_y1 =
|
||||
(static_cast<float>(regs.draw_texture.dy_dv) / 4294967296.f) * dst_height +
|
||||
draw_texture_state.src_y0;
|
||||
draw_texture_state.src_sampler = regs.draw_texture.src_sampler;
|
||||
draw_texture_state.src_texture = regs.draw_texture.src_texture;
|
||||
maxwell3d->rasterizer->DrawTexture();
|
||||
draw_texture_state.src_x0 = f32(maxwell3d.regs.draw_texture.src_x0) / 4096.f;
|
||||
draw_texture_state.src_y0 = f32(maxwell3d.regs.draw_texture.src_y0) / 4096.f;
|
||||
draw_texture_state.src_x1 = (f32(maxwell3d.regs.draw_texture.dx_du) / 4294967296.f) * dst_width + draw_texture_state.src_x0;
|
||||
draw_texture_state.src_y1 = (f32(maxwell3d.regs.draw_texture.dy_dv) / 4294967296.f) * dst_height + draw_texture_state.src_y0;
|
||||
draw_texture_state.src_sampler = maxwell3d.regs.draw_texture.src_sampler;
|
||||
draw_texture_state.src_texture = maxwell3d.regs.draw_texture.src_texture;
|
||||
maxwell3d.rasterizer->DrawTexture();
|
||||
}
|
||||
|
||||
void DrawManager::UpdateTopology() {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
switch (regs.primitive_topology_control) {
|
||||
case PrimitiveTopologyControl::UseInBeginMethods:
|
||||
void Maxwell3D::DrawManager::UpdateTopology(Maxwell3D& maxwell3d) {
|
||||
switch (maxwell3d.regs.primitive_topology_control) {
|
||||
case Maxwell3D::Regs::PrimitiveTopologyControl::UseInBeginMethods:
|
||||
break;
|
||||
case PrimitiveTopologyControl::UseSeparateState:
|
||||
switch (regs.topology_override) {
|
||||
case PrimitiveTopologyOverride::None:
|
||||
case Maxwell3D::Regs::PrimitiveTopologyControl::UseSeparateState:
|
||||
switch (maxwell3d.regs.topology_override) {
|
||||
case Maxwell3D::Regs::PrimitiveTopologyOverride::None:
|
||||
break;
|
||||
case PrimitiveTopologyOverride::Points:
|
||||
draw_state.topology = PrimitiveTopology::Points;
|
||||
case Maxwell3D::Regs::PrimitiveTopologyOverride::Points:
|
||||
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Points;
|
||||
break;
|
||||
case PrimitiveTopologyOverride::Lines:
|
||||
draw_state.topology = PrimitiveTopology::Lines;
|
||||
case Maxwell3D::Regs::PrimitiveTopologyOverride::Lines:
|
||||
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Lines;
|
||||
break;
|
||||
case PrimitiveTopologyOverride::LineStrip:
|
||||
draw_state.topology = PrimitiveTopology::LineStrip;
|
||||
case Maxwell3D::Regs::PrimitiveTopologyOverride::LineStrip:
|
||||
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::LineStrip;
|
||||
break;
|
||||
default:
|
||||
draw_state.topology = static_cast<PrimitiveTopology>(regs.topology_override);
|
||||
draw_state.topology = Maxwell3D::Regs::PrimitiveTopology(maxwell3d.regs.topology_override);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawManager::ProcessDraw(bool draw_indexed, u32 instance_count) {
|
||||
LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology,
|
||||
draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
|
||||
|
||||
UpdateTopology();
|
||||
|
||||
if (maxwell3d->ShouldExecute()) {
|
||||
maxwell3d->rasterizer->Draw(draw_indexed, instance_count);
|
||||
void Maxwell3D::DrawManager::ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed, u32 instance_count) {
|
||||
LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology, draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
|
||||
UpdateTopology(maxwell3d);
|
||||
if (maxwell3d.ShouldExecute()) {
|
||||
maxwell3d.rasterizer->Draw(draw_indexed, instance_count);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawManager::ProcessDrawIndirect() {
|
||||
LOG_TRACE(
|
||||
HW_GPU,
|
||||
"called, topology={}, is_indexed={}, includes_count={}, buffer_size={}, max_draw_count={}",
|
||||
draw_state.topology, indirect_state.is_indexed, indirect_state.include_count,
|
||||
indirect_state.buffer_size, indirect_state.max_draw_counts);
|
||||
|
||||
UpdateTopology();
|
||||
|
||||
if (maxwell3d->ShouldExecute()) {
|
||||
maxwell3d->rasterizer->DrawIndirect();
|
||||
void Maxwell3D::DrawManager::ProcessDrawIndirect(Maxwell3D& maxwell3d) {
|
||||
LOG_TRACE(HW_GPU, "called, topology={}, is_indexed={}, includes_count={}, buffer_size={}, max_draw_count={}", draw_state.topology, indirect_state.is_indexed, indirect_state.include_count, indirect_state.buffer_size, indirect_state.max_draw_counts);
|
||||
UpdateTopology(maxwell3d);
|
||||
if (maxwell3d.ShouldExecute()) {
|
||||
maxwell3d.rasterizer->DrawIndirect();
|
||||
}
|
||||
}
|
||||
} // namespace Tegra::Engines
|
||||
|
||||
@@ -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_timing.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
@@ -26,7 +26,8 @@ namespace Tegra::Engines {
|
||||
constexpr u32 MacroRegistersStart = 0xE00;
|
||||
|
||||
Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
|
||||
: draw_manager{std::make_unique<DrawManager>(this)}, system{system_}
|
||||
: draw_manager()
|
||||
, system{system_}
|
||||
, memory_manager{memory_manager_}
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
, macro_engine(bool(Settings::values.disable_macro_jit))
|
||||
@@ -373,8 +374,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
||||
case MAXWELL3D_REG_INDEX(sync_info):
|
||||
return ProcessSyncPoint();
|
||||
case MAXWELL3D_REG_INDEX(launch_dma):
|
||||
return upload_state.ProcessExec(regs.launch_dma.memory_layout.Value() ==
|
||||
Regs::LaunchDMA::Layout::Pitch);
|
||||
return upload_state.ProcessExec(regs.launch_dma.memory_layout.Value() == Regs::LaunchDMA::Layout::Pitch);
|
||||
case MAXWELL3D_REG_INDEX(inline_data):
|
||||
upload_state.ProcessData(argument, is_last_call);
|
||||
return;
|
||||
@@ -386,7 +386,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
||||
case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
|
||||
return rasterizer->TiledCacheBarrier();
|
||||
default:
|
||||
draw_manager->ProcessMethodCall(method, argument);
|
||||
draw_manager.ProcessMethodCall(*this, method, argument);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -401,8 +401,7 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
|
||||
|
||||
// Execute the current macro.
|
||||
macro_engine.Execute(*this, macro_positions[entry], parameters);
|
||||
|
||||
draw_manager->DrawDeferred();
|
||||
draw_manager.DrawDeferred(*this);
|
||||
}
|
||||
|
||||
void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/macro.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -40,8 +41,6 @@ class RasterizerInterface;
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
class DrawManager;
|
||||
|
||||
/**
|
||||
* This Engine is known as GF100_3D. Documentation can be found in:
|
||||
* https://github.com/NVIDIA/open-gpu-doc/blob/master/classes/3d/clb197.h
|
||||
@@ -543,7 +542,7 @@ public:
|
||||
}
|
||||
GPUVAddr StorageLimitAddress() const {
|
||||
return (GPUVAddr{storage_limit_address_high} << 32) |
|
||||
GPUVAddr{storage_limit_address_low};
|
||||
GPUVAddr{storage_limit_address_low};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -819,7 +818,7 @@ public:
|
||||
|
||||
u32 Map(std::size_t index) const {
|
||||
const std::array<u32, NumRenderTargets> maps{target0, target1, target2, target3,
|
||||
target4, target5, target6, target7};
|
||||
target4, target5, target6, target7};
|
||||
ASSERT(index < maps.size());
|
||||
return maps[index];
|
||||
}
|
||||
@@ -1831,7 +1830,7 @@ public:
|
||||
|
||||
bool AnyEnabled() const {
|
||||
return output0_enable || output1_enable || output2_enable || output3_enable ||
|
||||
output4_enable || output5_enable || output6_enable || output7_enable;
|
||||
output4_enable || output5_enable || output6_enable || output7_enable;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1870,7 +1869,7 @@ public:
|
||||
|
||||
bool AnyEnabled() const {
|
||||
return plane0 || plane1 || plane2 || plane3 || plane4 || plane5 || plane6 ||
|
||||
plane7;
|
||||
plane7;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3023,8 +3022,7 @@ public:
|
||||
u32 bindless_texture_const_buffer_slot; ///< 0x2608
|
||||
u32 trap_handler; ///< 0x260C
|
||||
INSERT_PADDING_BYTES_NOINIT(0x1F0);
|
||||
std::array<std::array<StreamOutLayout, 32>, NumTransformFeedbackBuffers>
|
||||
stream_out_layout; ///< 0x2800
|
||||
std::array<std::array<StreamOutLayout, 32>, NumTransformFeedbackBuffers> stream_out_layout; ///< 0x2800
|
||||
INSERT_PADDING_BYTES_NOINIT(0x93C);
|
||||
ShaderPerformance shader_performance; ///< 0x333C
|
||||
INSERT_PADDING_BYTES_NOINIT(0x18);
|
||||
@@ -3035,6 +3033,62 @@ public:
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
struct DrawManager {
|
||||
enum class DrawMode : u32 { General = 0, Instance, InlineIndex };
|
||||
struct State {
|
||||
Maxwell3D::Regs::PrimitiveTopology topology{};
|
||||
DrawMode draw_mode{};
|
||||
bool draw_indexed{};
|
||||
u32 base_index{};
|
||||
Maxwell3D::Regs::VertexBuffer vertex_buffer;
|
||||
Maxwell3D::Regs::IndexBuffer index_buffer;
|
||||
u32 base_instance{};
|
||||
u32 instance_count{};
|
||||
std::vector<u8> inline_index_draw_indexes;
|
||||
};
|
||||
struct DrawTextureState {
|
||||
f32 dst_x0;
|
||||
f32 dst_y0;
|
||||
f32 dst_x1;
|
||||
f32 dst_y1;
|
||||
f32 src_x0;
|
||||
f32 src_y0;
|
||||
f32 src_x1;
|
||||
f32 src_y1;
|
||||
u32 src_sampler;
|
||||
u32 src_texture;
|
||||
};
|
||||
struct IndirectParams {
|
||||
bool is_byte_count;
|
||||
bool is_indexed;
|
||||
bool include_count;
|
||||
GPUVAddr count_start_address;
|
||||
GPUVAddr indirect_start_address;
|
||||
size_t buffer_size;
|
||||
size_t max_draw_counts;
|
||||
size_t stride;
|
||||
};
|
||||
void ProcessMethodCall(Maxwell3D& maxwell3d, u32 method, u32 argument);
|
||||
void Clear(Maxwell3D& maxwell3d, u32 layer_count);
|
||||
void DrawDeferred(Maxwell3D& maxwell3d);
|
||||
void DrawArray(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, u32 base_instance, u32 num_instances);
|
||||
void DrawArrayInstanced(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, bool subsequent);
|
||||
void DrawIndex(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, u32 base_instance, u32 num_instances);
|
||||
void DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology);
|
||||
void DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count);
|
||||
void SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index);
|
||||
void DrawBegin(Maxwell3D& maxwell3d);
|
||||
void DrawEnd(Maxwell3D& maxwell3d, u32 instance_count = 1, bool force_draw = false);
|
||||
void DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument);
|
||||
void DrawTexture(Maxwell3D& maxwell3d);
|
||||
void UpdateTopology(Maxwell3D& maxwell3d);
|
||||
void ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed, u32 instance_count);
|
||||
void ProcessDrawIndirect(Maxwell3D& maxwell3d);
|
||||
State draw_state{};
|
||||
DrawTextureState draw_texture_state{};
|
||||
IndirectParams indirect_state{};
|
||||
};
|
||||
|
||||
Regs regs{};
|
||||
|
||||
/// Store temporary hw register values, used by some calls to restore state after a operation
|
||||
@@ -3102,8 +3156,7 @@ public:
|
||||
Tables tables{};
|
||||
} dirty;
|
||||
|
||||
std::unique_ptr<DrawManager> draw_manager;
|
||||
friend class DrawManager;
|
||||
DrawManager draw_manager;
|
||||
|
||||
GPUVAddr GetMacroAddress(size_t index) const {
|
||||
return macro_addresses[index];
|
||||
|
||||
@@ -89,7 +89,7 @@ struct GPU::Impl {
|
||||
renderer = std::move(renderer_);
|
||||
rasterizer = renderer->ReadRasterizer();
|
||||
host1x.MemoryManager().BindInterface(rasterizer);
|
||||
host1x.GMMU().BindRasterizer(rasterizer);
|
||||
host1x.gmmu_manager.BindRasterizer(rasterizer);
|
||||
}
|
||||
|
||||
/// Flush all current written commands into the host GPU for execution.
|
||||
|
||||
@@ -19,56 +19,45 @@
|
||||
|
||||
namespace VideoCommon::GPUThread {
|
||||
|
||||
/// Runs the GPU thread
|
||||
static void RunThread(std::stop_token stop_token, Core::System& system,
|
||||
VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
|
||||
Tegra::Control::Scheduler& scheduler, SynchState& state) {
|
||||
Common::SetCurrentThreadName("GPU");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
system.RegisterHostThread();
|
||||
|
||||
auto current_context = context.Acquire();
|
||||
VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
|
||||
|
||||
CommandDataContainer next;
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
state.queue.PopWait(next, stop_token);
|
||||
if (stop_token.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
||||
scheduler.Push(submit_list->channel, std::move(submit_list->entries));
|
||||
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
|
||||
system.GPU().TickWork();
|
||||
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
|
||||
rasterizer->FlushRegion(flush->addr, flush->size);
|
||||
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
|
||||
rasterizer->OnCacheInvalidation(invalidate->addr, invalidate->size);
|
||||
} else {
|
||||
ASSERT(false);
|
||||
}
|
||||
state.signaled_fence.store(next.fence);
|
||||
if (next.block) {
|
||||
// We have to lock the write_lock to ensure that the condition_variable wait not get a
|
||||
// race between the check and the lock itself.
|
||||
std::scoped_lock lk{state.write_lock};
|
||||
state.cv.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThreadManager::ThreadManager(Core::System& system_, bool is_async_)
|
||||
: system{system_}, is_async{is_async_} {}
|
||||
|
||||
ThreadManager::~ThreadManager() = default;
|
||||
|
||||
void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
|
||||
Core::Frontend::GraphicsContext& context,
|
||||
Tegra::Control::Scheduler& scheduler) {
|
||||
void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, Tegra::Control::Scheduler& scheduler) {
|
||||
rasterizer = renderer.ReadRasterizer();
|
||||
thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
|
||||
std::ref(scheduler), std::ref(state));
|
||||
thread = std::jthread([&](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("GPU");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
system.RegisterHostThread();
|
||||
|
||||
auto current_context = context.Acquire();
|
||||
CommandDataContainer next;
|
||||
while (!stop_token.stop_requested()) {
|
||||
state.queue.PopWait(next, stop_token);
|
||||
if (stop_token.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
||||
scheduler.Push(submit_list->channel, std::move(submit_list->entries));
|
||||
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
|
||||
system.GPU().TickWork();
|
||||
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
|
||||
renderer.ReadRasterizer()->FlushRegion(flush->addr, flush->size);
|
||||
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
|
||||
renderer.ReadRasterizer()->OnCacheInvalidation(invalidate->addr, invalidate->size);
|
||||
} else {
|
||||
ASSERT(false);
|
||||
}
|
||||
state.signaled_fence.store(next.fence);
|
||||
if (next.block) {
|
||||
// We have to lock the write_lock to ensure that the condition_variable wait not get a
|
||||
// race between the check and the lock itself.
|
||||
std::scoped_lock lk{state.write_lock};
|
||||
state.cv.notify_all();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -6,12 +9,192 @@
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
namespace Decoders {
|
||||
|
||||
struct Offset {
|
||||
constexpr u32 Address() const noexcept {
|
||||
return offset << 8;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 offset;
|
||||
};
|
||||
static_assert(std::is_trivial_v<Offset>, "Offset must be trivial");
|
||||
static_assert(sizeof(Offset) == 0x4, "Offset has the wrong size!");
|
||||
|
||||
struct H264ParameterSet {
|
||||
s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
|
||||
s32 delta_pic_order_always_zero_flag; ///< 0x04
|
||||
s32 frame_mbs_only_flag; ///< 0x08
|
||||
u32 pic_width_in_mbs; ///< 0x0C
|
||||
u32 frame_height_in_mbs; ///< 0x10
|
||||
union { ///< 0x14
|
||||
BitField<0, 2, u32> tile_format;
|
||||
BitField<2, 3, u32> gob_height;
|
||||
BitField<5, 27, u32> reserved_surface_format;
|
||||
};
|
||||
u32 entropy_coding_mode_flag; ///< 0x18
|
||||
s32 pic_order_present_flag; ///< 0x1C
|
||||
s32 num_refidx_l0_default_active; ///< 0x20
|
||||
s32 num_refidx_l1_default_active; ///< 0x24
|
||||
s32 deblocking_filter_control_present_flag; ///< 0x28
|
||||
s32 redundant_pic_cnt_present_flag; ///< 0x2C
|
||||
u32 transform_8x8_mode_flag; ///< 0x30
|
||||
u32 pitch_luma; ///< 0x34
|
||||
u32 pitch_chroma; ///< 0x38
|
||||
Offset luma_top_offset; ///< 0x3C
|
||||
Offset luma_bot_offset; ///< 0x40
|
||||
Offset luma_frame_offset; ///< 0x44
|
||||
Offset chroma_top_offset; ///< 0x48
|
||||
Offset chroma_bot_offset; ///< 0x4C
|
||||
Offset chroma_frame_offset; ///< 0x50
|
||||
u32 hist_buffer_size; ///< 0x54
|
||||
union { ///< 0x58
|
||||
union {
|
||||
BitField<0, 1, u64> mbaff_frame;
|
||||
BitField<1, 1, u64> direct_8x8_inference;
|
||||
BitField<2, 1, u64> weighted_pred;
|
||||
BitField<3, 1, u64> constrained_intra_pred;
|
||||
BitField<4, 1, u64> ref_pic;
|
||||
BitField<5, 1, u64> field_pic;
|
||||
BitField<6, 1, u64> bottom_field;
|
||||
BitField<7, 1, u64> second_field;
|
||||
} flags;
|
||||
BitField<8, 4, u64> log2_max_frame_num_minus4;
|
||||
BitField<12, 2, u64> chroma_format_idc;
|
||||
BitField<14, 2, u64> pic_order_cnt_type;
|
||||
BitField<16, 6, s64> pic_init_qp_minus26;
|
||||
BitField<22, 5, s64> chroma_qp_index_offset;
|
||||
BitField<27, 5, s64> second_chroma_qp_index_offset;
|
||||
BitField<32, 2, u64> weighted_bipred_idc;
|
||||
BitField<34, 7, u64> curr_pic_idx;
|
||||
BitField<41, 5, u64> curr_col_idx;
|
||||
BitField<46, 16, u64> frame_number;
|
||||
BitField<62, 1, u64> frame_surfaces;
|
||||
BitField<63, 1, u64> output_memory_layout;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(H264ParameterSet) == 0x60, "H264ParameterSet is an invalid size");
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) \
|
||||
static_assert(offsetof(H264ParameterSet, field_name) == position, \
|
||||
"Field " #field_name " has invalid position")
|
||||
|
||||
ASSERT_POSITION(log2_max_pic_order_cnt_lsb_minus4, 0x00);
|
||||
ASSERT_POSITION(delta_pic_order_always_zero_flag, 0x04);
|
||||
ASSERT_POSITION(frame_mbs_only_flag, 0x08);
|
||||
ASSERT_POSITION(pic_width_in_mbs, 0x0C);
|
||||
ASSERT_POSITION(frame_height_in_mbs, 0x10);
|
||||
ASSERT_POSITION(tile_format, 0x14);
|
||||
ASSERT_POSITION(entropy_coding_mode_flag, 0x18);
|
||||
ASSERT_POSITION(pic_order_present_flag, 0x1C);
|
||||
ASSERT_POSITION(num_refidx_l0_default_active, 0x20);
|
||||
ASSERT_POSITION(num_refidx_l1_default_active, 0x24);
|
||||
ASSERT_POSITION(deblocking_filter_control_present_flag, 0x28);
|
||||
ASSERT_POSITION(redundant_pic_cnt_present_flag, 0x2C);
|
||||
ASSERT_POSITION(transform_8x8_mode_flag, 0x30);
|
||||
ASSERT_POSITION(pitch_luma, 0x34);
|
||||
ASSERT_POSITION(pitch_chroma, 0x38);
|
||||
ASSERT_POSITION(luma_top_offset, 0x3C);
|
||||
ASSERT_POSITION(luma_bot_offset, 0x40);
|
||||
ASSERT_POSITION(luma_frame_offset, 0x44);
|
||||
ASSERT_POSITION(chroma_top_offset, 0x48);
|
||||
ASSERT_POSITION(chroma_bot_offset, 0x4C);
|
||||
ASSERT_POSITION(chroma_frame_offset, 0x50);
|
||||
ASSERT_POSITION(hist_buffer_size, 0x54);
|
||||
ASSERT_POSITION(flags, 0x58);
|
||||
#undef ASSERT_POSITION
|
||||
|
||||
struct DpbEntry {
|
||||
union {
|
||||
BitField<0, 7, u32> index;
|
||||
BitField<7, 5, u32> col_idx;
|
||||
BitField<12, 2, u32> state;
|
||||
BitField<14, 1, u32> is_long_term;
|
||||
BitField<15, 1, u32> non_existing;
|
||||
BitField<16, 1, u32> is_field;
|
||||
BitField<17, 4, u32> top_field_marking;
|
||||
BitField<21, 4, u32> bottom_field_marking;
|
||||
BitField<25, 1, u32> output_memory_layout;
|
||||
BitField<26, 6, u32> reserved;
|
||||
} flags;
|
||||
std::array<u32, 2> field_order_cnt;
|
||||
u32 frame_idx;
|
||||
};
|
||||
static_assert(sizeof(DpbEntry) == 0x10, "DpbEntry has the wrong size!");
|
||||
|
||||
struct DisplayParam {
|
||||
union {
|
||||
BitField<0, 1, u32> enable_tf_output;
|
||||
BitField<1, 1, u32> vc1_map_y_flag;
|
||||
BitField<2, 3, u32> map_y_value;
|
||||
BitField<5, 1, u32> vc1_map_uv_flag;
|
||||
BitField<6, 3, u32> map_uv_value;
|
||||
BitField<9, 8, u32> out_stride;
|
||||
BitField<17, 3, u32> tiling_format;
|
||||
BitField<20, 1, u32> output_structure; // 0=frame, 1=field
|
||||
BitField<21, 11, u32> reserved0;
|
||||
};
|
||||
std::array<s32, 2> output_top;
|
||||
std::array<s32, 2> output_bottom;
|
||||
union {
|
||||
BitField<0, 1, u32> enable_histogram;
|
||||
BitField<1, 12, u32> histogram_start_x;
|
||||
BitField<13, 12, u32> histogram_start_y;
|
||||
BitField<25, 7, u32> reserved1;
|
||||
};
|
||||
union {
|
||||
BitField<0, 12, u32> histogram_end_x;
|
||||
BitField<12, 12, u32> histogram_end_y;
|
||||
BitField<24, 8, u32> reserved2;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(DisplayParam) == 0x1C, "DisplayParam has the wrong size!");
|
||||
|
||||
struct H264DecoderContext {
|
||||
INSERT_PADDING_WORDS_NOINIT(13); ///< 0x0000
|
||||
std::array<u8, 16> eos; ///< 0x0034
|
||||
u8 explicit_eos_present_flag; ///< 0x0044
|
||||
u8 hint_dump_en; ///< 0x0045
|
||||
INSERT_PADDING_BYTES_NOINIT(2); ///< 0x0046
|
||||
u32 stream_len; ///< 0x0048
|
||||
u32 slice_count; ///< 0x004C
|
||||
u32 mbhist_buffer_size; ///< 0x0050
|
||||
u32 gptimer_timeout_value; ///< 0x0054
|
||||
H264ParameterSet h264_parameter_set; ///< 0x0058
|
||||
std::array<s32, 2> curr_field_order_cnt; ///< 0x00B8
|
||||
std::array<DpbEntry, 16> dpb; ///< 0x00C0
|
||||
std::array<u8, 0x60> weight_scale_4x4; ///< 0x01C0
|
||||
std::array<u8, 0x80> weight_scale_8x8; ///< 0x0220
|
||||
std::array<u8, 2> num_inter_view_refs_lX; ///< 0x02A0
|
||||
std::array<u8, 14> reserved2; ///< 0x02A2
|
||||
std::array<std::array<s8, 16>, 2> inter_view_refidx_lX; ///< 0x02B0
|
||||
union { ///< 0x02D0
|
||||
BitField<0, 1, u32> lossless_ipred8x8_filter_enable;
|
||||
BitField<1, 1, u32> qpprime_y_zero_transform_bypass_flag;
|
||||
BitField<2, 30, u32> reserved3;
|
||||
};
|
||||
DisplayParam display_param; ///< 0x02D4
|
||||
std::array<u32, 3> reserved4; ///< 0x02F0
|
||||
};
|
||||
static_assert(sizeof(H264DecoderContext) == 0x2FC, "H264DecoderContext is an invalid size");
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) \
|
||||
static_assert(offsetof(H264DecoderContext, field_name) == position, \
|
||||
"Field " #field_name " has invalid position")
|
||||
|
||||
ASSERT_POSITION(stream_len, 0x48);
|
||||
ASSERT_POSITION(h264_parameter_set, 0x58);
|
||||
ASSERT_POSITION(dpb, 0xC0);
|
||||
ASSERT_POSITION(weight_scale_4x4, 0x1C0);
|
||||
#undef ASSERT_POSITION
|
||||
|
||||
enum class Vp9SurfaceIndex : u32 {
|
||||
Last = 0,
|
||||
Golden = 1,
|
||||
@@ -287,10 +470,7 @@ struct RefPoolElement {
|
||||
bool refresh{};
|
||||
};
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) \
|
||||
static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \
|
||||
"Field " #field_name " has invalid position")
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(Vp9EntropyProbs, field_name) == position)
|
||||
ASSERT_POSITION(partition_prob, 0x0024);
|
||||
ASSERT_POSITION(switchable_interp_prob, 0x0724);
|
||||
ASSERT_POSITION(sign, 0x0772);
|
||||
@@ -298,10 +478,7 @@ ASSERT_POSITION(class_0_fr, 0x079E);
|
||||
ASSERT_POSITION(high_precision, 0x07B2);
|
||||
#undef ASSERT_POSITION
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) \
|
||||
static_assert(offsetof(PictureInfo, field_name) == position, \
|
||||
"Field " #field_name " has invalid position")
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(PictureInfo, field_name) == position)
|
||||
ASSERT_POSITION(bitstream_size, 0x30);
|
||||
ASSERT_POSITION(last_frame_size, 0x48);
|
||||
ASSERT_POSITION(first_level, 0x70);
|
||||
@@ -309,9 +486,7 @@ ASSERT_POSITION(segmentation, 0x80);
|
||||
ASSERT_POSITION(loop_filter, 0xE4);
|
||||
#undef ASSERT_POSITION
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) \
|
||||
static_assert(offsetof(EntropyProbs, field_name) == position, \
|
||||
"Field " #field_name " has invalid position")
|
||||
#define ASSERT_POSITION(field_name, position) static_assert(offsetof(EntropyProbs, field_name) == position)
|
||||
|
||||
ASSERT_POSITION(inter_mode_prob, 0x400);
|
||||
ASSERT_POSITION(tx_8x8_prob, 0x470);
|
||||
@@ -321,5 +496,47 @@ ASSERT_POSITION(class_0_fr, 0x560);
|
||||
ASSERT_POSITION(coef_probs, 0x5A0);
|
||||
#undef ASSERT_POSITION
|
||||
|
||||
struct VP8PictureInfo {
|
||||
INSERT_PADDING_WORDS_NOINIT(14);
|
||||
u16 frame_width; // actual frame width
|
||||
u16 frame_height; // actual frame height
|
||||
u8 key_frame;
|
||||
u8 version;
|
||||
union {
|
||||
u8 raw;
|
||||
BitField<0, 2, u8> tile_format;
|
||||
BitField<2, 3, u8> gob_height;
|
||||
BitField<5, 3, u8> reserved_surface_format;
|
||||
};
|
||||
u8 error_conceal_on; // 1: error conceal on; 0: off
|
||||
u32 first_part_size; // the size of first partition(frame header and mb header partition)
|
||||
u32 hist_buffer_size; // in units of 256
|
||||
u32 vld_buffer_size; // in units of 1
|
||||
// Current frame buffers
|
||||
std::array<u32, 2> frame_stride; // [y_c]
|
||||
u32 luma_top_offset; // offset of luma top field in units of 256
|
||||
u32 luma_bot_offset; // offset of luma bottom field in units of 256
|
||||
u32 luma_frame_offset; // offset of luma frame in units of 256
|
||||
u32 chroma_top_offset; // offset of chroma top field in units of 256
|
||||
u32 chroma_bot_offset; // offset of chroma bottom field in units of 256
|
||||
u32 chroma_frame_offset; // offset of chroma frame in units of 256
|
||||
|
||||
INSERT_PADDING_BYTES_NOINIT(0x1c); // NvdecDisplayParams
|
||||
|
||||
// Decode picture buffer related
|
||||
s8 current_output_memory_layout;
|
||||
// output NV12/NV24 setting. index 0: golden; 1: altref; 2: last
|
||||
std::array<s8, 3> output_memory_layout;
|
||||
|
||||
u8 segmentation_feature_data_update;
|
||||
INSERT_PADDING_BYTES_NOINIT(3);
|
||||
|
||||
// ucode return result
|
||||
u32 result_value;
|
||||
std::array<u32, 8> partition_offset;
|
||||
INSERT_PADDING_WORDS_NOINIT(3);
|
||||
};
|
||||
static_assert(sizeof(VP8PictureInfo) == 0xc0, "PictureInfo is an invalid size");
|
||||
|
||||
}; // namespace Decoders
|
||||
}; // namespace Tegra
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -13,9 +16,12 @@
|
||||
namespace Tegra {
|
||||
|
||||
Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs)
|
||||
: host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
|
||||
vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
|
||||
vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
|
||||
: host1x(host1x_)
|
||||
, state{regs}
|
||||
, h264_decoder(host1x_)
|
||||
, vp8_decoder(host1x_)
|
||||
, vp9_decoder(host1x_)
|
||||
{}
|
||||
|
||||
Codec::~Codec() = default;
|
||||
|
||||
@@ -32,13 +38,11 @@ void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) {
|
||||
|
||||
void Codec::Decode() {
|
||||
const bool is_first_frame = !initialized;
|
||||
if (is_first_frame) {
|
||||
if (is_first_frame)
|
||||
Initialize();
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
if (!initialized)
|
||||
return;
|
||||
}
|
||||
|
||||
// Assemble bitstream.
|
||||
bool vp9_hidden_frame = false;
|
||||
@@ -46,13 +50,13 @@ void Codec::Decode() {
|
||||
const auto packet_data = [&]() {
|
||||
switch (current_codec) {
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
|
||||
return h264_decoder->ComposeFrame(state, &configuration_size, is_first_frame);
|
||||
return h264_decoder.ComposeFrame(state, &configuration_size, is_first_frame);
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
|
||||
return vp8_decoder->ComposeFrame(state);
|
||||
return vp8_decoder.ComposeFrame(state);
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
|
||||
vp9_decoder->ComposeFrame(state);
|
||||
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
|
||||
return vp9_decoder->GetFrameBytes();
|
||||
vp9_decoder.ComposeFrame(state);
|
||||
vp9_hidden_frame = vp9_decoder.WasFrameHidden();
|
||||
return vp9_decoder.GetFrameBytes();
|
||||
default:
|
||||
ASSERT(false);
|
||||
return std::span<const u8>{};
|
||||
@@ -81,19 +85,13 @@ void Codec::Decode() {
|
||||
std::unique_ptr<FFmpeg::Frame> Codec::GetCurrentFrame() {
|
||||
// Sometimes VIC will request more frames than have been decoded.
|
||||
// in this case, return a blank frame and don't overwrite previous data.
|
||||
if (frames.empty()) {
|
||||
if (frames.empty())
|
||||
return {};
|
||||
}
|
||||
|
||||
auto frame = std::move(frames.front());
|
||||
frames.pop();
|
||||
return frame;
|
||||
}
|
||||
|
||||
Host1x::NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
|
||||
return current_codec;
|
||||
}
|
||||
|
||||
std::string_view Codec::GetCurrentCodecName() const {
|
||||
switch (current_codec) {
|
||||
case Host1x::NvdecCommon::VideoCodec::None:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -8,17 +11,14 @@
|
||||
#include <string_view>
|
||||
#include <queue>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/host1x/ffmpeg/ffmpeg.h"
|
||||
#include "video_core/host1x/codecs/h264.h"
|
||||
#include "video_core/host1x/codecs/vp8.h"
|
||||
#include "video_core/host1x/codecs/vp9.h"
|
||||
#include "video_core/host1x/ffmpeg.h"
|
||||
#include "video_core/host1x/nvdec_common.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
namespace Decoder {
|
||||
class H264;
|
||||
class VP8;
|
||||
class VP9;
|
||||
} // namespace Decoder
|
||||
|
||||
namespace Host1x {
|
||||
class Host1x;
|
||||
} // namespace Host1x
|
||||
@@ -40,9 +40,6 @@ public:
|
||||
/// Returns next decoded frame
|
||||
[[nodiscard]] std::unique_ptr<FFmpeg::Frame> GetCurrentFrame();
|
||||
|
||||
/// Returns the value of current_codec
|
||||
[[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const;
|
||||
|
||||
/// Return name of the current codec
|
||||
[[nodiscard]] std::string_view GetCurrentCodecName() const;
|
||||
|
||||
@@ -53,10 +50,9 @@ private:
|
||||
|
||||
Host1x::Host1x& host1x;
|
||||
const Host1x::NvdecCommon::NvdecRegisters& state;
|
||||
std::unique_ptr<Decoder::H264> h264_decoder;
|
||||
std::unique_ptr<Decoder::VP8> vp8_decoder;
|
||||
std::unique_ptr<Decoder::VP9> vp9_decoder;
|
||||
|
||||
Decoders::H264 h264_decoder;
|
||||
Decoders::VP8 vp8_decoder;
|
||||
Decoders::VP9 vp9_decoder;
|
||||
std::queue<std::unique_ptr<FFmpeg::Frame>> frames{};
|
||||
};
|
||||
|
||||
|
||||
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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -12,10 +12,11 @@
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::NvdecRegisters& regs_,
|
||||
Host1x::FrameQueue& frame_queue_)
|
||||
: host1x(host1x_), memory_manager{host1x.GMMU()}, regs{regs_}, id{id_}, frame_queue{
|
||||
frame_queue_} {}
|
||||
Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::NvdecRegisters& regs_)
|
||||
: host1x(host1x_)
|
||||
, regs{regs_}
|
||||
, id{id_}
|
||||
{}
|
||||
|
||||
Decoder::~Decoder() = default;
|
||||
|
||||
@@ -53,11 +54,11 @@ void Decoder::Decode() {
|
||||
}
|
||||
|
||||
if (UsingDecodeOrder()) {
|
||||
frame_queue.PushDecodeOrder(id, luma_top, std::move(frame));
|
||||
frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy));
|
||||
host1x.frame_queue.PushDecodeOrder(id, luma_top, std::move(frame));
|
||||
host1x.frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy));
|
||||
} else {
|
||||
frame_queue.PushPresentOrder(id, luma_top, std::move(frame));
|
||||
frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy));
|
||||
host1x.frame_queue.PushPresentOrder(id, luma_top, std::move(frame));
|
||||
host1x.frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy));
|
||||
}
|
||||
} else {
|
||||
auto [luma_offset, chroma_offset] = GetProgressiveOffsets();
|
||||
@@ -68,9 +69,9 @@ void Decoder::Decode() {
|
||||
}
|
||||
|
||||
if (UsingDecodeOrder()) {
|
||||
frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame));
|
||||
host1x.frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame));
|
||||
} else {
|
||||
frame_queue.PushPresentOrder(id, luma_offset, std::move(frame));
|
||||
host1x.frame_queue.PushPresentOrder(id, luma_offset, std::move(frame));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <queue>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/host1x/ffmpeg/ffmpeg.h"
|
||||
#include "video_core/host1x/ffmpeg.h"
|
||||
#include "video_core/host1x/nvdec_common.h"
|
||||
|
||||
namespace Tegra {
|
||||
@@ -35,33 +35,23 @@ public:
|
||||
return decode_api.UsingDecodeOrder();
|
||||
}
|
||||
|
||||
/// Returns the value of current_codec
|
||||
[[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const {
|
||||
return codec;
|
||||
}
|
||||
|
||||
/// Return name of the current codec
|
||||
[[nodiscard]] virtual std::string_view GetCurrentCodecName() const = 0;
|
||||
|
||||
protected:
|
||||
explicit Decoder(Host1x::Host1x& host1x, s32 id,
|
||||
const Host1x::NvdecCommon::NvdecRegisters& regs,
|
||||
Host1x::FrameQueue& frame_queue);
|
||||
explicit Decoder(Host1x::Host1x& host1x, s32 id, const Host1x::NvdecCommon::NvdecRegisters& regs);
|
||||
|
||||
virtual std::span<const u8> ComposeFrame() = 0;
|
||||
virtual std::tuple<u64, u64> GetProgressiveOffsets() = 0;
|
||||
virtual std::tuple<u64, u64, u64, u64> GetInterlacedOffsets() = 0;
|
||||
virtual bool IsInterlaced() = 0;
|
||||
|
||||
FFmpeg::DecodeApi decode_api;
|
||||
Host1x::Host1x& host1x;
|
||||
Tegra::MemoryManager& memory_manager;
|
||||
const Host1x::NvdecCommon::NvdecRegisters& regs;
|
||||
s32 id;
|
||||
Host1x::FrameQueue& frame_queue;
|
||||
Host1x::NvdecCommon::VideoCodec codec;
|
||||
FFmpeg::DecodeApi decode_api;
|
||||
bool initialized{};
|
||||
bool vp9_hidden_frame{};
|
||||
bool initialized : 1 = false;
|
||||
bool vp9_hidden_frame : 1 = false;
|
||||
};
|
||||
|
||||
} // namespace Tegra
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -14,25 +14,11 @@
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra::Decoders {
|
||||
namespace {
|
||||
// ZigZag LUTs from libavcodec.
|
||||
constexpr std::array<u8, 64> zig_zag_direct{
|
||||
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
|
||||
41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
|
||||
30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
|
||||
};
|
||||
|
||||
constexpr std::array<u8, 16> zig_zag_scan{
|
||||
0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
|
||||
1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4,
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
H264::H264(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_,
|
||||
Host1x::FrameQueue& frame_queue_)
|
||||
: Decoder{host1x_, id_, regs_, frame_queue_} {
|
||||
codec = Host1x::NvdecCommon::VideoCodec::H264;
|
||||
initialized = decode_api.Initialize(codec);
|
||||
H264::H264(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_)
|
||||
: Decoder{host1x_, id_, regs_}
|
||||
{
|
||||
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::H264);
|
||||
}
|
||||
|
||||
H264::~H264() = default;
|
||||
@@ -65,14 +51,11 @@ bool H264::IsInterlaced() {
|
||||
}
|
||||
|
||||
std::span<const u8> H264::ComposeFrame() {
|
||||
memory_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context,
|
||||
sizeof(H264DecoderContext));
|
||||
|
||||
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(H264DecoderContext));
|
||||
const s64 frame_number = current_context.h264_parameter_set.frame_number.Value();
|
||||
if (!is_first_frame && frame_number != 0) {
|
||||
frame_scratch.resize_destructive(current_context.stream_len);
|
||||
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data(),
|
||||
frame_scratch.size());
|
||||
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data(), frame_scratch.size());
|
||||
return frame_scratch;
|
||||
}
|
||||
|
||||
@@ -174,15 +157,13 @@ std::span<const u8> H264::ComposeFrame() {
|
||||
|
||||
for (s32 index = 0; index < 6; index++) {
|
||||
writer.WriteBit(true);
|
||||
std::span<const u8> matrix{current_context.weight_scale_4x4};
|
||||
writer.WriteScalingList(scan_scratch, matrix, index * 16, 16);
|
||||
writer.WriteScalingList(current_context.weight_scale_4x4, index * 16, 16);
|
||||
}
|
||||
|
||||
if (current_context.h264_parameter_set.transform_8x8_mode_flag) {
|
||||
for (s32 index = 0; index < 2; index++) {
|
||||
writer.WriteBit(true);
|
||||
std::span<const u8> matrix{current_context.weight_scale_8x8};
|
||||
writer.WriteScalingList(scan_scratch, matrix, index * 64, 64);
|
||||
writer.WriteScalingList(current_context.weight_scale_8x8, index * 64, 64);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,11 +177,7 @@ std::span<const u8> H264::ComposeFrame() {
|
||||
const auto& encoded_header = writer.GetByteArray();
|
||||
frame_scratch.resize(encoded_header.size() + current_context.stream_len);
|
||||
std::memcpy(frame_scratch.data(), encoded_header.data(), encoded_header.size());
|
||||
|
||||
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(),
|
||||
frame_scratch.data() + encoded_header.size(),
|
||||
current_context.stream_len);
|
||||
|
||||
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), frame_scratch.data() + encoded_header.size(), current_context.stream_len);
|
||||
return frame_scratch;
|
||||
}
|
||||
|
||||
@@ -229,23 +206,37 @@ void H264BitWriter::WriteBit(bool state) {
|
||||
WriteBits(state ? 1 : 0, 1);
|
||||
}
|
||||
|
||||
void H264BitWriter::WriteScalingList(Common::ScratchBuffer<u8>& scan, std::span<const u8> list,
|
||||
s32 start, s32 count) {
|
||||
scan.resize_destructive(count);
|
||||
void H264BitWriter::WriteScalingList(std::span<const u8> list, s32 start, s32 count) {
|
||||
if (count == 16) {
|
||||
std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());
|
||||
u8 last_scale = 8;
|
||||
for (s32 index = 0; index < count; index++) {
|
||||
// libavcodec has a zig zag LUT, but we dont need it, just use a magic
|
||||
// constant which is a packing of 4 bits for each component of the table
|
||||
const u8 value = list[start + ((0xfeb7adc963258410 >> (index * 4)) & 0xf)];
|
||||
const s32 delta_scale = s32(value - last_scale);
|
||||
WriteSe(delta_scale);
|
||||
last_scale = value;
|
||||
}
|
||||
} else {
|
||||
std::memcpy(scan.data(), zig_zag_direct.data(), scan.size());
|
||||
}
|
||||
u8 last_scale = 8;
|
||||
|
||||
for (s32 index = 0; index < count; index++) {
|
||||
const u8 value = list[start + scan[index]];
|
||||
const s32 delta_scale = static_cast<s32>(value - last_scale);
|
||||
|
||||
WriteSe(delta_scale);
|
||||
|
||||
last_scale = value;
|
||||
// ZigZag LUTs from libavcodec: this is the famous zigzag pattern found in the ffmpeg logo itself!
|
||||
static constexpr std::array<u8, 64> scan{
|
||||
0, 1, 8, 16, 9, 2, 3, 10,
|
||||
17, 24, 32, 25, 18, 11, 4,
|
||||
5, 12, 19, 26, 33, 40, 48,
|
||||
41, 34, 27, 20, 13, 6, 7,
|
||||
14, 21, 28, 35, 42, 49, 56,
|
||||
57, 50, 43, 36, 29, 22, 15,
|
||||
23, 30, 37, 44, 51, 58, 59,
|
||||
52, 45, 38, 31, 39, 46, 53,
|
||||
60, 61, 54, 47, 55, 62, 63,
|
||||
};
|
||||
u8 last_scale = 8;
|
||||
for (s32 index = 0; index < count; index++) {
|
||||
const u8 value = list[start + scan[index]];
|
||||
const s32 delta_scale = s32(value - last_scale);
|
||||
WriteSe(delta_scale);
|
||||
last_scale = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,19 +277,15 @@ void H264BitWriter::WriteBits(s32 value, s32 bit_count) {
|
||||
|
||||
void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
|
||||
const s32 sign = value <= 0 ? 0 : 1;
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
}
|
||||
value = (value << 1) - sign;
|
||||
WriteExpGolombCodedUInt(value);
|
||||
if (!sign) value = -value;
|
||||
WriteExpGolombCodedUInt((value << 1) - sign);
|
||||
}
|
||||
|
||||
void H264BitWriter::WriteExpGolombCodedUInt(u32 value) {
|
||||
const s32 size = 32 - std::countl_zero(value + 1);
|
||||
WriteBits(1, size);
|
||||
|
||||
value -= (1U << (size - 1)) - 1;
|
||||
WriteBits(static_cast<s32>(value), size - 1);
|
||||
WriteBits(s32(value), size - 1);
|
||||
}
|
||||
|
||||
s32 H264BitWriter::GetFreeBufferBits() {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -6,11 +9,10 @@
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "video_core/host1x/codecs/decoder.h"
|
||||
#include "video_core/host1x/codec_types.h"
|
||||
#include "video_core/host1x/nvdec_common.h"
|
||||
|
||||
namespace Tegra {
|
||||
@@ -40,8 +42,7 @@ public:
|
||||
|
||||
/// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification
|
||||
/// Writes the scaling matrices of the sream
|
||||
void WriteScalingList(Common::ScratchBuffer<u8>& scan, std::span<const u8> list, s32 start,
|
||||
s32 count);
|
||||
void WriteScalingList(std::span<const u8> list, s32 start, s32 count);
|
||||
|
||||
/// Return the bitstream as a vector.
|
||||
[[nodiscard]] std::vector<u8>& GetByteArray();
|
||||
@@ -61,188 +62,9 @@ private:
|
||||
std::vector<u8> byte_array;
|
||||
};
|
||||
|
||||
struct Offset {
|
||||
constexpr u32 Address() const noexcept {
|
||||
return offset << 8;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 offset;
|
||||
};
|
||||
static_assert(std::is_trivial_v<Offset>, "Offset must be trivial");
|
||||
static_assert(sizeof(Offset) == 0x4, "Offset has the wrong size!");
|
||||
|
||||
struct H264ParameterSet {
|
||||
s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
|
||||
s32 delta_pic_order_always_zero_flag; ///< 0x04
|
||||
s32 frame_mbs_only_flag; ///< 0x08
|
||||
u32 pic_width_in_mbs; ///< 0x0C
|
||||
u32 frame_height_in_mbs; ///< 0x10
|
||||
union { ///< 0x14
|
||||
BitField<0, 2, u32> tile_format;
|
||||
BitField<2, 3, u32> gob_height;
|
||||
BitField<5, 27, u32> reserved_surface_format;
|
||||
};
|
||||
u32 entropy_coding_mode_flag; ///< 0x18
|
||||
s32 pic_order_present_flag; ///< 0x1C
|
||||
s32 num_refidx_l0_default_active; ///< 0x20
|
||||
s32 num_refidx_l1_default_active; ///< 0x24
|
||||
s32 deblocking_filter_control_present_flag; ///< 0x28
|
||||
s32 redundant_pic_cnt_present_flag; ///< 0x2C
|
||||
u32 transform_8x8_mode_flag; ///< 0x30
|
||||
u32 pitch_luma; ///< 0x34
|
||||
u32 pitch_chroma; ///< 0x38
|
||||
Offset luma_top_offset; ///< 0x3C
|
||||
Offset luma_bot_offset; ///< 0x40
|
||||
Offset luma_frame_offset; ///< 0x44
|
||||
Offset chroma_top_offset; ///< 0x48
|
||||
Offset chroma_bot_offset; ///< 0x4C
|
||||
Offset chroma_frame_offset; ///< 0x50
|
||||
u32 hist_buffer_size; ///< 0x54
|
||||
union { ///< 0x58
|
||||
union {
|
||||
BitField<0, 1, u64> mbaff_frame;
|
||||
BitField<1, 1, u64> direct_8x8_inference;
|
||||
BitField<2, 1, u64> weighted_pred;
|
||||
BitField<3, 1, u64> constrained_intra_pred;
|
||||
BitField<4, 1, u64> ref_pic;
|
||||
BitField<5, 1, u64> field_pic;
|
||||
BitField<6, 1, u64> bottom_field;
|
||||
BitField<7, 1, u64> second_field;
|
||||
} flags;
|
||||
BitField<8, 4, u64> log2_max_frame_num_minus4;
|
||||
BitField<12, 2, u64> chroma_format_idc;
|
||||
BitField<14, 2, u64> pic_order_cnt_type;
|
||||
BitField<16, 6, s64> pic_init_qp_minus26;
|
||||
BitField<22, 5, s64> chroma_qp_index_offset;
|
||||
BitField<27, 5, s64> second_chroma_qp_index_offset;
|
||||
BitField<32, 2, u64> weighted_bipred_idc;
|
||||
BitField<34, 7, u64> curr_pic_idx;
|
||||
BitField<41, 5, u64> curr_col_idx;
|
||||
BitField<46, 16, u64> frame_number;
|
||||
BitField<62, 1, u64> frame_surfaces;
|
||||
BitField<63, 1, u64> output_memory_layout;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(H264ParameterSet) == 0x60, "H264ParameterSet is an invalid size");
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) \
|
||||
static_assert(offsetof(H264ParameterSet, field_name) == position, \
|
||||
"Field " #field_name " has invalid position")
|
||||
|
||||
ASSERT_POSITION(log2_max_pic_order_cnt_lsb_minus4, 0x00);
|
||||
ASSERT_POSITION(delta_pic_order_always_zero_flag, 0x04);
|
||||
ASSERT_POSITION(frame_mbs_only_flag, 0x08);
|
||||
ASSERT_POSITION(pic_width_in_mbs, 0x0C);
|
||||
ASSERT_POSITION(frame_height_in_mbs, 0x10);
|
||||
ASSERT_POSITION(tile_format, 0x14);
|
||||
ASSERT_POSITION(entropy_coding_mode_flag, 0x18);
|
||||
ASSERT_POSITION(pic_order_present_flag, 0x1C);
|
||||
ASSERT_POSITION(num_refidx_l0_default_active, 0x20);
|
||||
ASSERT_POSITION(num_refidx_l1_default_active, 0x24);
|
||||
ASSERT_POSITION(deblocking_filter_control_present_flag, 0x28);
|
||||
ASSERT_POSITION(redundant_pic_cnt_present_flag, 0x2C);
|
||||
ASSERT_POSITION(transform_8x8_mode_flag, 0x30);
|
||||
ASSERT_POSITION(pitch_luma, 0x34);
|
||||
ASSERT_POSITION(pitch_chroma, 0x38);
|
||||
ASSERT_POSITION(luma_top_offset, 0x3C);
|
||||
ASSERT_POSITION(luma_bot_offset, 0x40);
|
||||
ASSERT_POSITION(luma_frame_offset, 0x44);
|
||||
ASSERT_POSITION(chroma_top_offset, 0x48);
|
||||
ASSERT_POSITION(chroma_bot_offset, 0x4C);
|
||||
ASSERT_POSITION(chroma_frame_offset, 0x50);
|
||||
ASSERT_POSITION(hist_buffer_size, 0x54);
|
||||
ASSERT_POSITION(flags, 0x58);
|
||||
#undef ASSERT_POSITION
|
||||
|
||||
struct DpbEntry {
|
||||
union {
|
||||
BitField<0, 7, u32> index;
|
||||
BitField<7, 5, u32> col_idx;
|
||||
BitField<12, 2, u32> state;
|
||||
BitField<14, 1, u32> is_long_term;
|
||||
BitField<15, 1, u32> non_existing;
|
||||
BitField<16, 1, u32> is_field;
|
||||
BitField<17, 4, u32> top_field_marking;
|
||||
BitField<21, 4, u32> bottom_field_marking;
|
||||
BitField<25, 1, u32> output_memory_layout;
|
||||
BitField<26, 6, u32> reserved;
|
||||
} flags;
|
||||
std::array<u32, 2> field_order_cnt;
|
||||
u32 frame_idx;
|
||||
};
|
||||
static_assert(sizeof(DpbEntry) == 0x10, "DpbEntry has the wrong size!");
|
||||
|
||||
struct DisplayParam {
|
||||
union {
|
||||
BitField<0, 1, u32> enable_tf_output;
|
||||
BitField<1, 1, u32> vc1_map_y_flag;
|
||||
BitField<2, 3, u32> map_y_value;
|
||||
BitField<5, 1, u32> vc1_map_uv_flag;
|
||||
BitField<6, 3, u32> map_uv_value;
|
||||
BitField<9, 8, u32> out_stride;
|
||||
BitField<17, 3, u32> tiling_format;
|
||||
BitField<20, 1, u32> output_structure; // 0=frame, 1=field
|
||||
BitField<21, 11, u32> reserved0;
|
||||
};
|
||||
std::array<s32, 2> output_top;
|
||||
std::array<s32, 2> output_bottom;
|
||||
union {
|
||||
BitField<0, 1, u32> enable_histogram;
|
||||
BitField<1, 12, u32> histogram_start_x;
|
||||
BitField<13, 12, u32> histogram_start_y;
|
||||
BitField<25, 7, u32> reserved1;
|
||||
};
|
||||
union {
|
||||
BitField<0, 12, u32> histogram_end_x;
|
||||
BitField<12, 12, u32> histogram_end_y;
|
||||
BitField<24, 8, u32> reserved2;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(DisplayParam) == 0x1C, "DisplayParam has the wrong size!");
|
||||
|
||||
struct H264DecoderContext {
|
||||
INSERT_PADDING_WORDS_NOINIT(13); ///< 0x0000
|
||||
std::array<u8, 16> eos; ///< 0x0034
|
||||
u8 explicit_eos_present_flag; ///< 0x0044
|
||||
u8 hint_dump_en; ///< 0x0045
|
||||
INSERT_PADDING_BYTES_NOINIT(2); ///< 0x0046
|
||||
u32 stream_len; ///< 0x0048
|
||||
u32 slice_count; ///< 0x004C
|
||||
u32 mbhist_buffer_size; ///< 0x0050
|
||||
u32 gptimer_timeout_value; ///< 0x0054
|
||||
H264ParameterSet h264_parameter_set; ///< 0x0058
|
||||
std::array<s32, 2> curr_field_order_cnt; ///< 0x00B8
|
||||
std::array<DpbEntry, 16> dpb; ///< 0x00C0
|
||||
std::array<u8, 0x60> weight_scale_4x4; ///< 0x01C0
|
||||
std::array<u8, 0x80> weight_scale_8x8; ///< 0x0220
|
||||
std::array<u8, 2> num_inter_view_refs_lX; ///< 0x02A0
|
||||
std::array<u8, 14> reserved2; ///< 0x02A2
|
||||
std::array<std::array<s8, 16>, 2> inter_view_refidx_lX; ///< 0x02B0
|
||||
union { ///< 0x02D0
|
||||
BitField<0, 1, u32> lossless_ipred8x8_filter_enable;
|
||||
BitField<1, 1, u32> qpprime_y_zero_transform_bypass_flag;
|
||||
BitField<2, 30, u32> reserved3;
|
||||
};
|
||||
DisplayParam display_param; ///< 0x02D4
|
||||
std::array<u32, 3> reserved4; ///< 0x02F0
|
||||
};
|
||||
static_assert(sizeof(H264DecoderContext) == 0x2FC, "H264DecoderContext is an invalid size");
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) \
|
||||
static_assert(offsetof(H264DecoderContext, field_name) == position, \
|
||||
"Field " #field_name " has invalid position")
|
||||
|
||||
ASSERT_POSITION(stream_len, 0x48);
|
||||
ASSERT_POSITION(h264_parameter_set, 0x58);
|
||||
ASSERT_POSITION(dpb, 0xC0);
|
||||
ASSERT_POSITION(weight_scale_4x4, 0x1C0);
|
||||
#undef ASSERT_POSITION
|
||||
|
||||
class H264 final : public Decoder {
|
||||
public:
|
||||
explicit H264(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
|
||||
Host1x::FrameQueue& frame_queue);
|
||||
explicit H264(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id);
|
||||
~H264() override;
|
||||
|
||||
H264(const H264&) = delete;
|
||||
@@ -263,10 +85,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_first_frame{true};
|
||||
Common::ScratchBuffer<u8> frame_scratch;
|
||||
Common::ScratchBuffer<u8> scan_scratch;
|
||||
H264DecoderContext current_context{};
|
||||
std::array<u8, 64> scan_scratch;
|
||||
Common::ScratchBuffer<u8> frame_scratch;
|
||||
bool is_first_frame{true};
|
||||
};
|
||||
|
||||
} // namespace Decoders
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -8,11 +11,10 @@
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra::Decoders {
|
||||
VP8::VP8(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_,
|
||||
Host1x::FrameQueue& frame_queue_)
|
||||
: Decoder{host1x_, id_, regs_, frame_queue_} {
|
||||
codec = Host1x::NvdecCommon::VideoCodec::VP8;
|
||||
initialized = decode_api.Initialize(codec);
|
||||
VP8::VP8(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_)
|
||||
: Decoder{host1x_, id_, regs_}
|
||||
{
|
||||
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::VP8);
|
||||
}
|
||||
|
||||
VP8::~VP8() = default;
|
||||
@@ -25,35 +27,30 @@ std::tuple<u64, u64> VP8::GetProgressiveOffsets() {
|
||||
|
||||
std::tuple<u64, u64, u64, u64> VP8::GetInterlacedOffsets() {
|
||||
auto luma_top{regs.surface_luma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
|
||||
auto luma_bottom{
|
||||
regs.surface_luma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
|
||||
auto chroma_top{
|
||||
regs.surface_chroma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
|
||||
auto chroma_bottom{
|
||||
regs.surface_chroma_offsets[static_cast<u32>(Vp8SurfaceIndex::Current)].Address()};
|
||||
auto luma_bottom = regs.surface_luma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
|
||||
auto chroma_top = regs.surface_chroma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
|
||||
auto chroma_bottom = regs.surface_chroma_offsets[u32(Vp8SurfaceIndex::Current)].Address();
|
||||
return {luma_top, luma_bottom, chroma_top, chroma_bottom};
|
||||
}
|
||||
|
||||
std::span<const u8> VP8::ComposeFrame() {
|
||||
memory_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context,
|
||||
sizeof(VP8PictureInfo));
|
||||
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(VP8PictureInfo));
|
||||
|
||||
const bool is_key_frame = current_context.key_frame == 1u;
|
||||
const auto bitstream_size = static_cast<size_t>(current_context.vld_buffer_size);
|
||||
const auto bitstream_size = size_t(current_context.vld_buffer_size);
|
||||
const size_t header_size = is_key_frame ? 10u : 3u;
|
||||
frame_scratch.resize(header_size + bitstream_size);
|
||||
|
||||
// Based on page 30 of the VP8 specification.
|
||||
// https://datatracker.ietf.org/doc/rfc6386/
|
||||
frame_scratch[0] = is_key_frame ? 0u : 1u; // 1-bit frame type (0: keyframe, 1: interframes).
|
||||
frame_scratch[0] |=
|
||||
static_cast<u8>((current_context.version & 7u) << 1u); // 3-bit version number
|
||||
frame_scratch[0] |= static_cast<u8>(1u << 4u); // 1-bit show_frame flag
|
||||
frame_scratch[0] |= u8((current_context.version & 7u) << 1u); // 3-bit version number
|
||||
frame_scratch[0] |= u8(1u << 4u); // 1-bit show_frame flag
|
||||
|
||||
// The next 19-bits are the first partition size
|
||||
frame_scratch[0] |= static_cast<u8>((current_context.first_part_size & 7u) << 5u);
|
||||
frame_scratch[1] = static_cast<u8>((current_context.first_part_size & 0x7f8u) >> 3u);
|
||||
frame_scratch[2] = static_cast<u8>((current_context.first_part_size & 0x7f800u) >> 11u);
|
||||
frame_scratch[0] |= u8((current_context.first_part_size & 7u) << 5u);
|
||||
frame_scratch[1] = u8((current_context.first_part_size & 0x7f8u) >> 3u);
|
||||
frame_scratch[2] = u8((current_context.first_part_size & 0x7f800u) >> 11u);
|
||||
|
||||
if (is_key_frame) {
|
||||
frame_scratch[3] = 0x9du;
|
||||
@@ -61,15 +58,15 @@ std::span<const u8> VP8::ComposeFrame() {
|
||||
frame_scratch[5] = 0x2au;
|
||||
// TODO(ameerj): Horizontal/Vertical Scale
|
||||
// 16 bits: (2 bits Horizontal Scale << 14) | Width (14 bits)
|
||||
frame_scratch[6] = static_cast<u8>(current_context.frame_width & 0xff);
|
||||
frame_scratch[7] = static_cast<u8>(((current_context.frame_width >> 8) & 0x3f));
|
||||
frame_scratch[6] = u8(current_context.frame_width & 0xff);
|
||||
frame_scratch[7] = u8(((current_context.frame_width >> 8) & 0x3f));
|
||||
// 16 bits:(2 bits Vertical Scale << 14) | Height (14 bits)
|
||||
frame_scratch[8] = static_cast<u8>(current_context.frame_height & 0xff);
|
||||
frame_scratch[9] = static_cast<u8>(((current_context.frame_height >> 8) & 0x3f));
|
||||
frame_scratch[8] = u8(current_context.frame_height & 0xff);
|
||||
frame_scratch[9] = u8(((current_context.frame_height >> 8) & 0x3f));
|
||||
}
|
||||
const u64 bitstream_offset = regs.frame_bitstream_offset.Address();
|
||||
memory_manager.ReadBlock(bitstream_offset, frame_scratch.data() + header_size, bitstream_size);
|
||||
|
||||
const u64 bitstream_offset = regs.frame_bitstream_offset.Address();
|
||||
host1x.gmmu_manager.ReadBlock(bitstream_offset, frame_scratch.data() + header_size, bitstream_size);
|
||||
return frame_scratch;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -11,6 +14,7 @@
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "video_core/host1x/codecs/decoder.h"
|
||||
#include "video_core/host1x/nvdec_common.h"
|
||||
#include "video_core/host1x/codec_types.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
@@ -28,8 +32,7 @@ enum class Vp8SurfaceIndex : u32 {
|
||||
|
||||
class VP8 final : public Decoder {
|
||||
public:
|
||||
explicit VP8(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
|
||||
Host1x::FrameQueue& frame_queue);
|
||||
explicit VP8(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id);
|
||||
~VP8() override;
|
||||
|
||||
VP8(const VP8&) = delete;
|
||||
@@ -52,51 +55,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Common::ScratchBuffer<u8> frame_scratch;
|
||||
|
||||
struct VP8PictureInfo {
|
||||
INSERT_PADDING_WORDS_NOINIT(14);
|
||||
u16 frame_width; // actual frame width
|
||||
u16 frame_height; // actual frame height
|
||||
u8 key_frame;
|
||||
u8 version;
|
||||
union {
|
||||
u8 raw;
|
||||
BitField<0, 2, u8> tile_format;
|
||||
BitField<2, 3, u8> gob_height;
|
||||
BitField<5, 3, u8> reserved_surface_format;
|
||||
};
|
||||
u8 error_conceal_on; // 1: error conceal on; 0: off
|
||||
u32 first_part_size; // the size of first partition(frame header and mb header partition)
|
||||
u32 hist_buffer_size; // in units of 256
|
||||
u32 vld_buffer_size; // in units of 1
|
||||
// Current frame buffers
|
||||
std::array<u32, 2> frame_stride; // [y_c]
|
||||
u32 luma_top_offset; // offset of luma top field in units of 256
|
||||
u32 luma_bot_offset; // offset of luma bottom field in units of 256
|
||||
u32 luma_frame_offset; // offset of luma frame in units of 256
|
||||
u32 chroma_top_offset; // offset of chroma top field in units of 256
|
||||
u32 chroma_bot_offset; // offset of chroma bottom field in units of 256
|
||||
u32 chroma_frame_offset; // offset of chroma frame in units of 256
|
||||
|
||||
INSERT_PADDING_BYTES_NOINIT(0x1c); // NvdecDisplayParams
|
||||
|
||||
// Decode picture buffer related
|
||||
s8 current_output_memory_layout;
|
||||
// output NV12/NV24 setting. index 0: golden; 1: altref; 2: last
|
||||
std::array<s8, 3> output_memory_layout;
|
||||
|
||||
u8 segmentation_feature_data_update;
|
||||
INSERT_PADDING_BYTES_NOINIT(3);
|
||||
|
||||
// ucode return result
|
||||
u32 result_value;
|
||||
std::array<u32, 8> partition_offset;
|
||||
INSERT_PADDING_WORDS_NOINIT(3);
|
||||
};
|
||||
static_assert(sizeof(VP8PictureInfo) == 0xc0, "PictureInfo is an invalid size");
|
||||
|
||||
VP8PictureInfo current_context{};
|
||||
Common::ScratchBuffer<u8> frame_scratch;
|
||||
};
|
||||
|
||||
} // namespace Decoders
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm> // for std::copy
|
||||
#include <bit>
|
||||
#include <numeric>
|
||||
|
||||
#include "common/alignment.h"
|
||||
@@ -15,11 +16,11 @@
|
||||
|
||||
namespace Tegra::Decoders {
|
||||
namespace {
|
||||
constexpr u32 diff_update_probability = 252;
|
||||
constexpr u32 frame_sync_code = 0x498342;
|
||||
static constexpr u32 diff_update_probability = 252;
|
||||
static constexpr u32 frame_sync_code = 0x498342;
|
||||
|
||||
// Default compressed header probabilities once frame context resets
|
||||
constexpr Vp9EntropyProbs default_probs{
|
||||
static constexpr Vp9EntropyProbs default_probs{
|
||||
.y_mode_prob{
|
||||
65, 32, 18, 144, 162, 194, 41, 51, 98, 132, 68, 18, 165, 217, 196, 45, 40, 78,
|
||||
173, 80, 19, 176, 240, 193, 64, 35, 46, 221, 135, 38, 194, 248, 121, 96, 85, 29,
|
||||
@@ -158,34 +159,6 @@ constexpr Vp9EntropyProbs default_probs{
|
||||
.high_precision{128, 128},
|
||||
};
|
||||
|
||||
constexpr std::array<u8, 256> norm_lut{
|
||||
0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
constexpr std::array<u8, 254> map_lut{
|
||||
20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
|
||||
1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, 51, 52, 53, 54,
|
||||
55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||
73, 4, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 5, 86, 87, 88, 89,
|
||||
90, 91, 92, 93, 94, 95, 96, 97, 6, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
|
||||
108, 109, 7, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 8, 122, 123, 124,
|
||||
125, 126, 127, 128, 129, 130, 131, 132, 133, 9, 134, 135, 136, 137, 138, 139, 140, 141, 142,
|
||||
143, 144, 145, 10, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 11, 158, 159,
|
||||
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, 177,
|
||||
178, 179, 180, 181, 13, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 14, 194,
|
||||
195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, 208, 209, 210, 211, 212,
|
||||
213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 17,
|
||||
230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 18, 242, 243, 244, 245, 246, 247,
|
||||
248, 249, 250, 251, 252, 253, 19,
|
||||
};
|
||||
|
||||
// 6.2.14 Tile size calculation
|
||||
|
||||
[[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) {
|
||||
@@ -227,25 +200,17 @@ constexpr std::array<u8, 254> map_lut{
|
||||
[[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) {
|
||||
new_prob--;
|
||||
old_prob--;
|
||||
|
||||
std::size_t index{};
|
||||
|
||||
if (old_prob * 2 <= 0xff) {
|
||||
index = static_cast<std::size_t>((std::max)(0, RecenterNonNeg(new_prob, old_prob) - 1));
|
||||
} else {
|
||||
index = static_cast<std::size_t>(
|
||||
(std::max)(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
|
||||
}
|
||||
|
||||
return static_cast<s32>(map_lut[index]);
|
||||
u8 i = old_prob * 2 <= 0xff
|
||||
? u8((std::max)(0, RecenterNonNeg(new_prob, old_prob) - 1))
|
||||
: u8((std::max)(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
|
||||
return s32((i + 7) % 13 == 0 ? (i + 7) / 13 - 1 : i + 20 - (i + 7) / 13);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
VP9::VP9(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_,
|
||||
Host1x::FrameQueue& frame_queue_)
|
||||
: Decoder{host1x_, id_, regs_, frame_queue_} {
|
||||
codec = Host1x::NvdecCommon::VideoCodec::VP9;
|
||||
initialized = decode_api.Initialize(codec);
|
||||
VP9::VP9(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs_, s32 id_)
|
||||
: Decoder{host1x_, id_, regs_}
|
||||
{
|
||||
initialized = decode_api.Initialize(Host1x::NvdecCommon::VideoCodec::VP9);
|
||||
}
|
||||
|
||||
VP9::~VP9() = default;
|
||||
@@ -377,8 +342,7 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
|
||||
|
||||
if (update_map) {
|
||||
EntropyProbs entropy_probs{};
|
||||
memory_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs,
|
||||
sizeof(entropy_probs));
|
||||
host1x.gmmu_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs, sizeof(entropy_probs));
|
||||
|
||||
auto WriteProb = [&](u8 prob) {
|
||||
bool coded = prob != 255;
|
||||
@@ -442,8 +406,7 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
|
||||
}
|
||||
|
||||
Vp9PictureInfo VP9::GetVp9PictureInfo() {
|
||||
memory_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_picture_info,
|
||||
sizeof(PictureInfo));
|
||||
host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_picture_info, sizeof(PictureInfo));
|
||||
Vp9PictureInfo vp9_info = current_picture_info.Convert();
|
||||
|
||||
InsertEntropy(regs.vp9_prob_tab_buffer_offset.Address(), vp9_info.entropy);
|
||||
@@ -459,7 +422,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo() {
|
||||
|
||||
void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
|
||||
EntropyProbs entropy;
|
||||
memory_manager.ReadBlock(offset, &entropy, sizeof(EntropyProbs));
|
||||
host1x.gmmu_manager.ReadBlock(offset, &entropy, sizeof(EntropyProbs));
|
||||
entropy.Convert(dst);
|
||||
}
|
||||
|
||||
@@ -469,9 +432,7 @@ Vp9FrameContainer VP9::GetCurrentFrame() {
|
||||
// gpu.SyncGuestHost(); epic, why?
|
||||
current_frame.info = GetVp9PictureInfo();
|
||||
current_frame.bit_stream.resize(current_frame.info.bitstream_size);
|
||||
memory_manager.ReadBlock(regs.frame_bitstream_offset.Address(),
|
||||
current_frame.bit_stream.data(),
|
||||
current_frame.info.bitstream_size);
|
||||
host1x.gmmu_manager.ReadBlock(regs.frame_bitstream_offset.Address(), current_frame.bit_stream.data(), current_frame.info.bitstream_size);
|
||||
}
|
||||
if (!next_frame.bit_stream.empty()) {
|
||||
Vp9FrameContainer temp{
|
||||
@@ -893,13 +854,9 @@ std::span<const u8> VP9::ComposeFrame() {
|
||||
// Write headers and frame to buffer
|
||||
frame_scratch.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size());
|
||||
std::copy(uncompressed_header.begin(), uncompressed_header.end(), frame_scratch.begin());
|
||||
std::copy(compressed_header.begin(), compressed_header.end(),
|
||||
frame_scratch.begin() + uncompressed_header.size());
|
||||
std::copy(bitstream.begin(), bitstream.end(),
|
||||
frame_scratch.begin() + uncompressed_header.size() + compressed_header.size());
|
||||
|
||||
std::copy(compressed_header.begin(), compressed_header.end(), frame_scratch.begin() + uncompressed_header.size());
|
||||
std::copy(bitstream.begin(), bitstream.end(), frame_scratch.begin() + uncompressed_header.size() + compressed_header.size());
|
||||
vp9_hidden_frame = WasFrameHidden();
|
||||
|
||||
return GetFrameBytes();
|
||||
}
|
||||
|
||||
@@ -929,7 +886,7 @@ void VpxRangeEncoder::Write(bool bit, s32 probability) {
|
||||
local_range = range - split;
|
||||
}
|
||||
|
||||
s32 shift = static_cast<s32>(norm_lut[local_range]);
|
||||
s32 shift = s32(local_range == 0 ? 0 : (std::countl_zero<uint32_t>(local_range) - 24));
|
||||
local_range <<= shift;
|
||||
count += shift;
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -11,7 +14,7 @@
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/stream.h"
|
||||
#include "video_core/host1x/codecs/decoder.h"
|
||||
#include "video_core/host1x/codecs/vp9_types.h"
|
||||
#include "video_core/host1x/codec_types.h"
|
||||
#include "video_core/host1x/nvdec_common.h"
|
||||
|
||||
namespace Tegra {
|
||||
@@ -113,8 +116,7 @@ private:
|
||||
|
||||
class VP9 final : public Decoder {
|
||||
public:
|
||||
explicit VP9(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id,
|
||||
Host1x::FrameQueue& frame_queue);
|
||||
explicit VP9(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs, s32 id);
|
||||
~VP9() override;
|
||||
|
||||
VP9(const VP9&) = delete;
|
||||
@@ -193,11 +195,10 @@ private:
|
||||
[[nodiscard]] std::vector<u8> ComposeCompressedHeader();
|
||||
[[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
|
||||
|
||||
Common::ScratchBuffer<u8> frame_scratch;
|
||||
|
||||
std::array<s8, 4> loop_filter_ref_deltas{};
|
||||
std::array<s8, 2> loop_filter_mode_deltas{};
|
||||
|
||||
Common::ScratchBuffer<u8> frame_scratch;
|
||||
Vp9FrameContainer next_frame{};
|
||||
std::array<Vp9EntropyProbs, 4> frame_ctxs{};
|
||||
bool swap_ref_indices{};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/host1x/ffmpeg/ffmpeg.h"
|
||||
#include "video_core/host1x/ffmpeg.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
extern "C" {
|
||||
@@ -34,7 +34,9 @@ constexpr std::array PreferredGpuDecoders = {
|
||||
AV_HWDEVICE_TYPE_DXVA2,
|
||||
AV_HWDEVICE_TYPE_D3D12VA,
|
||||
#elif defined(__FreeBSD__)
|
||||
AV_HWDEVICE_TYPE_VAAPI,
|
||||
AV_HWDEVICE_TYPE_VDPAU,
|
||||
AV_HWDEVICE_TYPE_DRM,
|
||||
#elif defined(__APPLE__)
|
||||
AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
|
||||
#elif defined(ANDROID)
|
||||
@@ -105,18 +107,17 @@ Frame::~Frame() {
|
||||
Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
|
||||
const AVCodecID av_codec = [&] {
|
||||
switch (codec) {
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
|
||||
return AV_CODEC_ID_H264;
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
|
||||
return AV_CODEC_ID_VP8;
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
|
||||
return AV_CODEC_ID_VP9;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unknown codec {}", codec);
|
||||
return AV_CODEC_ID_NONE;
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
|
||||
return AV_CODEC_ID_H264;
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
|
||||
return AV_CODEC_ID_VP8;
|
||||
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
|
||||
return AV_CODEC_ID_VP9;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unknown codec {}", codec);
|
||||
return AV_CODEC_ID_NONE;
|
||||
}
|
||||
}();
|
||||
|
||||
m_codec = avcodec_find_decoder(av_codec);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
@@ -12,9 +12,12 @@
|
||||
namespace Tegra::Host1x {
|
||||
|
||||
Host1x::Host1x(Core::System& system_)
|
||||
: system{system_}, syncpoint_manager{},
|
||||
memory_manager(system.DeviceMemory()), gmmu_manager{system, memory_manager, 32, 0, 12},
|
||||
allocator{std::make_unique<Common::FlatAllocator<u32, 0, 32>>(1 << 12)} {}
|
||||
: system{system_}
|
||||
, syncpoint_manager{}
|
||||
, memory_manager(system.DeviceMemory())
|
||||
, gmmu_manager{system, memory_manager, 32, 0, 12}
|
||||
, allocator{1 << 12}
|
||||
{}
|
||||
|
||||
Host1x::~Host1x() = default;
|
||||
|
||||
@@ -24,13 +27,13 @@ void Host1x::StartDevice(s32 fd, ChannelType type, u32 syncpt) {
|
||||
#ifdef YUZU_LEGACY
|
||||
std::call_once(nvdec_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer
|
||||
#endif
|
||||
devices[fd] = std::make_unique<Tegra::Host1x::Nvdec>(*this, fd, syncpt, frame_queue);
|
||||
devices[fd] = std::make_unique<Tegra::Host1x::Nvdec>(*this, fd, syncpt);
|
||||
break;
|
||||
case ChannelType::VIC:
|
||||
#ifdef YUZU_LEGACY
|
||||
std::call_once(vic_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer
|
||||
#endif
|
||||
devices[fd] = std::make_unique<Tegra::Host1x::Vic>(*this, fd, syncpt, frame_queue);
|
||||
devices[fd] = std::make_unique<Tegra::Host1x::Vic>(*this, fd, syncpt);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(HW_GPU, "Unimplemented host1x device {}", static_cast<u32>(type));
|
||||
|
||||
@@ -184,20 +184,12 @@ public:
|
||||
return memory_manager;
|
||||
}
|
||||
|
||||
Tegra::MemoryManager& GMMU() {
|
||||
return gmmu_manager;
|
||||
}
|
||||
|
||||
const Tegra::MemoryManager& GMMU() const {
|
||||
return gmmu_manager;
|
||||
}
|
||||
|
||||
Common::FlatAllocator<u32, 0, 32>& Allocator() {
|
||||
return *allocator;
|
||||
return allocator;
|
||||
}
|
||||
|
||||
const Common::FlatAllocator<u32, 0, 32>& Allocator() const {
|
||||
return *allocator;
|
||||
return allocator;
|
||||
}
|
||||
|
||||
void StartDevice(s32 fd, ChannelType type, u32 syncpt);
|
||||
@@ -211,12 +203,11 @@ public:
|
||||
it->second->PushEntries(std::move(entries));
|
||||
}
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
SyncpointManager syncpoint_manager;
|
||||
Tegra::MaxwellDeviceMemoryManager memory_manager;
|
||||
Tegra::MemoryManager gmmu_manager;
|
||||
std::unique_ptr<Common::FlatAllocator<u32, 0, 32>> allocator;
|
||||
Common::FlatAllocator<u32, 0, 32> allocator;
|
||||
FrameQueue frame_queue;
|
||||
ankerl::unordered_dense::map<s32, std::unique_ptr<CDmaPusher>> devices;
|
||||
#ifdef YUZU_LEGACY
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <variant>
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
@@ -19,10 +20,13 @@ namespace Tegra::Host1x {
|
||||
#define NVDEC_REG_INDEX(field_name) \
|
||||
(offsetof(NvdecCommon::NvdecRegisters, field_name) / sizeof(u64))
|
||||
|
||||
Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt, FrameQueue& frame_queue_)
|
||||
: CDmaPusher{host1x_, id_}, id{id_}, syncpoint{syncpt}, frame_queue{frame_queue_} {
|
||||
Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt)
|
||||
: CDmaPusher{host1x_, id_}
|
||||
, id{id_}
|
||||
, syncpoint{syncpt}
|
||||
{
|
||||
LOG_INFO(HW_GPU, "Created nvdec {}", id);
|
||||
frame_queue.Open(id);
|
||||
host1x.frame_queue.Open(id);
|
||||
}
|
||||
|
||||
Nvdec::~Nvdec() {
|
||||
@@ -43,24 +47,22 @@ void Nvdec::ProcessMethod(u32 method, u32 argument) {
|
||||
}
|
||||
|
||||
void Nvdec::CreateDecoder(NvdecCommon::VideoCodec codec) {
|
||||
if (decoder.get()) {
|
||||
return;
|
||||
if (std::holds_alternative<std::monostate>(decoder)) {
|
||||
switch (codec) {
|
||||
case NvdecCommon::VideoCodec::H264:
|
||||
decoder.emplace<Decoders::H264>(host1x, regs, id);
|
||||
break;
|
||||
case NvdecCommon::VideoCodec::VP8:
|
||||
decoder.emplace<Decoders::VP8>(host1x, regs, id);
|
||||
break;
|
||||
case NvdecCommon::VideoCodec::VP9:
|
||||
decoder.emplace<Decoders::VP9>(host1x, regs, id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LOG_INFO(HW_GPU, "Created decoder {} for id {}", codec, id);
|
||||
}
|
||||
switch (codec) {
|
||||
case NvdecCommon::VideoCodec::H264:
|
||||
decoder = std::make_unique<Decoders::H264>(host1x, regs, id, frame_queue);
|
||||
break;
|
||||
case NvdecCommon::VideoCodec::VP8:
|
||||
decoder = std::make_unique<Decoders::VP8>(host1x, regs, id, frame_queue);
|
||||
break;
|
||||
case NvdecCommon::VideoCodec::VP9:
|
||||
decoder = std::make_unique<Decoders::VP9>(host1x, regs, id, frame_queue);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Codec {}", decoder->GetCurrentCodecName());
|
||||
break;
|
||||
}
|
||||
LOG_INFO(HW_GPU, "Created decoder {} for id {}", decoder->GetCurrentCodecName(), id);
|
||||
}
|
||||
|
||||
void Nvdec::Execute() {
|
||||
@@ -70,15 +72,14 @@ void Nvdec::Execute() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(8));
|
||||
return;
|
||||
}
|
||||
switch (decoder->GetCurrentCodec()) {
|
||||
case NvdecCommon::VideoCodec::H264:
|
||||
case NvdecCommon::VideoCodec::VP8:
|
||||
case NvdecCommon::VideoCodec::VP9:
|
||||
decoder->Decode();
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Codec {}", decoder->GetCurrentCodecName());
|
||||
break;
|
||||
if (auto* h264 = std::get_if<Decoders::H264>(&decoder)) {
|
||||
h264->Decode();
|
||||
} else if (auto* vp8 = std::get_if<Decoders::VP8>(&decoder)) {
|
||||
vp8->Decode();
|
||||
} else if (auto* vp9 = std::get_if<Decoders::VP9>(&decoder)) {
|
||||
vp9->Decode();
|
||||
} else {
|
||||
LOG_ERROR(HW_GPU, "Unrecognized codec executed?");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user