mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-25 16:54:41 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e20cd98a7 | |||
| 6a3adefd46 | |||
| 0fc813332f | |||
| 95c78a206c | |||
| 24cb761852 | |||
| 23d0ad417d | |||
| 2fa88c4868 | |||
| a9461e23b3 | |||
| 8476fbfcfe | |||
| ad854018df | |||
| b748f0ad5f | |||
| b2c9ccfc7e | |||
| 13f48a56ad | |||
| 63e33f37f6 | |||
| 41cd784835 | |||
| c41cadf3f4 | |||
| a43664c0fd | |||
| a0f1cd1baf | |||
| 49a0ca6d5d | |||
| 5e1d5e82dc | |||
| ba9130fbf9 | |||
| 7d5f390ffb | |||
| 8fe1e6efa2 | |||
| ee197e6222 | |||
| 612409c7ba |
@@ -188,6 +188,7 @@ android {
|
||||
create("mainline") {
|
||||
dimension = "version"
|
||||
isDefault = true
|
||||
minSdk = 33
|
||||
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden")
|
||||
resValue("string", "app_name_suffixed", "Eden")
|
||||
@@ -199,6 +200,7 @@ android {
|
||||
|
||||
create("genshinSpoof") {
|
||||
dimension = "version"
|
||||
minSdk = 35
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden Optimized")
|
||||
resValue("string", "app_name_suffixed", "Eden Optimized")
|
||||
applicationId = "com.miHoYo.Yuanshen"
|
||||
@@ -216,6 +218,7 @@ android {
|
||||
|
||||
create("legacy") {
|
||||
dimension = "version"
|
||||
minSdk = 29
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden Legacy")
|
||||
resValue("string", "app_name_suffixed", "Eden Legacy")
|
||||
applicationId = "dev.legacy.eden_emulator"
|
||||
|
||||
@@ -218,6 +218,8 @@ object NativeLibrary {
|
||||
|
||||
external fun logSettings()
|
||||
|
||||
external fun refreshThreadPolicies()
|
||||
|
||||
external fun getDebugKnobAt(index: Int): Boolean
|
||||
|
||||
/**
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
|
||||
RENDERER_ASYNC_PRESENTATION("async_presentation"),
|
||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
|
||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
|
||||
|
||||
+8
-1
@@ -594,7 +594,7 @@ abstract class SettingsItem(
|
||||
IntSetting.ANDROID_PIPELINE_WORKERS,
|
||||
titleId = R.string.pipeline_worker_cores,
|
||||
descriptionId = R.string.pipeline_worker_cores_description,
|
||||
min = 4,
|
||||
min = 2,
|
||||
max = 8,
|
||||
units = "cores"
|
||||
)
|
||||
@@ -685,6 +685,13 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.renderer_asynchronous_shaders_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_UNIFIED_MEMORY,
|
||||
titleId = R.string.renderer_unified_memory,
|
||||
descriptionId = R.string.renderer_unified_memory_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.FAST_GPU_TIME,
|
||||
|
||||
+3
-2
@@ -250,8 +250,9 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.USE_CUSTOM_RTC.key)
|
||||
add(LongSetting.CUSTOM_RTC.key)
|
||||
|
||||
add(HeaderSetting(R.string.cpu))
|
||||
add(HeaderSetting(R.string.clocks))
|
||||
add(IntSetting.FAST_CPU_TIME.key)
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
|
||||
|
||||
add(IntSetting.MEMORY_LAYOUT.key)
|
||||
@@ -298,12 +299,12 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(HeaderSetting(R.string.hacks))
|
||||
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
|
||||
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
|
||||
add(BooleanSetting.EMULATE_BGR565.key)
|
||||
add(BooleanSetting.RESCALE_HACK.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
|
||||
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
|
||||
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
|
||||
|
||||
@@ -1451,6 +1451,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
NativeLibrary.refreshThreadPolicies()
|
||||
val b = _binding ?: return
|
||||
updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt())
|
||||
updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt())
|
||||
|
||||
@@ -20,132 +20,96 @@ struct RomMetadata {
|
||||
std::vector<u8> icon;
|
||||
bool isHomebrew;
|
||||
};
|
||||
static ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
|
||||
|
||||
ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
|
||||
static RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
auto& instance = EmulationSession::GetInstance();
|
||||
const auto file = Core::GetGameFileFromPath(instance.System().GetFilesystem(), path);
|
||||
if (auto loader = Loader::GetLoader(instance.System(), file, 0, 0); loader) {
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadProgramId(entry.programId);
|
||||
loader->ReadIcon(entry.icon);
|
||||
|
||||
RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
const auto file =
|
||||
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
const FileSys::PatchManager pm{
|
||||
entry.programId,
|
||||
instance.System().GetFileSystemController(),
|
||||
instance.System().GetContentProvider()
|
||||
};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadProgramId(entry.programId);
|
||||
loader->ReadIcon(entry.icon);
|
||||
|
||||
const FileSys::PatchManager pm{
|
||||
entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
EmulationSession::GetInstance().System().GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
if (control.first != nullptr) {
|
||||
entry.developer = control.first->GetDeveloperName();
|
||||
entry.version = control.first->GetVersionString();
|
||||
} else {
|
||||
FileSys::NACP nacp;
|
||||
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
|
||||
entry.developer = nacp.GetDeveloperName();
|
||||
if (control.first != nullptr) {
|
||||
entry.developer = control.first->GetDeveloperName();
|
||||
entry.version = control.first->GetVersionString();
|
||||
} else {
|
||||
entry.developer = "";
|
||||
FileSys::NACP nacp{};
|
||||
entry.developer = loader->ReadControlData(nacp) == Loader::ResultStatus::Success
|
||||
? nacp.GetDeveloperName()
|
||||
: "";
|
||||
entry.version = "1.0.0";
|
||||
}
|
||||
|
||||
entry.version = "1.0.0";
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
}
|
||||
m_rom_metadata_cache[path] = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
}
|
||||
|
||||
m_rom_metadata_cache[path] = entry;
|
||||
|
||||
return entry;
|
||||
return {};
|
||||
}
|
||||
|
||||
RomMetadata GetRomMetadata(const std::string& path, bool reload = false) {
|
||||
if (reload) {
|
||||
static RomMetadata GetRomMetadata(const std::string& path, bool reload = false) {
|
||||
if (reload)
|
||||
return CacheRomMetadata(path);
|
||||
}
|
||||
|
||||
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
if (auto it = m_rom_metadata_cache.find(path); it != m_rom_metadata_cache.end())
|
||||
return it->second;
|
||||
return CacheRomMetadata(path);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
|
||||
Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read);
|
||||
if (!file) {
|
||||
return false;
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
if (auto const file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read); file) {
|
||||
if (auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file); loader) {
|
||||
auto const file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error)
|
||||
return false;
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) && !Loader::IsBootableGameContainer(file, file_type))
|
||||
return false;
|
||||
u64 program_id = 0;
|
||||
return loader->ReadProgramId(program_id) == Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file);
|
||||
if (!loader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
|
||||
!Loader::IsBootableGameContainer(file, file_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
Loader::ResultStatus res = loader->ReadProgramId(program_id);
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId));
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId));
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
|
||||
jstring jpath, jboolean jreload) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, jstring jpath, jboolean jreload) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version);
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
auto icon_data = GetRomMetadata(Common::Android::GetJString(env, jpath)).icon;
|
||||
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
|
||||
reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
jbyteArray icon = env->NewByteArray(jsize(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
return icon;
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return static_cast<jboolean>(
|
||||
GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew);
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return jboolean(GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
|
||||
|
||||
@@ -50,6 +50,7 @@ extern "C" {
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/thread.h"
|
||||
#include "frontend_common/play_time_manager.h"
|
||||
#include "core/constants.h"
|
||||
#include "core/core.h"
|
||||
@@ -1182,6 +1183,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
|
||||
Settings::LogSettings();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) {
|
||||
Common::RefreshThreadPolicies();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
|
||||
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
|
||||
}
|
||||
|
||||
@@ -958,7 +958,6 @@
|
||||
<string name="clock_fast">سريع (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">تعطيل</string>
|
||||
<string name="fast_gpu_medium">متوسط (256)</string>
|
||||
<string name="fast_gpu_high">مرتفع (512)</string>
|
||||
|
||||
|
||||
@@ -884,7 +884,6 @@ Wirklich fortfahren?</string>
|
||||
<string name="clock_fast">Schnell (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Aus</string>
|
||||
<string name="fast_gpu_medium">Mittel (256)</string>
|
||||
<string name="fast_gpu_high">Hoch (512)</string>
|
||||
|
||||
|
||||
@@ -950,7 +950,6 @@
|
||||
<string name="clock_fast">Rápido (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Desactivado</string>
|
||||
<string name="fast_gpu_medium">Medio (256)</string>
|
||||
<string name="fast_gpu_high">Alto (512)</string>
|
||||
|
||||
|
||||
@@ -897,7 +897,6 @@
|
||||
<string name="clock_fast">Rapide (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Désactivé</string>
|
||||
<string name="fast_gpu_medium">Moyen (256)</string>
|
||||
<string name="fast_gpu_high">Élevé (512)</string>
|
||||
|
||||
|
||||
@@ -871,7 +871,6 @@
|
||||
<string name="clock_fast">Szybkie (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Wyłączone</string>
|
||||
<string name="fast_gpu_medium">Średnie (256)</string>
|
||||
<string name="fast_gpu_high">Wysokie (512)</string>
|
||||
|
||||
|
||||
@@ -954,7 +954,6 @@
|
||||
<string name="clock_fast">Быстрая (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Выкл.</string>
|
||||
<string name="fast_gpu_medium">Среднее (256)</string>
|
||||
<string name="fast_gpu_high">Высокое (512)</string>
|
||||
|
||||
|
||||
@@ -943,7 +943,6 @@
|
||||
<string name="clock_fast">Швидко (2000 МГц)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Вимкнено</string>
|
||||
<string name="fast_gpu_medium">Середньо (256)</string>
|
||||
<string name="fast_gpu_high">Високо (512)</string>
|
||||
|
||||
|
||||
@@ -948,7 +948,6 @@
|
||||
<string name="clock_fast">快速 (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">关闭</string>
|
||||
<string name="fast_gpu_medium">中 (256)</string>
|
||||
<string name="fast_gpu_high">高 (512)</string>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</integer-array>
|
||||
|
||||
<string-array name="clockNames">
|
||||
<item>@string/off</item>
|
||||
<item>@string/clock_normal</item>
|
||||
<item>@string/clock_boost</item>
|
||||
<item>@string/clock_fast</item>
|
||||
</string-array>
|
||||
@@ -547,7 +547,7 @@
|
||||
</integer-array>
|
||||
|
||||
<string-array name="gpuEntries">
|
||||
<item>@string/off</item>
|
||||
<item>@string/fast_gpu_normal</item>
|
||||
<item>@string/fast_gpu_medium</item>
|
||||
<item>@string/fast_gpu_high</item>
|
||||
</string-array>
|
||||
|
||||
@@ -450,8 +450,8 @@
|
||||
<string name="set_custom_rtc">Set custom RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">CPU Overclock</string>
|
||||
<string name="fast_cpu_time_description">Forces the emulated CPU to run at a higher clock, reducing certain FPS limiters. Use Boost (1700MHz) to run at the Switch\'s highest native clock, or Fast (2000MHz) to run at 2x clock.</string>
|
||||
<string name="fast_cpu_time">CPU Clocks</string>
|
||||
<string name="fast_cpu_time_description">Raises the clock the emulated CPU reports, which removes some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly.</string>
|
||||
<string name="custom_cpu_ticks">Custom CPU Ticks</string>
|
||||
<string name="custom_cpu_ticks_description">Set a custom value of CPU ticks. Higher values can increase performance, but may also cause the game to freeze. A range of 77–21000 is recommended.</string>
|
||||
<string name="cpu_ticks">Ticks</string>
|
||||
@@ -512,8 +512,8 @@
|
||||
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
<string name="fast_gpu_time">Fast GPU Time</string>
|
||||
<string name="fast_gpu_time_description">Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity.</string>
|
||||
<string name="fast_gpu_time">GPU Clocks</string>
|
||||
<string name="fast_gpu_time_description">Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch\'s clocks.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
|
||||
<string name="fix_bloom_effects">Fix Bloom Effects</string>
|
||||
@@ -524,6 +524,8 @@
|
||||
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
|
||||
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
|
||||
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
|
||||
<string name="renderer_unified_memory">Unified memory access</string>
|
||||
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
|
||||
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
|
||||
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
|
||||
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
|
||||
@@ -560,6 +562,7 @@
|
||||
|
||||
<!-- Debug settings strings -->
|
||||
<string name="cpu">CPU</string>
|
||||
<string name="clocks">Clocks</string>
|
||||
<string name="use_auto_stub">Use Auto Stub</string>
|
||||
<string name="use_auto_stub_description">Automatically stub missing services and functions. This may improve compatibility but can cause crashes and stability issues.</string>
|
||||
|
||||
@@ -968,14 +971,15 @@
|
||||
<string name="memory_6gb">6GB (Unsafe)</string>
|
||||
<string name="memory_8gb">8GB (Unsafe)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Fast (2000MHz)</string>
|
||||
<!-- CPU clock levels -->
|
||||
<string name="clock_normal">Normal</string>
|
||||
<string name="clock_boost">Boost</string>
|
||||
<string name="clock_fast">Overclock</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Off</string>
|
||||
<string name="fast_gpu_medium">Medium (256)</string>
|
||||
<string name="fast_gpu_high">High (512)</string>
|
||||
<!-- GPU clock levels -->
|
||||
<string name="fast_gpu_normal">Normal</string>
|
||||
<string name="fast_gpu_medium">Boost</string>
|
||||
<string name="fast_gpu_high">Overclock</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">Very Small (16 MB)</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<game-mode-config
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:supportsBatteryGameMode="true"
|
||||
android:supportsBatteryGameMode="false"
|
||||
android:supportsPerformanceGameMode="true"
|
||||
android:allowGameDownscaling="false"
|
||||
android:allowGameFpsOverride="false"/>
|
||||
@@ -18,20 +18,15 @@
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
constexpr u32 TempBufferSize = 0x3F00;
|
||||
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
|
||||
|
||||
/**
|
||||
* Decode PCM data. Only s16 or f32 is supported.
|
||||
*
|
||||
* @tparam T - Type to decode. Only s16 and f32 are supported.
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
/// @brief Decode PCM data. Only s16 or f32 is supported.
|
||||
/// @tparam T - Type to decode. Only s16 and f32 are supported.
|
||||
/// @param memory - Core memory for reading samples.
|
||||
/// @param out_buffer - Output mix buffer to receive the samples.
|
||||
/// @param req - Information for how to decode.
|
||||
/// @return Number of samples decoded.
|
||||
template <typename T>
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, const DecodeArg& req) {
|
||||
constexpr s32 min{(std::numeric_limits<s16>::min)()};
|
||||
constexpr s32 max{(std::numeric_limits<s16>::max)()};
|
||||
|
||||
@@ -94,16 +89,12 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return samples_to_decode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode ADPCM data.
|
||||
*
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
/// @brief Decode ADPCM data.
|
||||
/// @param memory - Core memory for reading samples.
|
||||
/// @param out_buffer - Output mix buffer to receive the samples.
|
||||
/// @param req - Information for how to decode.
|
||||
/// @return Number of samples decoded.
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, const DecodeArg& req) {
|
||||
constexpr u32 SamplesPerFrame{14};
|
||||
constexpr u32 NibblesPerFrame{16};
|
||||
|
||||
@@ -115,8 +106,7 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto end{(req.end_offset % SamplesPerFrame) +
|
||||
NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
|
||||
auto end{(req.end_offset % SamplesPerFrame) + NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
|
||||
if (req.end_offset % SamplesPerFrame) {
|
||||
end += 3;
|
||||
} else {
|
||||
@@ -133,52 +123,49 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_read{samples_to_process};
|
||||
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
|
||||
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
|
||||
samples_remaining_in_frame};
|
||||
|
||||
auto samples_to_read = samples_to_process;
|
||||
auto samples_remaining_in_frame = start_pos % SamplesPerFrame;
|
||||
auto position_in_frame = (start_pos / SamplesPerFrame) * NibblesPerFrame + samples_remaining_in_frame;
|
||||
if (samples_remaining_in_frame) {
|
||||
position_in_frame += 2;
|
||||
}
|
||||
|
||||
const auto size{(std::max)((samples_to_process / 8U) * SamplesPerFrame, 8U)};
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(
|
||||
memory, req.buffer + position_in_frame / 2, size);
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(memory, req.buffer + position_in_frame / 2, size);
|
||||
|
||||
auto context{req.adpcm_context};
|
||||
auto header{context->header};
|
||||
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
|
||||
u8 scale{static_cast<u8>(header & 0xFU)};
|
||||
s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
|
||||
s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
|
||||
auto context = req.adpcm_context;
|
||||
auto header = context->header;
|
||||
u8 scale = u8(header & 0xfU);
|
||||
u8 coeff_index = u8((header >> 4U) & 0x7u);
|
||||
s32 coeff0 = req.coefficients[coeff_index * 2 + 0];
|
||||
s32 coeff1 = req.coefficients[coeff_index * 2 + 1];
|
||||
|
||||
auto yn0{context->yn0};
|
||||
auto yn1{context->yn1};
|
||||
|
||||
static constexpr std::array<s32, 16> Steps{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
|
||||
auto yn0 = context->yn0;
|
||||
auto yn1 = context->yn1;
|
||||
auto const get_step = [](u32 index) {
|
||||
// Emulates the following table
|
||||
// 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
|
||||
constexpr u64 steps_table = 0x1234567876543210ull;
|
||||
constexpr u64 steps_sign = 0b1111111100000000ull;
|
||||
auto const r = s32((steps_table >> (index * 4)) & 0xf);
|
||||
return ((steps_sign >> index) & 1) == 0 ? -r : r;
|
||||
};
|
||||
|
||||
const auto decode_sample = [&](const s32 code) -> s16 {
|
||||
auto const decode_sample = [&](const s32 code) -> s16 {
|
||||
const auto xn = code * (1 << scale);
|
||||
const auto prediction = coeff0 * yn0 + coeff1 * yn1;
|
||||
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
|
||||
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
|
||||
yn1 = yn0;
|
||||
yn0 = static_cast<s16>(saturated);
|
||||
return yn0;
|
||||
return yn0 = s16(saturated);
|
||||
};
|
||||
|
||||
u32 read_index{0};
|
||||
u32 write_index{0};
|
||||
|
||||
while (samples_to_read > 0) {
|
||||
u32 read_index = 0;
|
||||
for (u32 write_index = 0; samples_to_read > 0 && write_index < out_buffer.size(); ) {
|
||||
// Are we at a new frame?
|
||||
if ((position_in_frame % NibblesPerFrame) == 0) {
|
||||
header = wavebuffer[read_index++];
|
||||
coeff_index = (header >> 4) & 0xF;
|
||||
scale = header & 0xF;
|
||||
scale = header & 0xFu;
|
||||
coeff_index = (header >> 4) & 0x7u;
|
||||
coeff0 = req.coefficients[coeff_index * 2 + 0];
|
||||
coeff1 = req.coefficients[coeff_index * 2 + 1];
|
||||
position_in_frame += 2;
|
||||
@@ -187,14 +174,12 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
if (samples_to_read >= SamplesPerFrame) {
|
||||
// Can grab all samples until the next header
|
||||
for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
|
||||
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
|
||||
auto code1{Steps[wavebuffer[read_index] & 0xF]};
|
||||
read_index++;
|
||||
|
||||
auto code0 = get_step((wavebuffer[read_index + i] >> 4) & 0xF);
|
||||
auto code1 = get_step(wavebuffer[read_index + i] & 0xF);
|
||||
out_buffer[write_index++] = decode_sample(code0);
|
||||
out_buffer[write_index++] = decode_sample(code1);
|
||||
}
|
||||
|
||||
read_index += SamplesPerFrame / 2;
|
||||
position_in_frame += SamplesPerFrame;
|
||||
samples_to_read -= SamplesPerFrame;
|
||||
continue;
|
||||
@@ -202,15 +187,14 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
}
|
||||
|
||||
// Decode a single sample
|
||||
auto code{wavebuffer[read_index]};
|
||||
auto code = wavebuffer[read_index];
|
||||
if (position_in_frame & 1) {
|
||||
code &= 0xF;
|
||||
read_index++;
|
||||
} else {
|
||||
code >>= 4;
|
||||
}
|
||||
|
||||
out_buffer[write_index++] = decode_sample(Steps[code]);
|
||||
out_buffer[write_index++] = decode_sample(get_step(code));
|
||||
|
||||
position_in_frame++;
|
||||
samples_to_read--;
|
||||
@@ -219,27 +203,21 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
context->header = header;
|
||||
context->yn0 = yn0;
|
||||
context->yn1 = yn1;
|
||||
|
||||
return samples_to_process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode implementation.
|
||||
* Decode wavebuffers according to the given args.
|
||||
*
|
||||
* @param memory - Core memory to read data from.
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
/// @brief Decode implementation.
|
||||
/// Decode wavebuffers according to the given args.
|
||||
///
|
||||
/// @param memory - Core memory to read data from.
|
||||
/// @param args - The wavebuffer data, and information for how to decode it.
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
|
||||
static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
|
||||
auto& played_samples, auto& consumed) -> void {
|
||||
constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, auto& played_samples, auto& consumed) -> void {
|
||||
voice_state.wave_buffer_valid[index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_samples = 0;
|
||||
}
|
||||
|
||||
index = (index + 1) % MaxWaveBuffers;
|
||||
consumed++;
|
||||
};
|
||||
@@ -255,8 +233,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
return;
|
||||
}
|
||||
|
||||
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
|
||||
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
|
||||
// 0 -> 4, 1 -> 8, 2 -> 4
|
||||
auto pitch = u32((0x040804ul >> (u32(args.src_quality) * 8)) & 0xfful);
|
||||
if (u32(pitch + size_required.to_int_floor()) > TempBufferSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,7 +251,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
bool is_buffer_starved{false};
|
||||
u32 offset{voice_state.offset};
|
||||
|
||||
auto output_buffer{args.output};
|
||||
auto output_buffer = args.output;
|
||||
std::array<s16, TempBufferSize> temp_buffer{};
|
||||
|
||||
while (remaining_sample_count > 0) {
|
||||
@@ -294,8 +273,8 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
if (wavebuffer_index >= MaxWaveBuffers) {
|
||||
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
|
||||
wavebuffer_index = 0;
|
||||
voice_state.wave_buffer_valid.fill(false);
|
||||
wavebuffers_consumed = MaxWaveBuffers;
|
||||
voice_state.wave_buffer_valid.fill(false);
|
||||
}
|
||||
|
||||
if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
|
||||
@@ -303,12 +282,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
break;
|
||||
}
|
||||
|
||||
auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
|
||||
|
||||
if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
|
||||
wavebuffer.context != 0) {
|
||||
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
|
||||
wavebuffer.context_size);
|
||||
auto& wavebuffer = args.wave_buffers[wavebuffer_index];
|
||||
if (offset == 0 && args.sample_format == SampleFormat::Adpcm && wavebuffer.context != 0) {
|
||||
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, wavebuffer.context_size);
|
||||
}
|
||||
|
||||
auto start_offset{wavebuffer.start_offset};
|
||||
@@ -351,9 +327,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
case SampleFormat::Adpcm: {
|
||||
decode_arg.adpcm_context = &voice_state.adpcm_context;
|
||||
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
|
||||
samples_decoded = DecodeAdpcm(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
samples_decoded = DecodeAdpcm( memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, decode_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 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -5,21 +8,13 @@
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
|
||||
const Common::FixedPoint<49, 15>& sample_rate_ratio,
|
||||
Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
|
||||
if (sample_rate_ratio == 1.0f) {
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[i];
|
||||
}
|
||||
} else {
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[read_index + (fraction >= 0.5f)];
|
||||
fraction += sample_rate_ratio;
|
||||
read_index += static_cast<u32>(fraction.to_int_floor());
|
||||
fraction.clear_int();
|
||||
}
|
||||
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, const Common::FixedPoint<49, 15>& sample_rate_ratio, Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[read_index + (fraction >= 0.5f)];
|
||||
fraction += sample_rate_ratio;
|
||||
read_index += u32(fraction.to_int_floor());
|
||||
fraction.clear_int();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +290,7 @@ static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> in
|
||||
auto lut{get_lut()};
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
const auto lut_index{(fraction.get_frac() >> 8) * 4};
|
||||
const auto lut_index = ((fraction.get_frac() >> 8) << 2) & 511;
|
||||
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
|
||||
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
|
||||
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
|
||||
@@ -845,7 +840,7 @@ static void ResampleHighQuality(std::span<s32> output, std::span<const s16> inpu
|
||||
auto lut{get_lut()};
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
const auto lut_index{(fraction.get_frac() >> 8) * 8};
|
||||
const auto lut_index = ((fraction.get_frac() >> 8) << 3) & 1023;
|
||||
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
|
||||
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
|
||||
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
|
||||
|
||||
@@ -121,6 +121,8 @@ add_library(
|
||||
swap.h
|
||||
thread.cpp
|
||||
thread.h
|
||||
adpf.cpp
|
||||
adpf.h
|
||||
thread_queue_list.h
|
||||
thread_worker.h
|
||||
threadsafe_queue.h
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/adpf.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::chrono::nanoseconds DEFAULT_TARGET = std::chrono::nanoseconds{16'666'667};
|
||||
|
||||
struct AHintManager;
|
||||
struct AHintSession;
|
||||
|
||||
using PFN_GetManager = AHintManager* (*)();
|
||||
using PFN_CreateSession = AHintSession* (*)(AHintManager*, const s32*, size_t, s64);
|
||||
using PFN_CloseSession = void (*)(AHintSession*);
|
||||
using PFN_UpdateTarget = int (*)(AHintSession*, s64);
|
||||
using PFN_ReportActual = int (*)(AHintSession*, s64);
|
||||
using PFN_SetThreads = int (*)(AHintSession*, const pid_t*, size_t);
|
||||
using PFN_SetPowerEfficiency = int (*)(AHintSession*, bool);
|
||||
|
||||
struct Api {
|
||||
PFN_GetManager get_manager = nullptr;
|
||||
PFN_CreateSession create_session = nullptr;
|
||||
PFN_CloseSession close_session = nullptr;
|
||||
PFN_UpdateTarget update_target = nullptr;
|
||||
PFN_ReportActual report_actual = nullptr;
|
||||
PFN_SetThreads set_threads = nullptr;
|
||||
PFN_SetPowerEfficiency set_power_efficiency = nullptr;
|
||||
AHintManager* manager = nullptr;
|
||||
bool usable = false;
|
||||
};
|
||||
|
||||
const Api& Resolve() {
|
||||
static const Api api = [] {
|
||||
Api resolved;
|
||||
void* library = dlopen("libandroid.so", RTLD_NOW);
|
||||
if (library == nullptr) {
|
||||
LOG_INFO(Common, "libandroid.so unavailable, ADPF is disabled");
|
||||
return resolved;
|
||||
}
|
||||
const auto load = [library](const char* name) { return dlsym(library, name); };
|
||||
|
||||
resolved.get_manager = reinterpret_cast<PFN_GetManager>(load("APerformanceHint_getManager"));
|
||||
resolved.create_session =
|
||||
reinterpret_cast<PFN_CreateSession>(load("APerformanceHint_createSession"));
|
||||
resolved.close_session =
|
||||
reinterpret_cast<PFN_CloseSession>(load("APerformanceHint_closeSession"));
|
||||
resolved.update_target =
|
||||
reinterpret_cast<PFN_UpdateTarget>(load("APerformanceHint_updateTargetWorkDuration"));
|
||||
resolved.report_actual =
|
||||
reinterpret_cast<PFN_ReportActual>(load("APerformanceHint_reportActualWorkDuration"));
|
||||
resolved.set_threads =
|
||||
reinterpret_cast<PFN_SetThreads>(load("APerformanceHint_setThreads"));
|
||||
resolved.set_power_efficiency = reinterpret_cast<PFN_SetPowerEfficiency>(
|
||||
load("APerformanceHint_setPreferPowerEfficiency"));
|
||||
|
||||
if (resolved.get_manager == nullptr || resolved.create_session == nullptr ||
|
||||
resolved.close_session == nullptr || resolved.update_target == nullptr ||
|
||||
resolved.report_actual == nullptr) {
|
||||
LOG_INFO(Common, "Performance hint API not exported, ADPF is disabled");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
resolved.manager = resolved.get_manager();
|
||||
if (resolved.manager == nullptr) {
|
||||
LOG_INFO(Common, "Device does not provide a performance hint manager");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
resolved.usable = true;
|
||||
LOG_INFO(Common, "ADPF available, setThreads {}, power efficiency {}",
|
||||
resolved.set_threads != nullptr ? "yes" : "no",
|
||||
resolved.set_power_efficiency != nullptr ? "yes" : "no");
|
||||
return resolved;
|
||||
}();
|
||||
return api;
|
||||
}
|
||||
|
||||
struct SessionState {
|
||||
AHintSession* handle = nullptr;
|
||||
std::vector<pid_t> threads;
|
||||
bool unsupported = false;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::array<SessionState, 2> g_sessions;
|
||||
|
||||
constexpr s64 MAX_REPORTED_TARGETS = 4;
|
||||
|
||||
std::atomic<s64> g_target_ns{DEFAULT_TARGET.count()};
|
||||
thread_local std::chrono::steady_clock::time_point t_last_frame{};
|
||||
|
||||
SessionState& StateOf(Session session) {
|
||||
return g_sessions[static_cast<size_t>(session)];
|
||||
}
|
||||
|
||||
bool IsBackgroundUsable(const Api& api) {
|
||||
return api.set_power_efficiency != nullptr;
|
||||
}
|
||||
|
||||
void CloseLocked(SessionState& state) {
|
||||
if (state.handle != nullptr) {
|
||||
Resolve().close_session(state.handle);
|
||||
state.handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AHintSession* CreateSessionFor(Session session, const std::vector<pid_t>& threads) {
|
||||
const Api& api = Resolve();
|
||||
const s64 target = session == Session::Render ? g_target_ns.load(std::memory_order_relaxed) : 0;
|
||||
|
||||
std::vector<s32> ids;
|
||||
ids.reserve(threads.size());
|
||||
for (const pid_t tid : threads) {
|
||||
ids.push_back(static_cast<s32>(tid));
|
||||
}
|
||||
|
||||
AHintSession* handle = api.create_session(api.manager, ids.data(), ids.size(), target);
|
||||
if (handle == nullptr && target == 0) {
|
||||
handle = api.create_session(api.manager, ids.data(), ids.size(), DEFAULT_TARGET.count());
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (session == Session::Background && api.set_power_efficiency != nullptr) {
|
||||
api.set_power_efficiency(handle, true);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool SyncLocked(Session session, SessionState& state) {
|
||||
if (state.threads.empty()) {
|
||||
CloseLocked(state);
|
||||
return false;
|
||||
}
|
||||
|
||||
const Api& api = Resolve();
|
||||
if (state.handle != nullptr && api.set_threads != nullptr) {
|
||||
std::vector<pid_t> ids = state.threads;
|
||||
if (api.set_threads(state.handle, ids.data(), ids.size()) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
AHintSession* const replacement = CreateSessionFor(session, state.threads);
|
||||
if (replacement == nullptr) {
|
||||
if (state.handle == nullptr) {
|
||||
state.unsupported = true;
|
||||
}
|
||||
LOG_WARNING(Common, "Could not open a performance hint session for {} threads, falling back",
|
||||
state.threads.size());
|
||||
return false;
|
||||
}
|
||||
CloseLocked(state);
|
||||
state.handle = replacement;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
bool IsSessionSupported(Session session) {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable) {
|
||||
return false;
|
||||
}
|
||||
if (session == Session::Background && !IsBackgroundUsable(api)) {
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
return !StateOf(session).unsupported;
|
||||
}
|
||||
|
||||
bool AddCurrentThread(Session session) {
|
||||
if (!IsSessionSupported(session)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pid_t tid = gettid();
|
||||
std::scoped_lock lock{g_mutex};
|
||||
|
||||
for (size_t i = 0; i < g_sessions.size(); ++i) {
|
||||
SessionState& state = g_sessions[i];
|
||||
if (static_cast<size_t>(session) == i) {
|
||||
continue;
|
||||
}
|
||||
const auto it = std::find(state.threads.begin(), state.threads.end(), tid);
|
||||
if (it != state.threads.end()) {
|
||||
state.threads.erase(it);
|
||||
SyncLocked(static_cast<Session>(i), state);
|
||||
}
|
||||
}
|
||||
|
||||
SessionState& state = StateOf(session);
|
||||
const bool added =
|
||||
std::find(state.threads.begin(), state.threads.end(), tid) == state.threads.end();
|
||||
if (added) {
|
||||
state.threads.push_back(tid);
|
||||
}
|
||||
if (!SyncLocked(session, state)) {
|
||||
if (added) {
|
||||
std::erase(state.threads, tid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveCurrentThread() {
|
||||
if (!Resolve().usable) {
|
||||
return;
|
||||
}
|
||||
const pid_t tid = gettid();
|
||||
std::scoped_lock lock{g_mutex};
|
||||
for (size_t i = 0; i < g_sessions.size(); ++i) {
|
||||
SessionState& state = g_sessions[i];
|
||||
if (std::erase(state.threads, tid) != 0) {
|
||||
SyncLocked(static_cast<Session>(i), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds target) {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable || target.count() <= 0) {
|
||||
return;
|
||||
}
|
||||
if (g_target_ns.exchange(target.count(), std::memory_order_relaxed) == target.count()) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SessionState& state = StateOf(Session::Render);
|
||||
if (state.handle != nullptr) {
|
||||
api.update_target(state.handle, target.count());
|
||||
}
|
||||
}
|
||||
|
||||
void ReportFrameInterval() {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto previous = t_last_frame;
|
||||
t_last_frame = now;
|
||||
if (previous.time_since_epoch().count() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
s64 actual = std::chrono::duration_cast<std::chrono::nanoseconds>(now - previous).count();
|
||||
if (actual <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const s64 ceiling = g_target_ns.load(std::memory_order_relaxed) * MAX_REPORTED_TARGETS;
|
||||
actual = (std::min)(actual, ceiling);
|
||||
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SessionState& state = StateOf(Session::Render);
|
||||
if (state.handle != nullptr) {
|
||||
api.report_actual(state.handle, actual);
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (!Resolve().usable) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
for (SessionState& state : g_sessions) {
|
||||
CloseLocked(state);
|
||||
state.threads.clear();
|
||||
state.unsupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common::ADPF
|
||||
|
||||
#else
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
bool IsSessionSupported(Session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AddCurrentThread(Session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RemoveCurrentThread() {}
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds) {}
|
||||
|
||||
void ReportFrameInterval() {}
|
||||
|
||||
void Shutdown() {}
|
||||
|
||||
} // namespace Common::ADPF
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
enum class Session {
|
||||
Render,
|
||||
Background,
|
||||
};
|
||||
|
||||
bool IsSessionSupported(Session session);
|
||||
|
||||
bool AddCurrentThread(Session session);
|
||||
void RemoveCurrentThread();
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds target);
|
||||
|
||||
void ReportFrameInterval();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
} // namespace Common::ADPF
|
||||
@@ -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
|
||||
|
||||
|
||||
+340
-6
@@ -51,14 +51,45 @@
|
||||
|
||||
#endif // ^^^ POSIX ^^^
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/free_region_manager.h"
|
||||
#include "common/host_memory.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/memory_detect.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <dlfcn.h>
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
namespace {
|
||||
|
||||
struct NativeHandle {
|
||||
int version;
|
||||
int numFds;
|
||||
int numInts;
|
||||
int data[1];
|
||||
};
|
||||
|
||||
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
|
||||
|
||||
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
|
||||
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
|
||||
if (lib == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
|
||||
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__) && __ANDROID_API__ < 30
|
||||
#include <sys/syscall.h>
|
||||
@@ -75,6 +106,12 @@ namespace Common {
|
||||
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
|
||||
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
|
||||
|
||||
static std::atomic<u64> committed_backing_size{};
|
||||
|
||||
u64 GetCommittedBackingSize() noexcept {
|
||||
return committed_backing_size.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Manually imported for MinGW compatibility
|
||||
@@ -123,7 +160,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
|
||||
|
||||
class HostMemory::Impl {
|
||||
public:
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_)
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
|
||||
: backing_size{backing_size_}
|
||||
, virtual_size{virtual_size_}
|
||||
, process{GetCurrentProcess()}
|
||||
@@ -229,6 +266,10 @@ public:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool IsBackingShared() const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t backing_size; ///< Size of the backing memory in bytes
|
||||
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
||||
|
||||
@@ -501,9 +542,10 @@ static int shm_open_anon(int flags, mode_t mode) {
|
||||
|
||||
class HostMemory::Impl {
|
||||
public:
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_)
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
|
||||
: backing_size{backing_size_}
|
||||
, virtual_size{virtual_size_}
|
||||
, preferred_offset{preferred_offset_}
|
||||
{}
|
||||
|
||||
bool Init() {
|
||||
@@ -543,10 +585,15 @@ public:
|
||||
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
|
||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
||||
if (fd > 0) {
|
||||
fd = -1;
|
||||
close(fd);
|
||||
}
|
||||
fd = -1;
|
||||
} else {
|
||||
#ifdef __ANDROID__
|
||||
if (InitAhbBacking()) {
|
||||
return InitVirtual();
|
||||
}
|
||||
#endif
|
||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
||||
}
|
||||
if (backing_base == MAP_FAILED) {
|
||||
@@ -554,7 +601,10 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Virtual memory initialization
|
||||
return InitVirtual();
|
||||
}
|
||||
|
||||
bool InitVirtual() {
|
||||
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
|
||||
if (virtual_base == MAP_FAILED) {
|
||||
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
||||
@@ -567,6 +617,222 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
|
||||
return AHardwareBuffer_Desc{
|
||||
.width = static_cast<u32>(len),
|
||||
.height = 1,
|
||||
.layers = 1,
|
||||
.format = AHARDWAREBUFFER_FORMAT_BLOB,
|
||||
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
|
||||
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
|
||||
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
|
||||
.stride = 0,
|
||||
.rfu0 = 0,
|
||||
.rfu1 = 0,
|
||||
};
|
||||
}
|
||||
|
||||
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
|
||||
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
|
||||
AHardwareBuffer* buffer{};
|
||||
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const NativeHandle* const handle = get_native_handle(buffer);
|
||||
if (handle == nullptr || handle->numFds < 1) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
return false;
|
||||
}
|
||||
const int probe_fd = handle->data[0];
|
||||
bool ok = true;
|
||||
const auto try_map = [&](int prot, off_t offset, const char* what) {
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
|
||||
if (ptr == MAP_FAILED) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
munmap(ptr, PageAlignment);
|
||||
};
|
||||
try_map(PROT_READ | PROT_WRITE, 0, "shared mappings");
|
||||
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment), "mappings at an offset");
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
try_map(PROT_READ | PROT_EXEC, 0, "executable mappings");
|
||||
#endif
|
||||
AHardwareBuffer_release(buffer);
|
||||
return ok;
|
||||
}
|
||||
|
||||
size_t ComputeAhbBudget(size_t window_size) const {
|
||||
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
|
||||
if (total_physical == 0) {
|
||||
return 0;
|
||||
}
|
||||
constexpr u64 MinimumTotalPhysical = 7ULL << 30;
|
||||
if (total_physical < MinimumTotalPhysical) {
|
||||
return 0;
|
||||
}
|
||||
const u64 max_map_count = Common::GetMaxMapCount();
|
||||
constexpr u64 ReservedMaps = 24576;
|
||||
if (max_map_count == 0 || max_map_count <= ReservedMaps) {
|
||||
return 0;
|
||||
}
|
||||
u64 budget = total_physical / 6;
|
||||
budget = (std::min)(budget, (max_map_count - ReservedMaps) * PageAlignment);
|
||||
const u64 available = Common::GetAvailablePhysicalMemory();
|
||||
if (available != 0) {
|
||||
constexpr u64 Headroom = 2ULL << 30;
|
||||
budget = (std::min)(budget, available > Headroom ? available - Headroom : 0);
|
||||
}
|
||||
budget = (std::min)(budget, static_cast<u64>(backing_size));
|
||||
budget = Common::AlignDown(budget, window_size);
|
||||
constexpr u64 MinimumBudget = 256ULL << 20;
|
||||
if (budget < MinimumBudget) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<size_t>(budget);
|
||||
}
|
||||
|
||||
bool InitAhbBacking() {
|
||||
if (!Settings::values.use_unified_memory.GetValue()) {
|
||||
return false;
|
||||
}
|
||||
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
|
||||
ResolveGetNativeHandle();
|
||||
if (get_native_handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
constexpr size_t window_size = 512ULL << 20;
|
||||
const AHardwareBuffer_Desc window_desc = MakeBlobDesc(window_size);
|
||||
if (AHardwareBuffer_isSupported(&window_desc) == 0) {
|
||||
return false;
|
||||
}
|
||||
const size_t budget = ComputeAhbBudget(window_size);
|
||||
if (budget == 0) {
|
||||
return false;
|
||||
}
|
||||
if (!ProbeAhbBacking(get_native_handle)) {
|
||||
return false;
|
||||
}
|
||||
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
|
||||
const size_t region_size = (std::min)(budget, aligned_backing);
|
||||
const size_t region_base = Common::AlignDown(
|
||||
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
|
||||
const size_t num_windows = region_size / window_size;
|
||||
|
||||
std::vector<AHardwareBuffer*> buffers;
|
||||
std::vector<int> buffer_fds;
|
||||
const auto cleanup = [&] {
|
||||
for (AHardwareBuffer* buffer : buffers) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
}
|
||||
buffers.clear();
|
||||
buffer_fds.clear();
|
||||
};
|
||||
for (size_t i = 0; i < num_windows; ++i) {
|
||||
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
|
||||
AHardwareBuffer* buffer{};
|
||||
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
buffers.push_back(buffer);
|
||||
const NativeHandle* const handle = get_native_handle(buffer);
|
||||
if (handle == nullptr || handle->numFds < 1) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
const int buffer_fd = handle->data[0];
|
||||
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
|
||||
if (buffer_len < static_cast<off_t>(window_size)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
buffer_fds.push_back(buffer_fd);
|
||||
}
|
||||
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
|
||||
if (base == MAP_FAILED) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
|
||||
off_t map_offset) {
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
|
||||
map_offset) == MAP_FAILED) {
|
||||
munmap(base, backing_size);
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!map_over_reservation(0, region_base, fd, 0)) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < num_windows; ++i) {
|
||||
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
|
||||
0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const size_t tail_offset = region_base + region_size;
|
||||
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
|
||||
static_cast<off_t>(tail_offset))) {
|
||||
return false;
|
||||
}
|
||||
backing_base = base;
|
||||
ahb_windows = std::move(buffers);
|
||||
ahb_fds = std::move(buffer_fds);
|
||||
ahb_window_size = window_size;
|
||||
ahb_base = region_base;
|
||||
ahb_bytes = region_size;
|
||||
committed_backing_size.store(region_size, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
|
||||
while (length > 0) {
|
||||
int map_fd = fd;
|
||||
off_t map_offset = static_cast<off_t>(host_offset);
|
||||
size_t chunk = length;
|
||||
if (host_offset < ahb_base) {
|
||||
chunk = (std::min)(chunk, ahb_base - host_offset);
|
||||
} else if (host_offset < ahb_base + ahb_bytes) {
|
||||
const size_t relative = host_offset - ahb_base;
|
||||
const size_t window = relative / ahb_window_size;
|
||||
const size_t local = relative % ahb_window_size;
|
||||
map_fd = ahb_fds[window];
|
||||
map_offset = static_cast<off_t>(local);
|
||||
chunk = (std::min)(chunk, ahb_window_size - local);
|
||||
}
|
||||
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
|
||||
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
|
||||
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
|
||||
virtual_offset += chunk;
|
||||
host_offset += chunk;
|
||||
length -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
|
||||
return ahb_windows;
|
||||
}
|
||||
|
||||
size_t AhbWindowSize() const noexcept {
|
||||
return ahb_bytes != 0 ? ahb_window_size : 0;
|
||||
}
|
||||
|
||||
size_t AhbBase() const noexcept {
|
||||
return ahb_base;
|
||||
}
|
||||
#endif
|
||||
|
||||
~Impl() {
|
||||
Release();
|
||||
}
|
||||
@@ -587,6 +853,12 @@ public:
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
if (True(perms & MemoryPermission::Execute))
|
||||
prot_flags |= PROT_EXEC;
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
if (ahb_bytes != 0) {
|
||||
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
|
||||
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
|
||||
@@ -632,8 +904,18 @@ public:
|
||||
virtual_base = nullptr;
|
||||
}
|
||||
|
||||
bool IsBackingShared() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
if (ahb_bytes != 0) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return fd >= 0;
|
||||
}
|
||||
|
||||
const size_t backing_size; ///< Size of the backing memory in bytes
|
||||
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
||||
const size_t preferred_offset;
|
||||
|
||||
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
||||
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
||||
@@ -656,6 +938,18 @@ private:
|
||||
int ret = close(fd);
|
||||
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
for (AHardwareBuffer* buffer : ahb_windows) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
}
|
||||
ahb_windows.clear();
|
||||
ahb_fds.clear();
|
||||
if (ahb_bytes != 0) {
|
||||
committed_backing_size.store(0, std::memory_order_relaxed);
|
||||
ahb_bytes = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AdjustMap(size_t* virtual_offset, size_t* length) {
|
||||
@@ -681,11 +975,19 @@ private:
|
||||
|
||||
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
|
||||
FreeRegionManager free_manager{};
|
||||
|
||||
#ifdef __ANDROID__
|
||||
std::vector<AHardwareBuffer*> ahb_windows;
|
||||
std::vector<int> ahb_fds;
|
||||
size_t ahb_window_size{};
|
||||
size_t ahb_base{};
|
||||
size_t ahb_bytes{};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // ^^^ POSIX ^^^
|
||||
|
||||
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
||||
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
|
||||
: backing_size(backing_size_)
|
||||
, virtual_size(virtual_size_)
|
||||
{
|
||||
@@ -697,7 +999,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
||||
#else
|
||||
// Try to allocate a fastmem arena.
|
||||
// The implementation will fail with std::bad_alloc on errors.
|
||||
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
|
||||
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
|
||||
if (impl->Init()) {
|
||||
backing_base = impl->backing_base;
|
||||
virtual_base = impl->virtual_base;
|
||||
@@ -767,6 +1069,38 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
|
||||
std::memset(backing_base + physical_offset, fill_value, length);
|
||||
}
|
||||
|
||||
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbWindowSize() : 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool HostMemory::IsBackingShared() const noexcept {
|
||||
#if defined(__OPENORBIS__) || defined(__managarm__)
|
||||
return false;
|
||||
#else
|
||||
return impl && impl->IsBackingShared();
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbBase() : 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void HostMemory::EnableDirectMappedAddress() {
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||
if (impl) {
|
||||
|
||||
@@ -8,12 +8,17 @@
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/virtual_buffer.h"
|
||||
|
||||
struct AHardwareBuffer;
|
||||
|
||||
namespace Common {
|
||||
|
||||
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
|
||||
|
||||
enum class MemoryPermission : u32 {
|
||||
Read = 1 << 0,
|
||||
Write = 1 << 1,
|
||||
@@ -28,7 +33,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
|
||||
*/
|
||||
class HostMemory {
|
||||
public:
|
||||
explicit HostMemory(size_t backing_size_, size_t virtual_size_);
|
||||
explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
|
||||
~HostMemory();
|
||||
|
||||
/**
|
||||
@@ -62,6 +67,18 @@ public:
|
||||
return backing_base;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t BackingSize() const noexcept {
|
||||
return backing_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
|
||||
|
||||
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
|
||||
|
||||
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
|
||||
|
||||
[[nodiscard]] bool IsBackingShared() const noexcept;
|
||||
|
||||
[[nodiscard]] u8* VirtualBasePointer() noexcept {
|
||||
return virtual_base;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/memory_detect.h"
|
||||
|
||||
namespace Common {
|
||||
@@ -69,4 +73,55 @@ const MemoryInfo& GetMemInfo() {
|
||||
return mem_info;
|
||||
}
|
||||
|
||||
u64 GetAvailablePhysicalMemory() {
|
||||
#ifdef _WIN32
|
||||
MEMORYSTATUSEX memorystatus;
|
||||
memorystatus.dwLength = sizeof(memorystatus);
|
||||
if (GlobalMemoryStatusEx(&memorystatus)) {
|
||||
return memorystatus.ullAvailPhys;
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__linux__)
|
||||
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
|
||||
char line[256];
|
||||
u64 available = 0;
|
||||
while (std::fgets(line, sizeof(line), file) != nullptr) {
|
||||
if (std::strncmp(line, "MemAvailable:", 13) == 0) {
|
||||
available = std::strtoull(line + 13, nullptr, 10) * 1024ULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::fclose(file);
|
||||
if (available != 0) {
|
||||
return available;
|
||||
}
|
||||
}
|
||||
struct sysinfo info;
|
||||
if (sysinfo(&info) == 0) {
|
||||
const u64 unit = info.mem_unit != 0 ? info.mem_unit : 1ULL;
|
||||
return (static_cast<u64>(info.freeram) + static_cast<u64>(info.bufferram)) * unit;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 GetMaxMapCount() {
|
||||
#ifdef __linux__
|
||||
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
|
||||
char line[32];
|
||||
u64 count = 0;
|
||||
if (std::fgets(line, sizeof(line), file) != nullptr) {
|
||||
count = std::strtoull(line, nullptr, 10);
|
||||
}
|
||||
std::fclose(file);
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -18,4 +18,8 @@ struct MemoryInfo {
|
||||
*/
|
||||
[[nodiscard]] const MemoryInfo& GetMemInfo();
|
||||
|
||||
[[nodiscard]] u64 GetAvailablePhysicalMemory();
|
||||
|
||||
[[nodiscard]] u64 GetMaxMapCount();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+13
-10
@@ -260,10 +260,10 @@ struct Values {
|
||||
Category::Cpu};
|
||||
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
|
||||
"cpu_accuracy", Category::Cpu};
|
||||
SwitchableSetting<CpuClock> fast_cpu_time{linkage,
|
||||
CpuClock::Off,
|
||||
SwitchableSetting<CpuClock> cpu_clock{linkage,
|
||||
CpuClock::Normal,
|
||||
"fast_cpu_time",
|
||||
Category::Cpu,
|
||||
Category::System,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
@@ -540,13 +540,13 @@ struct Values {
|
||||
#endif
|
||||
|
||||
// Renderer Hacks //
|
||||
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
||||
GpuOverclock::Medium,
|
||||
"fast_gpu_time",
|
||||
Category::RendererHacks,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
SwitchableSetting<GpuClock> gpu_clock{linkage,
|
||||
GpuClock::Boost,
|
||||
"fast_gpu_time",
|
||||
Category::System,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
|
||||
SwitchableSetting<bool> skip_cpu_inner_invalidation{linkage,
|
||||
false,
|
||||
@@ -587,6 +587,9 @@ struct Values {
|
||||
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
|
||||
GpuUnswizzleSize::Large,
|
||||
"gpu_unswizzle_texture_size",
|
||||
|
||||
@@ -140,7 +140,7 @@ ENUM(DmaAccuracy, Default, Unsafe, Safe);
|
||||
ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict);
|
||||
ENUM(CpuBackend, Dynarmic, Nce);
|
||||
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging);
|
||||
ENUM(CpuClock, Off, Boost, Fast)
|
||||
ENUM(CpuClock, Normal, Boost, Overclock)
|
||||
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb);
|
||||
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
|
||||
ENUM(FullscreenMode, Borderless, Exclusive);
|
||||
@@ -152,7 +152,7 @@ ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
|
||||
ENUM(ConsoleMode, Handheld, Docked);
|
||||
ENUM(AppletMode, HLE, LLE);
|
||||
ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
|
||||
ENUM(GpuOverclock, Normal, Medium, High)
|
||||
ENUM(GpuClock, Normal, Boost, Overclock)
|
||||
ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
|
||||
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
|
||||
+508
-36
@@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/error.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/assert.h"
|
||||
@@ -39,6 +41,402 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/resource.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
constexpr int NICE_AUDIO = -16;
|
||||
constexpr int NICE_URGENT_DISPLAY = -8;
|
||||
constexpr int NICE_DISPLAY = -4;
|
||||
constexpr int NICE_DEFAULT = 0;
|
||||
constexpr int NICE_BACKGROUND = 10;
|
||||
|
||||
int LowestAllowedNice() {
|
||||
static const int lowest = [] {
|
||||
rlimit limit{};
|
||||
if (getrlimit(RLIMIT_NICE, &limit) != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (limit.rlim_cur >= 40) {
|
||||
return -20;
|
||||
}
|
||||
return 20 - static_cast<int>(limit.rlim_cur);
|
||||
}();
|
||||
return lowest;
|
||||
}
|
||||
|
||||
int NiceValueForPriority(Common::ThreadPriority priority) {
|
||||
const int wanted = [priority] {
|
||||
switch (priority) {
|
||||
case Common::ThreadPriority::Low: return NICE_BACKGROUND;
|
||||
case Common::ThreadPriority::Normal: return NICE_DEFAULT;
|
||||
case Common::ThreadPriority::High: return NICE_DISPLAY;
|
||||
case Common::ThreadPriority::VeryHigh: return NICE_URGENT_DISPLAY;
|
||||
case Common::ThreadPriority::Critical: return NICE_AUDIO;
|
||||
default: return NICE_DEFAULT;
|
||||
}
|
||||
}();
|
||||
return (std::max)(wanted, (std::min)(NICE_DEFAULT, LowestAllowedNice()));
|
||||
}
|
||||
} // Anonymous namespace
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <sys/utsname.h>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
|
||||
|
||||
// A core counts as a performance core while it is within this much of the fastest one.
|
||||
constexpr s64 ANDROID_PERFORMANCE_CAPACITY_PERCENT = 50;
|
||||
|
||||
constexpr std::chrono::nanoseconds ANDROID_POLICY_POLL_INTERVAL = std::chrono::milliseconds{500};
|
||||
|
||||
enum class CoreGroup {
|
||||
Unrestricted,
|
||||
Performance,
|
||||
Efficiency,
|
||||
};
|
||||
|
||||
struct ThreadPolicy {
|
||||
pid_t tid;
|
||||
CoreGroup group;
|
||||
s32 nice_value;
|
||||
bool has_nice;
|
||||
};
|
||||
|
||||
struct CoreInfo {
|
||||
s64 weight;
|
||||
u64 midr;
|
||||
s32 cpu;
|
||||
};
|
||||
|
||||
struct CpuTopologyState {
|
||||
std::mutex topology_mutex;
|
||||
cpu_set_t allowed{};
|
||||
cpu_set_t performance{};
|
||||
cpu_set_t efficiency{};
|
||||
bool separated = false;
|
||||
bool initialized = false;
|
||||
|
||||
pid_t canary_tid = 0;
|
||||
cpu_set_t canary_mask{};
|
||||
bool canary_valid = false;
|
||||
|
||||
std::atomic<s64> next_poll_ns{0};
|
||||
|
||||
std::mutex policy_mutex;
|
||||
std::vector<ThreadPolicy> policies;
|
||||
};
|
||||
|
||||
CpuTopologyState& State() {
|
||||
static CpuTopologyState* const state = new CpuTopologyState();
|
||||
return *state;
|
||||
}
|
||||
|
||||
struct PolicyRegistration {
|
||||
~PolicyRegistration() {
|
||||
const pid_t tid = gettid();
|
||||
::Common::ADPF::RemoveCurrentThread();
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock lock{state.policy_mutex};
|
||||
std::erase_if(state.policies,
|
||||
[tid](const ThreadPolicy& policy) { return policy.tid == tid; });
|
||||
}
|
||||
};
|
||||
|
||||
thread_local PolicyRegistration t_policy_registration;
|
||||
|
||||
s32 PossibleCpuCount() {
|
||||
std::ifstream file("/sys/devices/system/cpu/possible");
|
||||
std::string list;
|
||||
if (file && std::getline(file, list) && !list.empty()) {
|
||||
s64 highest = -1;
|
||||
const char* cursor = list.c_str();
|
||||
while (*cursor != '\0') {
|
||||
char* end = nullptr;
|
||||
const s64 value = std::strtol(cursor, &end, 10);
|
||||
if (end == cursor) {
|
||||
break;
|
||||
}
|
||||
highest = (std::max)(highest, value);
|
||||
cursor = end;
|
||||
while (*cursor == '-' || *cursor == ',') {
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
if (highest >= 0) {
|
||||
return static_cast<s32>((std::min<s64>)(highest + 1, CPU_SETSIZE));
|
||||
}
|
||||
}
|
||||
const s64 configured = sysconf(_SC_NPROCESSORS_CONF);
|
||||
if (configured > 0) {
|
||||
return static_cast<s32>((std::min<s64>)(configured, CPU_SETSIZE));
|
||||
}
|
||||
return static_cast<s32>((std::min<s64>)(std::thread::hardware_concurrency(), CPU_SETSIZE));
|
||||
}
|
||||
|
||||
s64 ReadCpuScalar(s32 cpu, const char* node) {
|
||||
s64 value = 0;
|
||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node);
|
||||
if (!file || !(file >> value) || value <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
u64 ReadCpuMidr(s32 cpu) {
|
||||
u64 midr = 0;
|
||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) +
|
||||
"/regs/identification/midr_el1");
|
||||
if (!file || !(file >> std::hex >> midr)) {
|
||||
return 0;
|
||||
}
|
||||
return midr;
|
||||
}
|
||||
|
||||
std::vector<CoreInfo> CollectCores(const cpu_set_t& allowed, s32 total, const char* node,
|
||||
bool require_all) {
|
||||
std::vector<CoreInfo> cores;
|
||||
for (s32 cpu = 0; cpu < total; ++cpu) {
|
||||
if (!CPU_ISSET(cpu, &allowed)) {
|
||||
continue;
|
||||
}
|
||||
const s64 weight = ReadCpuScalar(cpu, node);
|
||||
if (weight <= 0) {
|
||||
if (require_all) {
|
||||
return {};
|
||||
}
|
||||
LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core",
|
||||
node, cpu);
|
||||
continue;
|
||||
}
|
||||
cores.push_back(CoreInfo{weight, ReadCpuMidr(cpu), cpu});
|
||||
}
|
||||
return cores;
|
||||
}
|
||||
|
||||
bool WeightsAreUniform(const std::vector<CoreInfo>& cores) {
|
||||
return std::all_of(cores.begin(), cores.end(),
|
||||
[&](const CoreInfo& core) { return core.weight == cores.front().weight; });
|
||||
}
|
||||
|
||||
bool MidrsAreDistinct(const std::vector<CoreInfo>& cores) {
|
||||
return std::none_of(cores.begin(), cores.end(),
|
||||
[](const CoreInfo& core) { return core.midr == 0; }) &&
|
||||
std::any_of(cores.begin(), cores.end(),
|
||||
[&](const CoreInfo& core) { return core.midr != cores.front().midr; });
|
||||
}
|
||||
|
||||
void ComputeTopologyLocked(CpuTopologyState& state) {
|
||||
state.initialized = true;
|
||||
state.separated = false;
|
||||
CPU_ZERO(&state.allowed);
|
||||
CPU_ZERO(&state.performance);
|
||||
CPU_ZERO(&state.efficiency);
|
||||
|
||||
if (sched_getaffinity(getpid(), sizeof(state.allowed), &state.allowed) != 0) {
|
||||
LOG_WARNING(Common, "Could not query process CPU affinity: {}",
|
||||
::Common::GetLastErrorMsg());
|
||||
return;
|
||||
}
|
||||
|
||||
const s32 total = PossibleCpuCount();
|
||||
auto cores = CollectCores(state.allowed, total, "cpu_capacity", true);
|
||||
if (cores.empty() || (WeightsAreUniform(cores) && MidrsAreDistinct(cores))) {
|
||||
auto by_frequency = CollectCores(state.allowed, total, "cpufreq/cpuinfo_max_freq", false);
|
||||
if (!by_frequency.empty()) {
|
||||
cores = std::move(by_frequency);
|
||||
}
|
||||
}
|
||||
if (cores.empty()) {
|
||||
LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (WeightsAreUniform(cores)) {
|
||||
if (MidrsAreDistinct(cores)) {
|
||||
LOG_WARNING(Common, "CPU clusters differ but rank identically, thread placement is "
|
||||
"disabled");
|
||||
} else {
|
||||
LOG_INFO(Common, "CPU cores are symmetric, thread placement is disabled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(cores.begin(), cores.end(), [](const CoreInfo& lhs, const CoreInfo& rhs) {
|
||||
if (lhs.weight != rhs.weight) {
|
||||
return lhs.weight > rhs.weight;
|
||||
}
|
||||
return lhs.cpu < rhs.cpu;
|
||||
});
|
||||
|
||||
const s64 fastest = cores.front().weight;
|
||||
size_t taken = 0;
|
||||
for (const auto& core : cores) {
|
||||
const bool fast_enough =
|
||||
core.weight * 100 >= fastest * ANDROID_PERFORMANCE_CAPACITY_PERCENT;
|
||||
if (!fast_enough && taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
|
||||
break;
|
||||
}
|
||||
CPU_SET(core.cpu, &state.performance);
|
||||
++taken;
|
||||
}
|
||||
if (taken == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (s32 cpu = 0; cpu < total; ++cpu) {
|
||||
if (CPU_ISSET(cpu, &state.allowed) && !CPU_ISSET(cpu, &state.performance)) {
|
||||
CPU_SET(cpu, &state.efficiency);
|
||||
}
|
||||
}
|
||||
|
||||
state.separated = CPU_COUNT(&state.efficiency) > 0;
|
||||
LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}",
|
||||
CPU_COUNT(&state.performance), CPU_COUNT(&state.efficiency),
|
||||
state.separated ? "enabled" : "unavailable");
|
||||
}
|
||||
|
||||
void EnsureTopologyLocked(CpuTopologyState& state) {
|
||||
if (!state.initialized) {
|
||||
ComputeTopologyLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshTopologyLocked(CpuTopologyState& state) {
|
||||
if (!state.initialized) {
|
||||
ComputeTopologyLocked(state);
|
||||
return;
|
||||
}
|
||||
cpu_set_t current;
|
||||
CPU_ZERO(¤t);
|
||||
if (sched_getaffinity(getpid(), sizeof(current), ¤t) != 0) {
|
||||
return;
|
||||
}
|
||||
if (std::memcmp(¤t, &state.allowed, sizeof(current)) != 0) {
|
||||
ComputeTopologyLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
bool ApplyCoreGroupLocked(CpuTopologyState& state, pid_t tid, CoreGroup group,
|
||||
bool* gone = nullptr) {
|
||||
const bool restrict_group = group != CoreGroup::Unrestricted && state.separated;
|
||||
const cpu_set_t* mask = &state.allowed;
|
||||
if (restrict_group) {
|
||||
mask = group == CoreGroup::Performance ? &state.performance : &state.efficiency;
|
||||
}
|
||||
if (CPU_COUNT(mask) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (sched_setaffinity(tid, sizeof(*mask), mask) != 0) {
|
||||
if (gone != nullptr && errno == ESRCH) {
|
||||
*gone = true;
|
||||
return false;
|
||||
}
|
||||
LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid,
|
||||
::Common::GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelPreservesRequestedAffinity() {
|
||||
utsname info{};
|
||||
if (uname(&info) != 0) {
|
||||
return false;
|
||||
}
|
||||
s32 major = 0;
|
||||
s32 minor = 0;
|
||||
if (std::sscanf(info.release, "%d.%d", &major, &minor) != 2) {
|
||||
return false;
|
||||
}
|
||||
return major > 6 || (major == 6 && minor >= 2);
|
||||
}
|
||||
|
||||
void SnapshotCanaryLocked(CpuTopologyState& state) {
|
||||
state.canary_valid = false;
|
||||
if (!state.separated) {
|
||||
return;
|
||||
}
|
||||
for (const auto& policy : state.policies) {
|
||||
if (policy.group == CoreGroup::Unrestricted) {
|
||||
continue;
|
||||
}
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO(&mask);
|
||||
if (sched_getaffinity(policy.tid, sizeof(mask), &mask) != 0) {
|
||||
continue;
|
||||
}
|
||||
state.canary_tid = policy.tid;
|
||||
state.canary_mask = mask;
|
||||
state.canary_valid = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool DueForPoll(CpuTopologyState& state) {
|
||||
const auto now = std::chrono::steady_clock::now().time_since_epoch();
|
||||
const s64 now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
|
||||
s64 next = state.next_poll_ns.load(std::memory_order_relaxed);
|
||||
if (now_ns < next) {
|
||||
return false;
|
||||
}
|
||||
return state.next_poll_ns.compare_exchange_strong(
|
||||
next, now_ns + ANDROID_POLICY_POLL_INTERVAL.count(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
ThreadPolicy& AcquirePolicyLocked(CpuTopologyState& state, pid_t tid) {
|
||||
for (auto& policy : state.policies) {
|
||||
if (policy.tid == tid) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
return state.policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false});
|
||||
}
|
||||
|
||||
void SetCurrentThreadCoreGroup(CoreGroup group) {
|
||||
const pid_t tid = gettid();
|
||||
(void)&t_policy_registration;
|
||||
|
||||
CoreGroup effective = group;
|
||||
if (tid == getpid() && group != CoreGroup::Unrestricted) {
|
||||
LOG_WARNING(Common, "Refusing to place the main thread: the CPU topology is read from it");
|
||||
effective = CoreGroup::Unrestricted;
|
||||
}
|
||||
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock topology_lock{state.topology_mutex};
|
||||
EnsureTopologyLocked(state);
|
||||
ApplyCoreGroupLocked(state, tid, effective);
|
||||
|
||||
std::scoped_lock policy_lock{state.policy_mutex};
|
||||
AcquirePolicyLocked(state, tid).group = effective;
|
||||
if (!state.canary_valid) {
|
||||
SnapshotCanaryLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
void RememberCurrentThreadNice(pid_t tid, s32 nice_value) {
|
||||
(void)&t_policy_registration;
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock lock{state.policy_mutex};
|
||||
ThreadPolicy& policy = AcquirePolicyLocked(state, tid);
|
||||
policy.nice_value = nice_value;
|
||||
policy.has_nice = true;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
#endif
|
||||
|
||||
#include "common/cpu_features.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#ifdef _MSC_VER
|
||||
@@ -48,7 +446,6 @@
|
||||
#endif
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -78,21 +475,31 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
}
|
||||
}();
|
||||
set_thread_priority(find_thread(NULL), priority);
|
||||
#else
|
||||
pthread_t this_thread = pthread_self();
|
||||
const auto scheduling_type = SCHED_OTHER;
|
||||
s32 max_prio = sched_get_priority_max(scheduling_type);
|
||||
s32 min_prio = sched_get_priority_min(scheduling_type);
|
||||
u32 level = (std::max)(u32(new_priority) + 1, 4U);
|
||||
|
||||
struct sched_param params;
|
||||
if (max_prio > min_prio) {
|
||||
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
|
||||
} else {
|
||||
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
|
||||
#elif defined(__ANDROID__)
|
||||
const int nice_value = NiceValueForPriority(new_priority);
|
||||
const pid_t tid = gettid();
|
||||
if (setpriority(PRIO_PROCESS, static_cast<id_t>(tid), nice_value) != 0) {
|
||||
LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
return;
|
||||
}
|
||||
RememberCurrentThreadNice(tid, nice_value);
|
||||
#elif defined(__linux__)
|
||||
const int nice_value = NiceValueForPriority(new_priority);
|
||||
if (setpriority(PRIO_PROCESS, 0, nice_value) != 0) {
|
||||
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
}
|
||||
#else
|
||||
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
|
||||
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
|
||||
if (max_prio > min_prio) {
|
||||
const u32 level = (std::min)(static_cast<u32>(new_priority), 4U);
|
||||
sched_param params{};
|
||||
params.sched_priority =
|
||||
min_prio + static_cast<s32>(static_cast<u32>(max_prio - min_prio) * level) / 4;
|
||||
pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms);
|
||||
}
|
||||
|
||||
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -132,29 +539,94 @@ void SetCurrentThreadName(const char* name) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id) {
|
||||
ASSERT(core_id < 4);
|
||||
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
|
||||
// run in ANY processor!
|
||||
auto const total_cores = std::thread::hardware_concurrency();
|
||||
if (core_id < total_cores) {
|
||||
void SetCurrentThreadToPerformanceCores() {
|
||||
#if defined(__ANDROID__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
sched_setaffinity(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(_WIN32)
|
||||
DWORD set = 1UL << core_id;
|
||||
SetThreadAffinityMask(GetCurrentThread(), set);
|
||||
#else
|
||||
// No pin functionality implemented
|
||||
#endif
|
||||
if (ADPF::AddCurrentThread(ADPF::Session::Render)) {
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
return;
|
||||
}
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Performance);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToEfficiencyCores() {
|
||||
#if defined(__ANDROID__)
|
||||
if (ADPF::AddCurrentThread(ADPF::Session::Background)) {
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
return;
|
||||
}
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Efficiency);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToBackgroundWork() {
|
||||
#if defined(__ANDROID__)
|
||||
ADPF::AddCurrentThread(ADPF::Session::Background);
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToAllCores() {
|
||||
#if defined(__ANDROID__)
|
||||
ADPF::RemoveCurrentThread();
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RefreshThreadPolicies() {
|
||||
#if defined(__ANDROID__)
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock topology_lock{state.topology_mutex};
|
||||
RefreshTopologyLocked(state);
|
||||
|
||||
std::scoped_lock policy_lock{state.policy_mutex};
|
||||
std::erase_if(state.policies, [&state](const ThreadPolicy& policy) {
|
||||
bool gone = false;
|
||||
if (policy.has_nice &&
|
||||
setpriority(PRIO_PROCESS, static_cast<id_t>(policy.tid), policy.nice_value) != 0 &&
|
||||
errno == ESRCH) {
|
||||
gone = true;
|
||||
}
|
||||
if (!gone) {
|
||||
ApplyCoreGroupLocked(state, policy.tid, policy.group, &gone);
|
||||
}
|
||||
return gone;
|
||||
});
|
||||
SnapshotCanaryLocked(state);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PollThreadPolicies() {
|
||||
#if defined(__ANDROID__)
|
||||
static const bool needed = !KernelPreservesRequestedAffinity();
|
||||
if (!needed) {
|
||||
return;
|
||||
}
|
||||
|
||||
CpuTopologyState& state = State();
|
||||
if (!DueForPoll(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pid_t tid;
|
||||
cpu_set_t expected;
|
||||
{
|
||||
std::scoped_lock lock{state.topology_mutex};
|
||||
if (!state.canary_valid) {
|
||||
return;
|
||||
}
|
||||
tid = state.canary_tid;
|
||||
expected = state.canary_mask;
|
||||
}
|
||||
|
||||
cpu_set_t current;
|
||||
CPU_ZERO(¤t);
|
||||
if (sched_getaffinity(tid, sizeof(current), ¤t) == 0 &&
|
||||
std::memcmp(¤t, &expected, sizeof(current)) == 0) {
|
||||
return;
|
||||
}
|
||||
RefreshThreadPolicies();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
+13
-1
@@ -99,8 +99,20 @@ enum class ThreadPriority : u32 {
|
||||
Critical = 4,
|
||||
};
|
||||
|
||||
enum class ThreadPlacement : u32 {
|
||||
Default = 0,
|
||||
Background = 1,
|
||||
Efficiency = 2,
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
void SetCurrentThreadName(const char* name);
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||
void SetCurrentThreadToPerformanceCores();
|
||||
void SetCurrentThreadToEfficiencyCores();
|
||||
void SetCurrentThreadToBackgroundWork();
|
||||
void SetCurrentThreadToAllCores();
|
||||
|
||||
void RefreshThreadPolicies();
|
||||
void PollThreadPolicies();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -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 2020 yuzu Emulator Project
|
||||
@@ -37,10 +37,25 @@ class StatefulThreadWorker {
|
||||
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
||||
|
||||
public:
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {},
|
||||
ThreadPlacement placement = ThreadPlacement::Default)
|
||||
: workers_queued{num_workers}, thread_name{std::move(name)} {
|
||||
const auto lambda = [this, func](std::stop_token stop_token) {
|
||||
const auto lambda = [this, func, placement](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName(thread_name.c_str());
|
||||
if (placement != ThreadPlacement::Default) {
|
||||
Common::SetCurrentThreadPriority(ThreadPriority::Low);
|
||||
}
|
||||
switch (placement) {
|
||||
case ThreadPlacement::Efficiency:
|
||||
Common::SetCurrentThreadToEfficiencyCores();
|
||||
break;
|
||||
case ThreadPlacement::Background:
|
||||
Common::SetCurrentThreadToBackgroundWork();
|
||||
break;
|
||||
default:
|
||||
Common::SetCurrentThreadToAllCores();
|
||||
break;
|
||||
}
|
||||
{
|
||||
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
||||
while (!stop_token.stop_requested()) {
|
||||
|
||||
@@ -208,4 +208,12 @@ UUID UUID::MakeRandomRFC4122V4() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
UUID UUID::MakeRFC4122V5(std::span<u8, 20> sha1) {
|
||||
UUID uuid{};
|
||||
std::memcpy(&uuid.uuid, sha1.data(), sizeof(UUID));
|
||||
uuid.uuid[8] = 0x80 | (uuid.uuid[8] & 0x3F);
|
||||
uuid.uuid[6] = 0x50 | (uuid.uuid[6] & 0xF);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+16
-20
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
@@ -5,6 +8,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
@@ -86,28 +90,20 @@ struct UUID {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a random UUID.
|
||||
*
|
||||
* @returns A random UUID.
|
||||
*/
|
||||
static UUID MakeRandom();
|
||||
/// @brief Creates a random UUID.
|
||||
/// @returns A random UUID.
|
||||
[[nodiscard]] static UUID MakeRandom();
|
||||
|
||||
/**
|
||||
* Creates a random UUID with a seed.
|
||||
*
|
||||
* @param seed A seed to initialize the Mersenne-Twister RNG
|
||||
*
|
||||
* @returns A random UUID.
|
||||
*/
|
||||
static UUID MakeRandomWithSeed(u32 seed);
|
||||
/// @brief Creates a random UUID with a seed.
|
||||
/// @param seed A seed to initialize the Mersenne-Twister RNG
|
||||
/// @returns A random UUID.
|
||||
[[nodiscard]] static UUID MakeRandomWithSeed(u32 seed);
|
||||
|
||||
/**
|
||||
* Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant.
|
||||
*
|
||||
* @returns A random UUID that is RFC 4122 Version 4 compliant.
|
||||
*/
|
||||
static UUID MakeRandomRFC4122V4();
|
||||
/// @brief Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant.
|
||||
/// @returns A random UUID that is RFC 4122 Version 4 compliant.
|
||||
[[nodiscard]] static UUID MakeRandomRFC4122V4();
|
||||
|
||||
[[nodiscard]] static UUID MakeRFC4122V5(std::span<u8, 20> sha1);
|
||||
|
||||
friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default;
|
||||
};
|
||||
|
||||
+7
-1
@@ -10,6 +10,7 @@
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/adpf.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "common/string_util.h"
|
||||
@@ -118,6 +119,7 @@ struct System::Impl {
|
||||
|
||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
||||
unified_memory = Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
core_timing.SetMulticore(is_multicore);
|
||||
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
||||
@@ -145,7 +147,8 @@ struct System::Impl {
|
||||
!device_memory.has_value() ||
|
||||
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
||||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
|
||||
Settings::MemoryLayout::Memory_4Gb);
|
||||
Settings::MemoryLayout::Memory_4Gb) ||
|
||||
unified_memory != Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
if (!must_reinitialize) {
|
||||
return;
|
||||
@@ -156,6 +159,7 @@ struct System::Impl {
|
||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
extended_memory_layout =
|
||||
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
||||
unified_memory = Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
Initialize(system);
|
||||
}
|
||||
@@ -385,6 +389,7 @@ struct System::Impl {
|
||||
|
||||
void ShutdownMainProcess() {
|
||||
SetShuttingDown(true);
|
||||
Common::ADPF::Shutdown();
|
||||
|
||||
// Reset per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
@@ -501,6 +506,7 @@ struct System::Impl {
|
||||
std::atomic_bool is_powered_on{};
|
||||
bool is_multicore : 1 = false;
|
||||
bool extended_memory_layout : 1 = false;
|
||||
bool unified_memory : 1 = false;
|
||||
bool exit_locked : 1 = false;
|
||||
bool exit_requested : 1 = false;
|
||||
bool nvdec_active : 1 = false;
|
||||
|
||||
@@ -23,6 +23,21 @@ namespace Core::Timing {
|
||||
|
||||
constexpr s64 MAX_SLICE_LENGTH = 10000;
|
||||
|
||||
constexpr u32 CPU_CLOCK_BASE_MHZ = 1020;
|
||||
constexpr u32 CPU_CLOCK_BOOST_MHZ = 1734;
|
||||
constexpr u32 CPU_CLOCK_OVERCLOCK_MHZ = 2040;
|
||||
|
||||
constexpr u32 CpuClockTargetMhz(Settings::CpuClock clock) {
|
||||
switch (clock) {
|
||||
case Settings::CpuClock::Boost:
|
||||
return CPU_CLOCK_BOOST_MHZ;
|
||||
case Settings::CpuClock::Overclock:
|
||||
return CPU_CLOCK_OVERCLOCK_MHZ;
|
||||
default:
|
||||
return CPU_CLOCK_BASE_MHZ;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
|
||||
return std::make_shared<EventType>(std::move(callback), std::move(name));
|
||||
}
|
||||
@@ -58,7 +73,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
if (is_multicore) {
|
||||
timer_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("HostTiming");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
on_thread_init();
|
||||
has_started = true;
|
||||
|
||||
@@ -209,8 +225,9 @@ void CoreTiming::ResetTicks() {
|
||||
|
||||
u64 CoreTiming::GetClockTicks() const {
|
||||
u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) {
|
||||
fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock)));
|
||||
if (const u32 target = CpuClockTargetMhz(Settings::values.cpu_clock.GetValue());
|
||||
target != CPU_CLOCK_BASE_MHZ) {
|
||||
fres = fres * target / CPU_CLOCK_BASE_MHZ;
|
||||
}
|
||||
if (::Settings::values.sync_core_speed.GetValue()) {
|
||||
auto const ticks = f64(fres);
|
||||
|
||||
@@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
||||
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
#ifdef __ANDROID__
|
||||
// Aimed specifically for Snapdragon 8 Elite devices
|
||||
// This kills performance on desktop, but boosts perf for UMA devices
|
||||
// like the S8E. Mediatek and Mali likely won't suffer.
|
||||
Common::PinCurrentThreadToPerformanceCore(core);
|
||||
#endif
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
auto& data = core_data[core];
|
||||
data.host_context = Common::Fiber::ThreadToFiber();
|
||||
|
||||
|
||||
@@ -12,9 +12,18 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
|
||||
constexpr size_t VirtualReserveSize = 1ULL << 39;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
size_t ApplicationPoolOffset() {
|
||||
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
|
||||
const size_t dram_size = Init::GetIntendedMemorySize();
|
||||
const size_t application_pool_size = Init::GetApplicationPoolSize();
|
||||
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceMemory::DeviceMemory()
|
||||
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
|
||||
VirtualReserveSize} {}
|
||||
VirtualReserveSize, ApplicationPoolOffset()} {}
|
||||
|
||||
DeviceMemory::~DeviceMemory() = default;
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/virtual_buffer.h"
|
||||
|
||||
struct AHardwareBuffer;
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr size_t DEVICE_PAGEBITS = 12ULL;
|
||||
@@ -95,6 +97,34 @@ public:
|
||||
ApplyOpOnPAddr(address, buffer, operation);
|
||||
}
|
||||
|
||||
u8* GetPhysicalBase() noexcept {
|
||||
return reinterpret_cast<u8*>(physical_base);
|
||||
}
|
||||
|
||||
const u8* GetPhysicalBase() const noexcept {
|
||||
return reinterpret_cast<const u8*>(physical_base);
|
||||
}
|
||||
|
||||
size_t GetPhysicalSize() const noexcept {
|
||||
return physical_size;
|
||||
}
|
||||
|
||||
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
|
||||
return ahb_windows;
|
||||
}
|
||||
|
||||
size_t GetBackingHardwareBufferWindowSize() const noexcept {
|
||||
return ahb_window_size;
|
||||
}
|
||||
|
||||
size_t GetBackingHardwareBufferBase() const noexcept {
|
||||
return ahb_base;
|
||||
}
|
||||
|
||||
bool IsBackingShared() const noexcept {
|
||||
return backing_is_shared;
|
||||
}
|
||||
|
||||
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
|
||||
PAddr subbits = PAddr(address & page_mask);
|
||||
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
|
||||
@@ -171,6 +201,11 @@ private:
|
||||
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
|
||||
|
||||
const uintptr_t physical_base;
|
||||
const size_t physical_size;
|
||||
const std::span<AHardwareBuffer* const> ahb_windows;
|
||||
const size_t ahb_window_size;
|
||||
const size_t ahb_base;
|
||||
const bool backing_is_shared;
|
||||
DeviceInterface* device_inter;
|
||||
|
||||
struct TrackedEntry {
|
||||
|
||||
@@ -171,6 +171,11 @@ struct DeviceMemoryManagerAllocator {
|
||||
template <typename Traits>
|
||||
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
|
||||
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
|
||||
, physical_size{device_memory_.buffer.BackingSize()}
|
||||
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
|
||||
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
|
||||
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
|
||||
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
|
||||
, device_inter{nullptr}
|
||||
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
|
||||
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
|
||||
|
||||
@@ -274,23 +274,22 @@ public:
|
||||
explicit NACP(VirtualFile file);
|
||||
~NACP();
|
||||
|
||||
const LanguageEntry& GetLanguageEntry() const;
|
||||
std::string GetApplicationName() const;
|
||||
std::string GetDeveloperName() const;
|
||||
u64 GetTitleId() const;
|
||||
u64 GetDLCBaseTitleId() const;
|
||||
std::string GetVersionString() const;
|
||||
u64 GetDefaultNormalSaveSize() const;
|
||||
u64 GetDefaultJournalSaveSize() const;
|
||||
u32 GetSupportedLanguages() const;
|
||||
std::vector<std::string> GetApplicationNames() const;
|
||||
std::vector<u8> GetRawBytes() const;
|
||||
bool GetUserAccountSwitchLock() const;
|
||||
u64 GetDeviceSaveDataSize() const;
|
||||
u32 GetParentalControlFlag() const;
|
||||
const std::array<u8, 0x20>& GetRatingAge() const;
|
||||
[[nodiscard]] const LanguageEntry& GetLanguageEntry() const;
|
||||
[[nodiscard]] std::string GetApplicationName() const;
|
||||
[[nodiscard]] std::string GetDeveloperName() const;
|
||||
[[nodiscard]] u64 GetTitleId() const;
|
||||
[[nodiscard]] u64 GetDLCBaseTitleId() const;
|
||||
[[nodiscard]] std::string GetVersionString() const;
|
||||
[[nodiscard]] u64 GetDefaultNormalSaveSize() const;
|
||||
[[nodiscard]] u64 GetDefaultJournalSaveSize() const;
|
||||
[[nodiscard]] u32 GetSupportedLanguages() const;
|
||||
[[nodiscard]] std::vector<std::string> GetApplicationNames() const;
|
||||
[[nodiscard]] std::vector<u8> GetRawBytes() const;
|
||||
[[nodiscard]] bool GetUserAccountSwitchLock() const;
|
||||
[[nodiscard]] u64 GetDeviceSaveDataSize() const;
|
||||
[[nodiscard]] u32 GetParentalControlFlag() const;
|
||||
[[nodiscard]] const std::array<u8, 0x20>& GetRatingAge() const;
|
||||
|
||||
private:
|
||||
RawNACP raw{};
|
||||
std::vector<LanguageEntry> language_entries;
|
||||
};
|
||||
|
||||
@@ -1156,4 +1156,14 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||
|
||||
return {std::move(nacp), icon_file};
|
||||
}
|
||||
|
||||
[[nodiscard]] PatchManager::Metadata PatchManager::GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept {
|
||||
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr)
|
||||
return metadata;
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -105,6 +105,9 @@ public:
|
||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||
[[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
|
||||
|
||||
/// @brief Gets NACP metadata (accounting for any patches or updates)
|
||||
[[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
@@ -154,20 +156,7 @@ Result IApplicationFunctions::GetDesiredLanguage(Out<u64> out_language_code) {
|
||||
// Default to 0 (all languages supported)
|
||||
u32 supported_languages = 0;
|
||||
|
||||
const auto res = [this] {
|
||||
const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
if (res.first != nullptr) {
|
||||
supported_languages = res.first->GetSupportedLanguages();
|
||||
}
|
||||
@@ -205,20 +194,7 @@ Result IApplicationFunctions::SetTerminateResult(Result terminate_result) {
|
||||
Result IApplicationFunctions::GetDisplayVersion(Out<DisplayVersion> out_display_version) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
const auto res = [this] {
|
||||
const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
if (res.first != nullptr) {
|
||||
const auto& version = res.first->GetVersionString();
|
||||
std::memcpy(out_display_version->string.data(), version.data(),
|
||||
@@ -347,8 +323,21 @@ Result IApplicationFunctions::NotifyRunning(Out<bool> out_became_running) {
|
||||
}
|
||||
|
||||
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
*out_pseudo_device_id = {};
|
||||
LOG_WARNING(Service_AM, "(stubbed)");
|
||||
|
||||
// This should be hashed with the device specific hash
|
||||
// for now this will do
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
u8 hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int hash_len = 0;
|
||||
auto const seed = res.first->raw.seed_for_pseudo_device_id;
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
auto const algorithm = EVP_sha1();
|
||||
EVP_DigestInit_ex(ctx, algorithm, nullptr);
|
||||
EVP_DigestUpdate(ctx, &seed, sizeof(seed));
|
||||
EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
*out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span<u8, 20>{hash, std::size(hash)});
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -252,19 +252,7 @@ Result ILibraryAppletSelfAccessor::GetMainAppletApplicationDesiredLanguage(
|
||||
// Default to 0 (all languages supported)
|
||||
u32 supported_languages = 0;
|
||||
|
||||
const auto res = [this, identity] {
|
||||
const FileSys::PatchManager pm{identity.application_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(identity.application_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, identity.application_id);
|
||||
|
||||
if (res.first != nullptr) {
|
||||
supported_languages = res.first->GetSupportedLanguages();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
@@ -86,11 +87,16 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
|
||||
}
|
||||
|
||||
system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences));
|
||||
Common::ADPF::ReportFrameInterval();
|
||||
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||
system.GetPerfStats().EndSystemFrame();
|
||||
system.GetPerfStats().BeginSystemFrame();
|
||||
}
|
||||
|
||||
void nvdisp_disp0::WaitForComposite() {
|
||||
system.GPU().WaitForComposite();
|
||||
}
|
||||
|
||||
Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
|
||||
LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
|
||||
return nullptr;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -37,6 +40,8 @@ public:
|
||||
/// Performs a screen flip, compositing each buffer.
|
||||
void Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers);
|
||||
|
||||
void WaitForComposite();
|
||||
|
||||
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -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 2024 yuzu Emulator Project
|
||||
@@ -57,18 +57,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
// Set default speed limit to 100%.
|
||||
*out_speed_scale = 1.0f;
|
||||
|
||||
// If no layers are available, skip the logic.
|
||||
bool any_visible = false;
|
||||
for (auto& layer : display.stack.layers) {
|
||||
if (layer->visible) {
|
||||
any_visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!any_visible) {
|
||||
*out_speed_scale = 1.0f;
|
||||
return 1;
|
||||
}
|
||||
nvdisp.WaitForComposite();
|
||||
this->ReleaseFramebuffersLocked(display);
|
||||
|
||||
// Determine the number of vsync periods to wait before composing again.
|
||||
std::optional<s32> swap_interval{};
|
||||
@@ -158,55 +148,30 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
nvdisp.Composite(composition_stack);
|
||||
}
|
||||
|
||||
// Batch framebuffer releases, instead of one-into-one.
|
||||
std::vector<std::pair<Layer*, Framebuffer*>> to_release;
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired)
|
||||
continue;
|
||||
|
||||
auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer)
|
||||
continue;
|
||||
|
||||
// Overlay layers always release after every compose
|
||||
// Non-overlay layers release based on their swap interval
|
||||
if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) {
|
||||
to_release.emplace_back(layer.get(), &framebuffer);
|
||||
}
|
||||
}
|
||||
for (auto& [layer, framebuffer] : to_release) {
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence());
|
||||
framebuffer->is_acquired = false;
|
||||
}
|
||||
|
||||
// Advance by 1 frame (60 FPS compositing)
|
||||
m_frame_number += 1;
|
||||
|
||||
// Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
|
||||
return 1;
|
||||
}
|
||||
|
||||
void HardwareComposer::ReleaseFramebuffersLocked(Display& display) {
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired) {
|
||||
// Already released.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
const auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
|
||||
// Skip overlay layers as they were already released above
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: support release fence
|
||||
// This is needed to prevent screen tearing
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
if (!layer->is_overlay && framebuffer.release_frame_number > m_frame_number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
|
||||
|
||||
@@ -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 2024 yuzu Emulator Project
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
private:
|
||||
bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer);
|
||||
CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id);
|
||||
void ReleaseFramebuffersLocked(Display& display);
|
||||
};
|
||||
|
||||
} // namespace Service::Nvnflinger
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/vi/conductor.h"
|
||||
@@ -14,6 +18,8 @@
|
||||
|
||||
constexpr auto FrameNs = std::chrono::nanoseconds{1000000000 / 60};
|
||||
|
||||
constexpr s64 UNLOCKED_TARGET_DIVISOR = 4;
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
Conductor::Conductor(Core::System& system, Container& container, DisplayList& displays)
|
||||
@@ -68,6 +74,9 @@ void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) {
|
||||
}
|
||||
|
||||
void Conductor::ProcessVsync() {
|
||||
Common::PollThreadPolicies();
|
||||
Common::ADPF::SetTargetWorkDuration(std::chrono::nanoseconds{this->GetFramePeriodNs()});
|
||||
|
||||
for (auto& [display_id, manager] : m_vsync_managers) {
|
||||
m_container.ComposeOnDisplay(&m_swap_interval, &m_compose_speed_scale, display_id);
|
||||
manager.SignalVsync(m_system.Kernel());
|
||||
@@ -76,6 +85,8 @@ void Conductor::ProcessVsync() {
|
||||
|
||||
void Conductor::VsyncThread(std::stop_token token) {
|
||||
Common::SetCurrentThreadName("VSyncThread");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
|
||||
while (!token.stop_requested()) {
|
||||
m_signal.Wait();
|
||||
@@ -114,4 +125,25 @@ s64 Conductor::GetNextTicks() const {
|
||||
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
|
||||
}
|
||||
|
||||
s64 Conductor::GetFramePeriodNs() const {
|
||||
const auto& settings = Settings::values;
|
||||
f32 speed_scale = 1.f;
|
||||
bool unlocked = false;
|
||||
if (settings.use_multi_core.GetValue()) {
|
||||
if (settings.use_speed_limit.GetValue()) {
|
||||
speed_scale = 100.f / Settings::SpeedLimit();
|
||||
} else {
|
||||
unlocked = true;
|
||||
}
|
||||
}
|
||||
speed_scale /= m_compose_speed_scale;
|
||||
|
||||
const f32 effective_fps = 60.f / static_cast<f32>(m_swap_interval);
|
||||
s64 period = static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
|
||||
if (unlocked) {
|
||||
period /= UNLOCKED_TARGET_DIVISOR;
|
||||
}
|
||||
return std::clamp<s64>(period, 1'000'000, 100'000'000);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -44,6 +44,7 @@ private:
|
||||
void ProcessVsync();
|
||||
void VsyncThread(std::stop_token token);
|
||||
s64 GetNextTicks() const;
|
||||
s64 GetFramePeriodNs() const;
|
||||
|
||||
private:
|
||||
Core::System& m_system;
|
||||
|
||||
@@ -100,6 +100,7 @@ void EmulatedConsole::ReloadInput() {
|
||||
motion.gyro = emulated_motion.GetGyroscope();
|
||||
motion.rotation = emulated_motion.GetRotations();
|
||||
motion.orientation = emulated_motion.GetOrientation();
|
||||
motion.quaternion = emulated_motion.GetQuaternion();
|
||||
motion.is_at_rest = !emulated_motion.IsMoving(motion_sensitivity);
|
||||
|
||||
// Unique index for identifying touch device source
|
||||
|
||||
@@ -504,9 +504,12 @@ void NPad::OnUpdate(Kernel::KernelCore& kernel, const Core::Timing::CoreTiming&
|
||||
|
||||
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
|
||||
auto& controller = controller_data[aruid_index][i];
|
||||
controller.shared_memory =
|
||||
&data->shared_memory_format->npad.npad_entry[i].internal_state;
|
||||
controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state;
|
||||
auto* npad = controller.shared_memory;
|
||||
if (!npad || !controller.device) {
|
||||
LOG_WARNING(Service_HID, "No device for {}", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& controller_type = controller.device->GetNpadStyleIndex();
|
||||
|
||||
|
||||
@@ -92,12 +92,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
tr("Change the accuracy of the emulated CPU (for debugging only)."));
|
||||
INSERT(Settings, cpu_backend, tr("Backend:"), QString());
|
||||
|
||||
INSERT(Settings, fast_cpu_time, tr("CPU Overclock"),
|
||||
tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see "
|
||||
"reduced performance, "
|
||||
"and certain games may behave improperly.\nUse Boost (1700MHz) to run at the "
|
||||
"Switch's highest native "
|
||||
"clock, or Fast (2000MHz) to run at 2x clock."));
|
||||
INSERT(Settings, cpu_clock, tr("CPU Clocks"),
|
||||
tr("Raises the clock the emulated CPU reports, which removes some FPS limiters.\n"
|
||||
"Weaker CPUs may see reduced performance, and certain games may behave improperly."));
|
||||
|
||||
INSERT(Settings, use_custom_cpu_ticks, QString(), QString());
|
||||
INSERT(Settings, cpu_ticks, tr("Custom CPU Ticks"),
|
||||
@@ -230,9 +227,11 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
|
||||
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
|
||||
tr("May reduce shader stutter."));
|
||||
INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"),
|
||||
tr("Overclocks the emulated GPU to increase dynamic resolution and render "
|
||||
"distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity."));
|
||||
INSERT(Settings, use_unified_memory, tr("Enable unified memory access"),
|
||||
tr("Lets the GPU write buffer readbacks directly into guest memory."));
|
||||
INSERT(Settings, gpu_clock, tr("GPU Clocks"),
|
||||
tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering "
|
||||
"resolution and render distance to fit the Switch's clocks."));
|
||||
INSERT(Settings, gpu_unswizzle_enabled, tr("GPU Unswizzle"),
|
||||
tr("Accelerates BCn 3D texture decoding using GPU compute.\n"
|
||||
"Disable if experiencing crashes or graphical glitches."));
|
||||
@@ -639,9 +638,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::CpuClock>::Index(),
|
||||
{
|
||||
PAIR(CpuClock, Off, tr("Off")),
|
||||
PAIR(CpuClock, Boost, tr("Boost (1700MHz)")),
|
||||
PAIR(CpuClock, Fast, tr("Fast (2000MHz)")),
|
||||
PAIR(CpuClock, Normal, tr("Normal")),
|
||||
PAIR(CpuClock, Boost, tr("Boost")),
|
||||
PAIR(CpuClock, Overclock, tr("Overclock")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
|
||||
@@ -650,11 +649,11 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
||||
PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")),
|
||||
PAIR(ConfirmStop, Ask_Never, tr("Never ask")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuOverclock>::Index(),
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuClock>::Index(),
|
||||
{
|
||||
PAIR(GpuOverclock, Normal, tr("Off")),
|
||||
PAIR(GpuOverclock, Medium, tr("Medium (256)")),
|
||||
PAIR(GpuOverclock, High, tr("High (512)")),
|
||||
PAIR(GpuClock, Normal, tr("Normal")),
|
||||
PAIR(GpuClock, Boost, tr("Boost")),
|
||||
PAIR(GpuClock, Overclock, tr("Overclock")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuUnswizzleSize>::Index(),
|
||||
{
|
||||
|
||||
@@ -234,12 +234,13 @@ add_library(shader_recompiler STATIC
|
||||
ir_opt/texture_pass.cpp
|
||||
ir_opt/vendor_workaround_pass.cpp
|
||||
ir_opt/verification_pass.cpp
|
||||
object_pool.h
|
||||
profile.h
|
||||
program_header.h
|
||||
runtime_info.h
|
||||
shader_info.h
|
||||
varying_state.h
|
||||
shader_pool.h
|
||||
|
||||
)
|
||||
|
||||
target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit::sirit)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
Block::Block(boost::container::stable_vector<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
|
||||
Block::Block(ObjectPool<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
|
||||
|
||||
Block::~Block() = default;
|
||||
|
||||
@@ -23,12 +23,13 @@ void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
|
||||
}
|
||||
|
||||
Block::iterator Block::PrependNewInst(iterator insertion_point, const Inst& base_inst) {
|
||||
Inst* const inst{&inst_pool->emplace_back(base_inst)};
|
||||
Inst* const inst{inst_pool->Create(base_inst)};
|
||||
return instructions.insert(insertion_point, *inst);
|
||||
}
|
||||
|
||||
Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args, u32 flags) {
|
||||
Inst* const inst{&inst_pool->emplace_back(op, flags)};
|
||||
Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode op,
|
||||
std::initializer_list<Value> args, u32 flags) {
|
||||
Inst* const inst{inst_pool->Create(op, flags)};
|
||||
const auto result_it{instructions.insert(insertion_point, *inst)};
|
||||
|
||||
if (inst->NumArgs() != args.size()) {
|
||||
@@ -52,10 +53,12 @@ void Block::AddBranch(Block* block) {
|
||||
block->imm_predecessors.push_back(this);
|
||||
}
|
||||
|
||||
static std::string BlockToIndex(const std::map<const Block*, size_t>& block_to_index, Block* block) {
|
||||
if (const auto it{block_to_index.find(block)}; it != block_to_index.end())
|
||||
static std::string BlockToIndex(const std::map<const Block*, size_t>& block_to_index,
|
||||
Block* block) {
|
||||
if (const auto it{block_to_index.find(block)}; it != block_to_index.end()) {
|
||||
return fmt::format("{{Block ${}}}", it->second);
|
||||
return fmt::format("$<unknown block {:016x}>", u64(block));
|
||||
}
|
||||
return fmt::format("$<unknown block {:016x}>", reinterpret_cast<u64>(block));
|
||||
}
|
||||
|
||||
static size_t InstIndex(std::map<const Inst*, size_t>& inst_to_index, size_t& inst_index,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -13,11 +13,11 @@
|
||||
#include <bit>
|
||||
#include <numeric>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/container/stable_vector.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/frontend/ir/condition.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
#include "shader_recompiler/object_pool.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
using reverse_iterator = InstructionList::reverse_iterator;
|
||||
using const_reverse_iterator = InstructionList::const_reverse_iterator;
|
||||
|
||||
explicit Block(boost::container::stable_vector<Inst>& inst_pool_);
|
||||
explicit Block(ObjectPool<Inst>& inst_pool_);
|
||||
~Block();
|
||||
|
||||
Block(const Block&) = delete;
|
||||
@@ -170,7 +170,7 @@ public:
|
||||
|
||||
private:
|
||||
/// Memory pool for instruction list
|
||||
boost::container::stable_vector<Inst>* inst_pool;
|
||||
ObjectPool<Inst>* inst_pool;
|
||||
|
||||
/// List of instructions in this block
|
||||
InstructionList instructions;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -174,10 +174,11 @@ bool Block::Contains(Location pc) const noexcept {
|
||||
return pc >= begin && pc < end;
|
||||
}
|
||||
|
||||
Function::Function(boost::container::stable_vector<Block>& block_pool, Location start_address) : entrypoint{start_address} {
|
||||
Function::Function(ObjectPool<Block>& block_pool, Location start_address)
|
||||
: entrypoint{start_address} {
|
||||
Label& label{labels.emplace_back()};
|
||||
label.address = start_address;
|
||||
label.block = &block_pool.emplace_back(Block{});
|
||||
label.block = block_pool.Create(Block{});
|
||||
label.block->begin = start_address;
|
||||
label.block->end = start_address;
|
||||
label.block->end_class = EndClass::Branch;
|
||||
@@ -186,13 +187,12 @@ Function::Function(boost::container::stable_vector<Block>& block_pool, Location
|
||||
label.block->branch_false = nullptr;
|
||||
}
|
||||
|
||||
CFG::CFG(Environment& env_, boost::container::stable_vector<Block>& block_pool_, Location start_address, bool exits_to_dispatcher_)
|
||||
: env{env_}
|
||||
, block_pool{block_pool_}
|
||||
, program_start{start_address}
|
||||
, exits_to_dispatcher{exits_to_dispatcher_} {
|
||||
CFG::CFG(Environment& env_, ObjectPool<Block>& block_pool_, Location start_address,
|
||||
bool exits_to_dispatcher_)
|
||||
: env{env_}, block_pool{block_pool_}, program_start{start_address}, exits_to_dispatcher{
|
||||
exits_to_dispatcher_} {
|
||||
if (exits_to_dispatcher) {
|
||||
dispatch_block = &block_pool.emplace_back(Block{});
|
||||
dispatch_block = block_pool.Create(Block{});
|
||||
dispatch_block->begin = {};
|
||||
dispatch_block->end = {};
|
||||
dispatch_block->end_class = EndClass::Exit;
|
||||
@@ -371,7 +371,7 @@ void CFG::AnalyzeCondInst(Block* block, FunctionId function_id, Location pc,
|
||||
return;
|
||||
}
|
||||
// Create a virtual block and a conditional block
|
||||
Block* const conditional_block{&block_pool.emplace_back()};
|
||||
Block* const conditional_block{block_pool.Create()};
|
||||
Block virtual_block{};
|
||||
virtual_block.begin = block->begin.Virtual();
|
||||
virtual_block.end = block->begin.Virtual();
|
||||
@@ -546,7 +546,7 @@ Block* CFG::AddLabel(Block* block, Stack stack, Location pc, FunctionId function
|
||||
if (label_it != function.labels.end()) {
|
||||
return label_it->block;
|
||||
}
|
||||
Block* const new_block{&block_pool.emplace_back()};
|
||||
Block* const new_block{block_pool.Create()};
|
||||
new_block->begin = pc;
|
||||
new_block->end = pc;
|
||||
new_block->end_class = EndClass::Branch;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -12,7 +9,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/stable_vector.hpp>
|
||||
#include <boost/intrusive/set.hpp>
|
||||
|
||||
#include "shader_recompiler/environment.h"
|
||||
@@ -21,6 +17,7 @@
|
||||
#include "shader_recompiler/frontend/maxwell/instruction.h"
|
||||
#include "shader_recompiler/frontend/maxwell/location.h"
|
||||
#include "shader_recompiler/frontend/maxwell/opcodes.h"
|
||||
#include "shader_recompiler/object_pool.h"
|
||||
|
||||
namespace Shader::Maxwell::Flow {
|
||||
|
||||
@@ -99,7 +96,7 @@ struct Label {
|
||||
};
|
||||
|
||||
struct Function {
|
||||
explicit Function(boost::container::stable_vector<Block>& block_pool, Location start_address);
|
||||
explicit Function(ObjectPool<Block>& block_pool, Location start_address);
|
||||
|
||||
Location entrypoint;
|
||||
boost::container::small_vector<Label, 16> labels;
|
||||
@@ -113,7 +110,8 @@ class CFG {
|
||||
};
|
||||
|
||||
public:
|
||||
explicit CFG(Environment& env, boost::container::stable_vector<Block>& block_pool, Location start_address, bool exits_to_dispatcher = false);
|
||||
explicit CFG(Environment& env, ObjectPool<Block>& block_pool, Location start_address,
|
||||
bool exits_to_dispatcher = false);
|
||||
|
||||
CFG& operator=(const CFG&) = delete;
|
||||
CFG(const CFG&) = delete;
|
||||
@@ -140,20 +138,27 @@ private:
|
||||
/// Inspect already visited blocks.
|
||||
/// Return true when the block has already been visited
|
||||
bool InspectVisitedBlocks(FunctionId function_id, const Label& label);
|
||||
|
||||
AnalysisState AnalyzeInst(Block* block, FunctionId function_id, Location pc);
|
||||
void AnalyzeCondInst(Block* block, FunctionId function_id, Location pc, EndClass insn_end_class, IR::Condition cond);
|
||||
|
||||
void AnalyzeCondInst(Block* block, FunctionId function_id, Location pc, EndClass insn_end_class,
|
||||
IR::Condition cond);
|
||||
|
||||
/// Return true when the branch instruction is confirmed to be a branch
|
||||
bool AnalyzeBranch(Block* block, FunctionId function_id, Location pc, Instruction inst, Opcode opcode);
|
||||
void AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst, bool is_absolute);
|
||||
AnalysisState AnalyzeBRX(Block* block, Location pc, Instruction inst, bool is_absolute, FunctionId function_id);
|
||||
bool AnalyzeBranch(Block* block, FunctionId function_id, Location pc, Instruction inst,
|
||||
Opcode opcode);
|
||||
|
||||
void AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst,
|
||||
bool is_absolute);
|
||||
AnalysisState AnalyzeBRX(Block* block, Location pc, Instruction inst, bool is_absolute,
|
||||
FunctionId function_id);
|
||||
AnalysisState AnalyzeEXIT(Block* block, FunctionId function_id, Location pc, Instruction inst);
|
||||
|
||||
/// Return the branch target block id
|
||||
Block* AddLabel(Block* block, Stack stack, Location pc, FunctionId function_id);
|
||||
|
||||
Environment& env;
|
||||
boost::container::stable_vector<Block>& block_pool;
|
||||
ObjectPool<Block>& block_pool;
|
||||
boost::container::small_vector<Function, 1> functions;
|
||||
Location program_start;
|
||||
bool exits_to_dispatcher{};
|
||||
|
||||
@@ -13,19 +13,143 @@
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include <ranges>
|
||||
#include "shader_recompiler/shader_pool.h"
|
||||
#include "shader_recompiler/environment.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate/translate.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate_program.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
#include "shader_recompiler/host_translate_info.h"
|
||||
#include "shader_recompiler/object_pool.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
namespace {
|
||||
struct Statement;
|
||||
|
||||
// Use normal_link because we are not guaranteed to destroy the tree in order
|
||||
using ListBaseHook =
|
||||
boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>;
|
||||
|
||||
using Tree = boost::intrusive::list<Statement,
|
||||
// Allow using Statement without a definition
|
||||
boost::intrusive::base_hook<ListBaseHook>,
|
||||
// Avoid linear complexity on splice, size is never called
|
||||
boost::intrusive::constant_time_size<false>>;
|
||||
using Node = Tree::iterator;
|
||||
|
||||
enum class StatementType {
|
||||
Code,
|
||||
Goto,
|
||||
Label,
|
||||
If,
|
||||
Loop,
|
||||
Break,
|
||||
Return,
|
||||
Kill,
|
||||
Unreachable,
|
||||
Function,
|
||||
Identity,
|
||||
Not,
|
||||
Or,
|
||||
SetVariable,
|
||||
SetIndirectBranchVariable,
|
||||
Variable,
|
||||
IndirectBranchCond,
|
||||
};
|
||||
|
||||
bool HasChildren(StatementType type) {
|
||||
switch (type) {
|
||||
case StatementType::If:
|
||||
case StatementType::Loop:
|
||||
case StatementType::Function:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct Goto {};
|
||||
struct Label {};
|
||||
struct If {};
|
||||
struct Loop {};
|
||||
struct Break {};
|
||||
struct Return {};
|
||||
struct Kill {};
|
||||
struct Unreachable {};
|
||||
struct FunctionTag {};
|
||||
struct Identity {};
|
||||
struct Not {};
|
||||
struct Or {};
|
||||
struct SetVariable {};
|
||||
struct SetIndirectBranchVariable {};
|
||||
struct Variable {};
|
||||
struct IndirectBranchCond {};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26495) // Always initialize a member variable, expected in Statement
|
||||
#endif
|
||||
struct Statement : ListBaseHook {
|
||||
Statement(const Flow::Block* block_, Statement* up_)
|
||||
: block{block_}, up{up_}, type{StatementType::Code} {}
|
||||
Statement(Goto, Statement* cond_, Node label_, Statement* up_)
|
||||
: label{label_}, cond{cond_}, up{up_}, type{StatementType::Goto} {}
|
||||
Statement(Label, u32 id_, Statement* up_) : id{id_}, up{up_}, type{StatementType::Label} {}
|
||||
Statement(If, Statement* cond_, Tree&& children_, Statement* up_)
|
||||
: children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::If} {}
|
||||
Statement(Loop, Statement* cond_, Tree&& children_, Statement* up_)
|
||||
: children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::Loop} {}
|
||||
Statement(Break, Statement* cond_, Statement* up_)
|
||||
: cond{cond_}, up{up_}, type{StatementType::Break} {}
|
||||
Statement(Return, Statement* up_) : up{up_}, type{StatementType::Return} {}
|
||||
Statement(Kill, Statement* up_) : up{up_}, type{StatementType::Kill} {}
|
||||
Statement(Unreachable, Statement* up_) : up{up_}, type{StatementType::Unreachable} {}
|
||||
Statement(FunctionTag) : children{}, type{StatementType::Function} {}
|
||||
Statement(Identity, IR::Condition cond_, Statement* up_)
|
||||
: guest_cond{cond_}, up{up_}, type{StatementType::Identity} {}
|
||||
Statement(Not, Statement* op_, Statement* up_) : op{op_}, up{up_}, type{StatementType::Not} {}
|
||||
Statement(Or, Statement* op_a_, Statement* op_b_, Statement* up_)
|
||||
: op_a{op_a_}, op_b{op_b_}, up{up_}, type{StatementType::Or} {}
|
||||
Statement(SetVariable, u32 id_, Statement* op_, Statement* up_)
|
||||
: op{op_}, id{id_}, up{up_}, type{StatementType::SetVariable} {}
|
||||
Statement(SetIndirectBranchVariable, IR::Reg branch_reg_, s32 branch_offset_, Statement* up_)
|
||||
: branch_offset{branch_offset_},
|
||||
branch_reg{branch_reg_}, up{up_}, type{StatementType::SetIndirectBranchVariable} {}
|
||||
Statement(Variable, u32 id_, Statement* up_)
|
||||
: id{id_}, up{up_}, type{StatementType::Variable} {}
|
||||
Statement(IndirectBranchCond, u32 location_, Statement* up_)
|
||||
: location{location_}, up{up_}, type{StatementType::IndirectBranchCond} {}
|
||||
|
||||
~Statement() {
|
||||
if (HasChildren(type)) {
|
||||
std::destroy_at(&children);
|
||||
}
|
||||
}
|
||||
|
||||
union {
|
||||
const Flow::Block* block;
|
||||
Node label;
|
||||
Tree children;
|
||||
IR::Condition guest_cond;
|
||||
Statement* op;
|
||||
Statement* op_a;
|
||||
u32 location;
|
||||
s32 branch_offset;
|
||||
};
|
||||
union {
|
||||
Statement* cond;
|
||||
Statement* op_b;
|
||||
u32 id;
|
||||
IR::Reg branch_reg;
|
||||
};
|
||||
Statement* up{};
|
||||
StatementType type;
|
||||
};
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
std::string DumpExpr(const Statement* stmt) {
|
||||
switch (stmt->type) {
|
||||
@@ -190,9 +314,7 @@ bool NeedsLift(Node goto_stmt, Node label_stmt) noexcept {
|
||||
|
||||
class GotoPass {
|
||||
public:
|
||||
explicit GotoPass(Flow::CFG& cfg, ShaderPools& pools_)
|
||||
: pools{pools_}
|
||||
{
|
||||
explicit GotoPass(Flow::CFG& cfg, ObjectPool<Statement>& stmt_pool) : pool{stmt_pool} {
|
||||
std::vector gotos{BuildTree(cfg)};
|
||||
const auto end{gotos.rend()};
|
||||
for (auto goto_stmt = gotos.rbegin(); goto_stmt != end; ++goto_stmt) {
|
||||
@@ -263,14 +385,16 @@ private:
|
||||
return gotos;
|
||||
}
|
||||
|
||||
void BuildTree(Flow::CFG& cfg, Flow::Function& function, u32& label_id, std::vector<Node>& gotos, Node function_insert_point, std::optional<Node> return_label) {
|
||||
Statement* const false_stmt{&pools.stmt.emplace_back(Identity{}, IR::Condition{false}, &root_stmt)};
|
||||
void BuildTree(Flow::CFG& cfg, Flow::Function& function, u32& label_id,
|
||||
std::vector<Node>& gotos, Node function_insert_point,
|
||||
std::optional<Node> return_label) {
|
||||
Statement* const false_stmt{pool.Create(Identity{}, IR::Condition{false}, &root_stmt)};
|
||||
Tree& root{root_stmt.children};
|
||||
ankerl::unordered_dense::map<Flow::Block*, Node> local_labels;
|
||||
local_labels.reserve(function.blocks.size());
|
||||
|
||||
for (Flow::Block& block : function.blocks) {
|
||||
Statement* const label{&pools.stmt.emplace_back(Label{}, label_id, &root_stmt)};
|
||||
Statement* const label{pool.Create(Label{}, label_id, &root_stmt)};
|
||||
const Node label_it{root.insert(function_insert_point, *label)};
|
||||
local_labels.emplace(&block, label_it);
|
||||
++label_id;
|
||||
@@ -282,39 +406,46 @@ private:
|
||||
|
||||
// Reset goto variables before the first block and after its respective label
|
||||
const auto make_reset_variable{[&]() -> Statement& {
|
||||
return pools.stmt.emplace_back(SetVariable{}, label->id, false_stmt, &root_stmt);
|
||||
return *pool.Create(SetVariable{}, label->id, false_stmt, &root_stmt);
|
||||
}};
|
||||
root.push_front(make_reset_variable());
|
||||
root.insert(ip, make_reset_variable());
|
||||
root.insert(ip, pools.stmt.emplace_back(&block, &root_stmt));
|
||||
root.insert(ip, *pool.Create(&block, &root_stmt));
|
||||
|
||||
switch (block.end_class) {
|
||||
case Flow::EndClass::Branch: {
|
||||
Statement* const always_cond{&pools.stmt.emplace_back(Identity{}, IR::Condition{true}, &root_stmt)};
|
||||
Statement* const always_cond{
|
||||
pool.Create(Identity{}, IR::Condition{true}, &root_stmt)};
|
||||
if (block.cond == IR::Condition{true}) {
|
||||
const Node true_label{local_labels.at(block.branch_true)};
|
||||
gotos.push_back(root.insert(ip, pools.stmt.emplace_back(Goto{}, always_cond, true_label, &root_stmt)));
|
||||
gotos.push_back(
|
||||
root.insert(ip, *pool.Create(Goto{}, always_cond, true_label, &root_stmt)));
|
||||
} else if (block.cond == IR::Condition{false}) {
|
||||
const Node false_label{local_labels.at(block.branch_false)};
|
||||
gotos.push_back(root.insert(ip, pools.stmt.emplace_back(Goto{}, always_cond, false_label, &root_stmt)));
|
||||
gotos.push_back(root.insert(
|
||||
ip, *pool.Create(Goto{}, always_cond, false_label, &root_stmt)));
|
||||
} else {
|
||||
const Node true_label{local_labels.at(block.branch_true)};
|
||||
const Node false_label{local_labels.at(block.branch_false)};
|
||||
Statement* const true_cond{&pools.stmt.emplace_back(Identity{}, block.cond, &root_stmt)};
|
||||
gotos.push_back(root.insert(ip, pools.stmt.emplace_back(Goto{}, true_cond, true_label, &root_stmt)));
|
||||
gotos.push_back(root.insert(ip, pools.stmt.emplace_back(Goto{}, always_cond, false_label, &root_stmt)));
|
||||
Statement* const true_cond{pool.Create(Identity{}, block.cond, &root_stmt)};
|
||||
gotos.push_back(
|
||||
root.insert(ip, *pool.Create(Goto{}, true_cond, true_label, &root_stmt)));
|
||||
gotos.push_back(root.insert(
|
||||
ip, *pool.Create(Goto{}, always_cond, false_label, &root_stmt)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Flow::EndClass::IndirectBranch:
|
||||
root.insert(ip, pools.stmt.emplace_back(SetIndirectBranchVariable{}, block.branch_reg, block.branch_offset, &root_stmt));
|
||||
root.insert(ip, *pool.Create(SetIndirectBranchVariable{}, block.branch_reg,
|
||||
block.branch_offset, &root_stmt));
|
||||
for (const Flow::IndirectBranch& indirect : block.indirect_branches) {
|
||||
const Node indirect_label{local_labels.at(indirect.block)};
|
||||
Statement* cond{&pools.stmt.emplace_back(IndirectBranchCond{}, indirect.address, &root_stmt)};
|
||||
Statement* goto_stmt{&pools.stmt.emplace_back(Goto{}, cond, indirect_label, &root_stmt)};
|
||||
Statement* cond{
|
||||
pool.Create(IndirectBranchCond{}, indirect.address, &root_stmt)};
|
||||
Statement* goto_stmt{pool.Create(Goto{}, cond, indirect_label, &root_stmt)};
|
||||
gotos.push_back(root.insert(ip, *goto_stmt));
|
||||
}
|
||||
root.insert(ip, pools.stmt.emplace_back(Unreachable{}, &root_stmt));
|
||||
root.insert(ip, *pool.Create(Unreachable{}, &root_stmt));
|
||||
break;
|
||||
case Flow::EndClass::Call: {
|
||||
Flow::Function& call{cfg.Functions()[block.function_call]};
|
||||
@@ -323,16 +454,16 @@ private:
|
||||
break;
|
||||
}
|
||||
case Flow::EndClass::Exit:
|
||||
root.insert(ip, pools.stmt.emplace_back(Return{}, &root_stmt));
|
||||
root.insert(ip, *pool.Create(Return{}, &root_stmt));
|
||||
break;
|
||||
case Flow::EndClass::Return: {
|
||||
Statement* const always_cond{&pools.stmt.emplace_back(Identity{}, block.cond, &root_stmt)};
|
||||
auto goto_stmt{&pools.stmt.emplace_back(Goto{}, always_cond, return_label.value(), &root_stmt)};
|
||||
Statement* const always_cond{pool.Create(Identity{}, block.cond, &root_stmt)};
|
||||
auto goto_stmt{pool.Create(Goto{}, always_cond, return_label.value(), &root_stmt)};
|
||||
gotos.push_back(root.insert(ip, *goto_stmt));
|
||||
break;
|
||||
}
|
||||
case Flow::EndClass::Kill:
|
||||
root.insert(ip, pools.stmt.emplace_back(Kill{}, &root_stmt));
|
||||
root.insert(ip, *pool.Create(Kill{}, &root_stmt));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -348,8 +479,8 @@ private:
|
||||
Tree& body{goto_stmt->up->children};
|
||||
Tree if_body;
|
||||
if_body.splice(if_body.begin(), body, std::next(goto_stmt), label_stmt);
|
||||
Statement* const cond{&pools.stmt.emplace_back(Not{}, goto_stmt->cond, &root_stmt)};
|
||||
Statement* const if_stmt{&pools.stmt.emplace_back(If{}, cond, std::move(if_body), goto_stmt->up)};
|
||||
Statement* const cond{pool.Create(Not{}, goto_stmt->cond, &root_stmt)};
|
||||
Statement* const if_stmt{pool.Create(If{}, cond, std::move(if_body), goto_stmt->up)};
|
||||
UpdateTreeUp(if_stmt);
|
||||
body.insert(goto_stmt, *if_stmt);
|
||||
body.erase(goto_stmt);
|
||||
@@ -360,7 +491,7 @@ private:
|
||||
Tree loop_body;
|
||||
loop_body.splice(loop_body.begin(), body, label_stmt, goto_stmt);
|
||||
Statement* const cond{goto_stmt->cond};
|
||||
Statement* const loop{&pools.stmt.emplace_back(Loop{}, cond, std::move(loop_body), goto_stmt->up)};
|
||||
Statement* const loop{pool.Create(Loop{}, cond, std::move(loop_body), goto_stmt->up)};
|
||||
UpdateTreeUp(loop);
|
||||
body.insert(goto_stmt, *loop);
|
||||
body.erase(goto_stmt);
|
||||
@@ -385,15 +516,15 @@ private:
|
||||
const u32 label_id{label->id};
|
||||
|
||||
Statement* const goto_cond{goto_stmt->cond};
|
||||
Statement* const set_var{&pools.stmt.emplace_back(SetVariable{}, label_id, goto_cond, parent)};
|
||||
Statement* const set_var{pool.Create(SetVariable{}, label_id, goto_cond, parent)};
|
||||
body.insert(goto_stmt, *set_var);
|
||||
|
||||
Tree if_body;
|
||||
if_body.splice(if_body.begin(), body, std::next(goto_stmt), label_nested_stmt);
|
||||
Statement* const variable{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const neg_var{&pools.stmt.emplace_back(Not{}, variable, &root_stmt)};
|
||||
Statement* const variable{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const neg_var{pool.Create(Not{}, variable, &root_stmt)};
|
||||
if (!if_body.empty()) {
|
||||
Statement* const if_stmt{&pools.stmt.emplace_back(If{}, neg_var, std::move(if_body), parent)};
|
||||
Statement* const if_stmt{pool.Create(If{}, neg_var, std::move(if_body), parent)};
|
||||
UpdateTreeUp(if_stmt);
|
||||
body.insert(goto_stmt, *if_stmt);
|
||||
}
|
||||
@@ -402,7 +533,8 @@ private:
|
||||
switch (label_nested_stmt->type) {
|
||||
case StatementType::If:
|
||||
// Update nested if condition
|
||||
label_nested_stmt->cond = &pools.stmt.emplace_back(Or{}, variable, label_nested_stmt->cond, &root_stmt);
|
||||
label_nested_stmt->cond =
|
||||
pool.Create(Or{}, variable, label_nested_stmt->cond, &root_stmt);
|
||||
break;
|
||||
case StatementType::Loop:
|
||||
break;
|
||||
@@ -410,7 +542,7 @@ private:
|
||||
throw LogicError("Invalid inward movement");
|
||||
}
|
||||
Tree& nested_tree{label_nested_stmt->children};
|
||||
Statement* const new_goto{&pools.stmt.emplace_back(Goto{}, variable, label, &*label_nested_stmt)};
|
||||
Statement* const new_goto{pool.Create(Goto{}, variable, label, &*label_nested_stmt)};
|
||||
return nested_tree.insert(nested_tree.begin(), *new_goto);
|
||||
}
|
||||
|
||||
@@ -424,16 +556,16 @@ private:
|
||||
Tree loop_body;
|
||||
loop_body.splice(loop_body.begin(), body, label_nested_stmt, goto_stmt);
|
||||
SanitizeNoBreaks(loop_body);
|
||||
Statement* const variable{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const loop_stmt{&pools.stmt.emplace_back(Loop{}, variable, std::move(loop_body), parent)};
|
||||
Statement* const variable{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const loop_stmt{pool.Create(Loop{}, variable, std::move(loop_body), parent)};
|
||||
UpdateTreeUp(loop_stmt);
|
||||
body.insert(goto_stmt, *loop_stmt);
|
||||
|
||||
Statement* const new_goto{&pools.stmt.emplace_back(Goto{}, variable, label, loop_stmt)};
|
||||
Statement* const new_goto{pool.Create(Goto{}, variable, label, loop_stmt)};
|
||||
loop_stmt->children.push_front(*new_goto);
|
||||
const Node new_goto_node{loop_stmt->children.begin()};
|
||||
|
||||
Statement* const set_var{&pools.stmt.emplace_back(SetVariable{}, label_id, goto_stmt->cond, loop_stmt)};
|
||||
Statement* const set_var{pool.Create(SetVariable{}, label_id, goto_stmt->cond, loop_stmt)};
|
||||
loop_stmt->children.push_back(*set_var);
|
||||
|
||||
body.erase(goto_stmt);
|
||||
@@ -445,22 +577,22 @@ private:
|
||||
Tree& body{parent->children};
|
||||
const u32 label_id{goto_stmt->label->id};
|
||||
Statement* const goto_cond{goto_stmt->cond};
|
||||
Statement* const set_goto_var{&pools.stmt.emplace_back(SetVariable{}, label_id, goto_cond, &*parent)};
|
||||
Statement* const set_goto_var{pool.Create(SetVariable{}, label_id, goto_cond, &*parent)};
|
||||
body.insert(goto_stmt, *set_goto_var);
|
||||
|
||||
Tree if_body;
|
||||
if_body.splice(if_body.begin(), body, std::next(goto_stmt), body.end());
|
||||
if_body.pop_front();
|
||||
Statement* const cond{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const neg_cond{&pools.stmt.emplace_back(Not{}, cond, &root_stmt)};
|
||||
Statement* const if_stmt{&pools.stmt.emplace_back(If{}, neg_cond, std::move(if_body), &*parent)};
|
||||
Statement* const cond{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const neg_cond{pool.Create(Not{}, cond, &root_stmt)};
|
||||
Statement* const if_stmt{pool.Create(If{}, neg_cond, std::move(if_body), &*parent)};
|
||||
UpdateTreeUp(if_stmt);
|
||||
body.insert(goto_stmt, *if_stmt);
|
||||
|
||||
body.erase(goto_stmt);
|
||||
|
||||
Statement* const new_cond{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const new_goto{&pools.stmt.emplace_back(Goto{}, new_cond, goto_stmt->label, parent->up)};
|
||||
Statement* const new_cond{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const new_goto{pool.Create(Goto{}, new_cond, goto_stmt->label, parent->up)};
|
||||
Tree& parent_tree{parent->up->children};
|
||||
return parent_tree.insert(std::next(parent), *new_goto);
|
||||
}
|
||||
@@ -470,21 +602,21 @@ private:
|
||||
Tree& body{parent->children};
|
||||
const u32 label_id{goto_stmt->label->id};
|
||||
Statement* const goto_cond{goto_stmt->cond};
|
||||
Statement* const set_goto_var{&pools.stmt.emplace_back(SetVariable{}, label_id, goto_cond, parent)};
|
||||
Statement* const cond{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const break_stmt{&pools.stmt.emplace_back(Break{}, cond, parent)};
|
||||
Statement* const set_goto_var{pool.Create(SetVariable{}, label_id, goto_cond, parent)};
|
||||
Statement* const cond{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const break_stmt{pool.Create(Break{}, cond, parent)};
|
||||
body.insert(goto_stmt, *set_goto_var);
|
||||
body.insert(goto_stmt, *break_stmt);
|
||||
body.erase(goto_stmt);
|
||||
|
||||
const Node loop{Tree::s_iterator_to(*goto_stmt->up)};
|
||||
Statement* const new_goto_cond{&pools.stmt.emplace_back(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const new_goto{&pools.stmt.emplace_back(Goto{}, new_goto_cond, goto_stmt->label, loop->up)};
|
||||
Statement* const new_goto_cond{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||
Statement* const new_goto{pool.Create(Goto{}, new_goto_cond, goto_stmt->label, loop->up)};
|
||||
Tree& parent_tree{loop->up->children};
|
||||
return parent_tree.insert(std::next(loop), *new_goto);
|
||||
}
|
||||
|
||||
ShaderPools& pools;
|
||||
ObjectPool<Statement>& pool;
|
||||
Statement root_stmt{FunctionTag{}};
|
||||
};
|
||||
|
||||
@@ -520,11 +652,11 @@ private:
|
||||
|
||||
class TranslatePass {
|
||||
public:
|
||||
TranslatePass(ShaderPools& pools_, Environment& env_, Statement& root_stmt, IR::AbstractSyntaxList& syntax_list_, const HostTranslateInfo& host_info)
|
||||
: pools{pools_}
|
||||
, env{env_}
|
||||
, syntax_list{syntax_list_}
|
||||
{
|
||||
TranslatePass(ObjectPool<IR::Inst>& inst_pool_, ObjectPool<IR::Block>& block_pool_,
|
||||
ObjectPool<Statement>& stmt_pool_, Environment& env_, Statement& root_stmt,
|
||||
IR::AbstractSyntaxList& syntax_list_, const HostTranslateInfo& host_info)
|
||||
: stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_}, env{env_},
|
||||
syntax_list{syntax_list_} {
|
||||
Visit(root_stmt, nullptr, nullptr);
|
||||
|
||||
IR::Block& first_block{*syntax_list.front().data.block};
|
||||
@@ -542,7 +674,7 @@ private:
|
||||
if (current_block) {
|
||||
return;
|
||||
}
|
||||
current_block = &pools.block.emplace_back(pools.inst);
|
||||
current_block = block_pool.Create(inst_pool);
|
||||
auto& node{syntax_list.emplace_back()};
|
||||
node.type = IR::AbstractSyntaxNode::Type::Block;
|
||||
node.data.block = current_block;
|
||||
@@ -608,7 +740,7 @@ private:
|
||||
break;
|
||||
}
|
||||
case StatementType::Loop: {
|
||||
IR::Block* const loop_header_block{&pools.block.emplace_back(pools.inst)};
|
||||
IR::Block* const loop_header_block{block_pool.Create(inst_pool)};
|
||||
if (current_block) {
|
||||
current_block->AddBranch(loop_header_block);
|
||||
}
|
||||
@@ -616,7 +748,7 @@ private:
|
||||
header_node.type = IR::AbstractSyntaxNode::Type::Block;
|
||||
header_node.data.block = loop_header_block;
|
||||
|
||||
IR::Block* const continue_block{&pools.block.emplace_back(pools.inst)};
|
||||
IR::Block* const continue_block{block_pool.Create(inst_pool)};
|
||||
IR::Block* const merge_block{MergeBlock(parent, stmt)};
|
||||
|
||||
const size_t loop_node_index{syntax_list.size()};
|
||||
@@ -682,7 +814,7 @@ private:
|
||||
}
|
||||
case StatementType::Return: {
|
||||
ensure_block();
|
||||
IR::Block* return_block{&pools.block.emplace_back(pools.inst)};
|
||||
IR::Block* return_block{block_pool.Create(inst_pool)};
|
||||
IR::IREmitter{*return_block}.Epilogue();
|
||||
current_block->AddBranch(return_block);
|
||||
|
||||
@@ -730,10 +862,10 @@ private:
|
||||
Statement* merge_stmt{TryFindForwardBlock(stmt)};
|
||||
if (!merge_stmt) {
|
||||
// Create a merge block we can visit later
|
||||
merge_stmt = &pools.stmt.emplace_back(&dummy_flow_block, &parent);
|
||||
merge_stmt = stmt_pool.Create(&dummy_flow_block, &parent);
|
||||
parent.children.insert(std::next(Tree::s_iterator_to(stmt)), *merge_stmt);
|
||||
}
|
||||
return &pools.block.emplace_back(pools.inst);
|
||||
return block_pool.Create(inst_pool);
|
||||
}
|
||||
|
||||
void DemoteCombinationPass() {
|
||||
@@ -841,7 +973,9 @@ private:
|
||||
asl.insert(next_it_2, demote_if_node);
|
||||
}
|
||||
|
||||
ShaderPools& pools;
|
||||
ObjectPool<Statement>& stmt_pool;
|
||||
ObjectPool<IR::Inst>& inst_pool;
|
||||
ObjectPool<IR::Block>& block_pool;
|
||||
Environment& env;
|
||||
IR::AbstractSyntaxList& syntax_list;
|
||||
bool uses_demote_to_helper{};
|
||||
@@ -849,12 +983,15 @@ private:
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
IR::AbstractSyntaxList BuildASL(ShaderPools& pools, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
|
||||
GotoPass goto_pass{cfg, pools};
|
||||
IR::AbstractSyntaxList BuildASL(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
|
||||
Environment& env, Flow::CFG& cfg,
|
||||
const HostTranslateInfo& host_info) {
|
||||
ObjectPool<Statement> stmt_pool{64};
|
||||
GotoPass goto_pass{cfg, stmt_pool};
|
||||
Statement& root{goto_pass.RootStatement()};
|
||||
IR::AbstractSyntaxList syntax_list;
|
||||
TranslatePass pass{pools, env, root, syntax_list, host_info};
|
||||
pools.stmt.clear();
|
||||
TranslatePass{inst_pool, block_pool, stmt_pool, env, root, syntax_list, host_info};
|
||||
stmt_pool.ReleaseContents();
|
||||
return syntax_list;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -11,13 +8,15 @@
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
#include "shader_recompiler/object_pool.h"
|
||||
|
||||
namespace Shader {
|
||||
struct HostTranslateInfo;
|
||||
namespace Maxwell {
|
||||
|
||||
struct ShaderPools;
|
||||
[[nodiscard]] IR::AbstractSyntaxList BuildASL(ShaderPools& pools, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info);
|
||||
[[nodiscard]] IR::AbstractSyntaxList BuildASL(ObjectPool<IR::Inst>& inst_pool,
|
||||
ObjectPool<IR::Block>& block_pool, Environment& env,
|
||||
Flow::CFG& cfg, const HostTranslateInfo& host_info);
|
||||
|
||||
} // namespace Maxwell
|
||||
} // namespace Shader
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <queue>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "shader_recompiler/shader_pool.h"
|
||||
#include "shader_recompiler/exception.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/ir_emitter.h"
|
||||
@@ -235,12 +234,13 @@ void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInf
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
IR::Program TranslateProgram(ShaderPools& pools, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
|
||||
IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
|
||||
Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
|
||||
HostTranslateInfo normalized_host_info{host_info};
|
||||
normalized_host_info.ApplyDescriptorLimitPolicy();
|
||||
|
||||
IR::Program program;
|
||||
program.syntax_list = BuildASL(pools, env, cfg, host_info);
|
||||
program.syntax_list = BuildASL(inst_pool, block_pool, env, cfg, normalized_host_info);
|
||||
program.blocks = GenerateBlocks(program.syntax_list);
|
||||
program.post_order_blocks = PostOrder(program.syntax_list.front());
|
||||
program.stage = env.ShaderStage();
|
||||
@@ -410,7 +410,11 @@ void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& run
|
||||
}
|
||||
}
|
||||
|
||||
IR::Program GenerateGeometryPassthrough(ShaderPools& pools, const HostTranslateInfo& host_info, IR::Program& source_program, Shader::OutputTopology output_topology) {
|
||||
IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
|
||||
ObjectPool<IR::Block>& block_pool,
|
||||
const HostTranslateInfo& host_info,
|
||||
IR::Program& source_program,
|
||||
Shader::OutputTopology output_topology) {
|
||||
IR::Program program;
|
||||
program.stage = Stage::Geometry;
|
||||
program.output_topology = output_topology;
|
||||
@@ -422,15 +426,16 @@ IR::Program GenerateGeometryPassthrough(ShaderPools& pools, const HostTranslateI
|
||||
program.info.stores.Set(IR::Attribute::Layer, true);
|
||||
program.info.stores.Set(source_program.info.emulated_layer, false);
|
||||
|
||||
IR::Block* current_block = &pools.block.emplace_back(pools.inst);
|
||||
IR::Block* current_block = block_pool.Create(inst_pool);
|
||||
auto& node{program.syntax_list.emplace_back()};
|
||||
node.type = IR::AbstractSyntaxNode::Type::Block;
|
||||
node.data.block = current_block;
|
||||
|
||||
IR::IREmitter ir{*current_block};
|
||||
EmitGeometryPassthrough(ir, program, program.info.stores, true, source_program.info.emulated_layer);
|
||||
EmitGeometryPassthrough(ir, program, program.info.stores, true,
|
||||
source_program.info.emulated_layer);
|
||||
|
||||
IR::Block* return_block{&pools.block.emplace_back(pools.inst)};
|
||||
IR::Block* return_block{block_pool.Create(inst_pool)};
|
||||
IR::IREmitter{*return_block}.Epilogue();
|
||||
current_block->AddBranch(return_block);
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -10,7 +7,7 @@
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/program.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
|
||||
#include "shader_recompiler/object_pool.h"
|
||||
#include "shader_recompiler/runtime_info.h"
|
||||
|
||||
namespace Shader {
|
||||
@@ -19,17 +16,22 @@ struct HostTranslateInfo;
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
struct ShaderPools;
|
||||
[[nodiscard]] IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool,
|
||||
ObjectPool<IR::Block>& block_pool, Environment& env,
|
||||
Flow::CFG& cfg, const HostTranslateInfo& host_info);
|
||||
|
||||
[[nodiscard]] IR::Program TranslateProgram(ShaderPools& pools, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info);
|
||||
|
||||
[[nodiscard]] IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b, Environment& env_vertex_b);
|
||||
[[nodiscard]] IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b,
|
||||
Environment& env_vertex_b);
|
||||
|
||||
void ConvertLegacyToGeneric(IR::Program& program, const RuntimeInfo& runtime_info);
|
||||
|
||||
// Maxwell v1 and older Nvidia cards don't support setting gl_Layer from non-geometry stages.
|
||||
// This creates a workaround by setting the layer as a generic output and creating a
|
||||
// passthrough geometry shader that reads the generic and sets the layer.
|
||||
[[nodiscard]] IR::Program GenerateGeometryPassthrough(ShaderPools& pools, const HostTranslateInfo& host_info, IR::Program& source_program, Shader::OutputTopology output_topology);
|
||||
[[nodiscard]] IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
|
||||
ObjectPool<IR::Block>& block_pool,
|
||||
const HostTranslateInfo& host_info,
|
||||
IR::Program& source_program,
|
||||
Shader::OutputTopology output_topology);
|
||||
|
||||
} // namespace Shader::Maxwell
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace Shader {
|
||||
|
||||
template <typename T>
|
||||
requires std::is_destructible_v<T>
|
||||
class ObjectPool {
|
||||
public:
|
||||
explicit ObjectPool(size_t chunk_size = 8192) : new_chunk_size{chunk_size} {
|
||||
node = &chunks.emplace_back(new_chunk_size);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
requires std::is_constructible_v<T, Args...>
|
||||
[[nodiscard]] T* Create(Args&&... args) {
|
||||
return std::construct_at(Memory(), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void ReleaseContents() {
|
||||
if (chunks.empty()) {
|
||||
return;
|
||||
}
|
||||
Chunk& root{chunks.front()};
|
||||
if (root.used_objects == root.num_objects) {
|
||||
// Root chunk has been filled, squash allocations into it
|
||||
const size_t total_objects{root.num_objects + new_chunk_size * (chunks.size() - 1)};
|
||||
chunks.clear();
|
||||
chunks.emplace_back(total_objects);
|
||||
} else {
|
||||
root.Release();
|
||||
chunks.resize(1);
|
||||
}
|
||||
chunks.shrink_to_fit();
|
||||
node = &chunks.front();
|
||||
}
|
||||
|
||||
private:
|
||||
struct NonTrivialDummy {
|
||||
NonTrivialDummy() noexcept {}
|
||||
};
|
||||
|
||||
union Storage {
|
||||
Storage() noexcept {}
|
||||
~Storage() noexcept {}
|
||||
|
||||
NonTrivialDummy dummy{};
|
||||
T object;
|
||||
};
|
||||
|
||||
struct Chunk {
|
||||
explicit Chunk() = default;
|
||||
explicit Chunk(size_t size)
|
||||
: num_objects{size}, storage{std::make_unique<Storage[]>(size)} {}
|
||||
|
||||
Chunk& operator=(Chunk&& rhs) noexcept {
|
||||
Release();
|
||||
used_objects = std::exchange(rhs.used_objects, 0);
|
||||
num_objects = std::exchange(rhs.num_objects, 0);
|
||||
storage = std::move(rhs.storage);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Chunk(Chunk&& rhs) noexcept
|
||||
: used_objects{std::exchange(rhs.used_objects, 0)},
|
||||
num_objects{std::exchange(rhs.num_objects, 0)}, storage{std::move(rhs.storage)} {}
|
||||
|
||||
~Chunk() {
|
||||
Release();
|
||||
}
|
||||
|
||||
void Release() {
|
||||
std::destroy_n(storage.get(), used_objects);
|
||||
used_objects = 0;
|
||||
}
|
||||
|
||||
size_t used_objects{};
|
||||
size_t num_objects{};
|
||||
std::unique_ptr<Storage[]> storage;
|
||||
};
|
||||
|
||||
[[nodiscard]] T* Memory() {
|
||||
Chunk* const chunk{FreeChunk()};
|
||||
return &chunk->storage[chunk->used_objects++].object;
|
||||
}
|
||||
|
||||
[[nodiscard]] Chunk* FreeChunk() {
|
||||
if (node->used_objects != node->num_objects) {
|
||||
return node;
|
||||
}
|
||||
node = &chunks.emplace_back(new_chunk_size);
|
||||
return node;
|
||||
}
|
||||
|
||||
Chunk* node{};
|
||||
std::vector<Chunk> chunks;
|
||||
size_t new_chunk_size{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
@@ -1,134 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/container/stable_vector.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
|
||||
namespace Shader::Maxwell {
|
||||
|
||||
struct Statement;
|
||||
|
||||
// Use normal_link because we are not guaranteed to destroy the tree in order
|
||||
using ListBaseHook = boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>;
|
||||
using Tree = boost::intrusive::list<Statement,
|
||||
// Allow using Statement without a definition
|
||||
boost::intrusive::base_hook<ListBaseHook>,
|
||||
// Avoid linear complexity on splice, size is never called
|
||||
boost::intrusive::constant_time_size<false>>;
|
||||
using Node = Tree::iterator;
|
||||
|
||||
enum class StatementType {
|
||||
Code,
|
||||
Goto,
|
||||
Label,
|
||||
If,
|
||||
Loop,
|
||||
Break,
|
||||
Return,
|
||||
Kill,
|
||||
Unreachable,
|
||||
Function,
|
||||
Identity,
|
||||
Not,
|
||||
Or,
|
||||
SetVariable,
|
||||
SetIndirectBranchVariable,
|
||||
Variable,
|
||||
IndirectBranchCond,
|
||||
};
|
||||
|
||||
[[nodiscard]] inline bool HasChildren(StatementType type) {
|
||||
switch (type) {
|
||||
case StatementType::If:
|
||||
case StatementType::Loop:
|
||||
case StatementType::Function:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct Goto {};
|
||||
struct Label {};
|
||||
struct If {};
|
||||
struct Loop {};
|
||||
struct Break {};
|
||||
struct Return {};
|
||||
struct Kill {};
|
||||
struct Unreachable {};
|
||||
struct FunctionTag {};
|
||||
struct Identity {};
|
||||
struct Not {};
|
||||
struct Or {};
|
||||
struct SetVariable {};
|
||||
struct SetIndirectBranchVariable {};
|
||||
struct Variable {};
|
||||
struct IndirectBranchCond {};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26495) // Always initialize a member variable, expected in Statement
|
||||
#endif
|
||||
struct Statement : ListBaseHook {
|
||||
Statement(const Flow::Block* block_, Statement* up_) : block{block_}, up{up_}, type{StatementType::Code} {}
|
||||
Statement(Goto, Statement* cond_, Node label_, Statement* up_) : label{label_}, cond{cond_}, up{up_}, type{StatementType::Goto} {}
|
||||
Statement(Label, u32 id_, Statement* up_) : id{id_}, up{up_}, type{StatementType::Label} {}
|
||||
Statement(If, Statement* cond_, Tree&& children_, Statement* up_) : children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::If} {}
|
||||
Statement(Loop, Statement* cond_, Tree&& children_, Statement* up_) : children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::Loop} {}
|
||||
Statement(Break, Statement* cond_, Statement* up_) : cond{cond_}, up{up_}, type{StatementType::Break} {}
|
||||
Statement(Return, Statement* up_) : up{up_}, type{StatementType::Return} {}
|
||||
Statement(Kill, Statement* up_) : up{up_}, type{StatementType::Kill} {}
|
||||
Statement(Unreachable, Statement* up_) : up{up_}, type{StatementType::Unreachable} {}
|
||||
Statement(FunctionTag) : children{}, type{StatementType::Function} {}
|
||||
Statement(Identity, IR::Condition cond_, Statement* up_) : guest_cond{cond_}, up{up_}, type{StatementType::Identity} {}
|
||||
Statement(Not, Statement* op_, Statement* up_) : op{op_}, up{up_}, type{StatementType::Not} {}
|
||||
Statement(Or, Statement* op_a_, Statement* op_b_, Statement* up_) : op_a{op_a_}, op_b{op_b_}, up{up_}, type{StatementType::Or} {}
|
||||
Statement(SetVariable, u32 id_, Statement* op_, Statement* up_) : op{op_}, id{id_}, up{up_}, type{StatementType::SetVariable} {}
|
||||
Statement(SetIndirectBranchVariable, IR::Reg branch_reg_, s32 branch_offset_, Statement* up_) : branch_offset{branch_offset_}, branch_reg{branch_reg_}, up{up_}, type{StatementType::SetIndirectBranchVariable} {}
|
||||
Statement(Variable, u32 id_, Statement* up_) : id{id_}, up{up_}, type{StatementType::Variable} {}
|
||||
Statement(IndirectBranchCond, u32 location_, Statement* up_) : location{location_}, up{up_}, type{StatementType::IndirectBranchCond} {}
|
||||
~Statement() {
|
||||
if (HasChildren(type)) {
|
||||
std::destroy_at(&children);
|
||||
}
|
||||
}
|
||||
union {
|
||||
const Flow::Block* block;
|
||||
Node label;
|
||||
Tree children;
|
||||
IR::Condition guest_cond;
|
||||
Statement* op;
|
||||
Statement* op_a;
|
||||
u32 location;
|
||||
s32 branch_offset;
|
||||
};
|
||||
union {
|
||||
Statement* cond;
|
||||
Statement* op_b;
|
||||
u32 id;
|
||||
IR::Reg branch_reg;
|
||||
};
|
||||
Statement* up{};
|
||||
StatementType type;
|
||||
};
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
struct ShaderPools {
|
||||
void clear() {
|
||||
flow_block.clear();
|
||||
block.clear();
|
||||
inst.clear();
|
||||
stmt.clear();
|
||||
}
|
||||
boost::container::stable_vector<Shader::IR::Inst> inst{};
|
||||
boost::container::stable_vector<Shader::IR::Block> block{};
|
||||
boost::container::stable_vector<Shader::Maxwell::Flow::Block> flow_block{};
|
||||
boost::container::stable_vector<Shader::Maxwell::Statement> stmt;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -158,6 +158,8 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/vk_compute_pass.h
|
||||
renderer_vulkan/vk_compute_pipeline.cpp
|
||||
renderer_vulkan/vk_compute_pipeline.h
|
||||
renderer_vulkan/vk_descriptor_buffer.cpp
|
||||
renderer_vulkan/vk_descriptor_buffer.h
|
||||
renderer_vulkan/vk_descriptor_pool.cpp
|
||||
renderer_vulkan/vk_descriptor_pool.h
|
||||
renderer_vulkan/vk_fence_manager.cpp
|
||||
|
||||
@@ -571,7 +571,11 @@ void BufferCache<P>::AccumulateFlushes() {
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
|
||||
return (!async_buffers.empty() && async_buffers.front().has_value());
|
||||
if (async_buffers.empty()) {
|
||||
return false;
|
||||
}
|
||||
return async_buffers.front().has_value() ||
|
||||
!pending_downloads.front().unified_copies.empty();
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -579,6 +583,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
||||
AccumulateFlushes();
|
||||
|
||||
if (committed_gpu_modified_ranges.empty()) {
|
||||
pending_downloads.emplace_back();
|
||||
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
||||
return;
|
||||
}
|
||||
@@ -638,27 +643,83 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
||||
}
|
||||
committed_gpu_modified_ranges.clear();
|
||||
if (downloads.empty()) {
|
||||
pending_downloads.emplace_back();
|
||||
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
||||
return;
|
||||
}
|
||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
|
||||
boost::container::small_vector<BufferCopy, 4> normalized_copies;
|
||||
runtime.PreCopyBarrier();
|
||||
|
||||
struct QueuedUnifiedCopy {
|
||||
u64 window;
|
||||
BufferId buffer_id;
|
||||
boost::container::small_vector<BufferCopy, 16> copies;
|
||||
};
|
||||
|
||||
AsyncDownloadBatch batch;
|
||||
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> staging_downloads;
|
||||
boost::container::small_vector<QueuedUnifiedCopy, 4> unified_copy_queue;
|
||||
boost::container::small_vector<u64, 4> window_ids;
|
||||
UnifiedWindowGroups groups;
|
||||
u64 staging_size_bytes = 0;
|
||||
for (auto& [copy, buffer_id] : downloads) {
|
||||
copy.dst_offset += download_staging.offset;
|
||||
const std::array copies{copy};
|
||||
BufferCopy second_copy{copy};
|
||||
Buffer& buffer = slot_buffers[buffer_id];
|
||||
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
|
||||
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset);
|
||||
const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
|
||||
bool unified = false;
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
if (runtime.HasUnifiedMemory()) {
|
||||
window_ids.clear();
|
||||
groups.clear();
|
||||
unified = ResolveUnifiedWindows(orig_device_addr, copy.src_offset, copy.size,
|
||||
window_ids, groups);
|
||||
}
|
||||
}
|
||||
BufferCopy record{copy};
|
||||
record.src_offset = static_cast<size_t>(orig_device_addr);
|
||||
if (unified) {
|
||||
async_downloads.Add(orig_device_addr, copy.size);
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
unified_copy_queue.push_back(
|
||||
QueuedUnifiedCopy{window_ids[i], buffer_id, std::move(groups[i])});
|
||||
}
|
||||
batch.unified_copies.push_back(record);
|
||||
continue;
|
||||
}
|
||||
copy.dst_offset = staging_size_bytes;
|
||||
constexpr u64 align = 64ULL;
|
||||
staging_size_bytes += (copy.size + align - 1) & ~(align - 1ULL);
|
||||
staging_downloads.push_back({copy, buffer_id});
|
||||
}
|
||||
|
||||
std::optional<Async_Buffer> download_staging;
|
||||
if (!staging_downloads.empty()) {
|
||||
download_staging = runtime.DownloadStagingBuffer(staging_size_bytes, true);
|
||||
}
|
||||
runtime.PreCopyBarrier();
|
||||
for (auto& [copy, buffer_id] : staging_downloads) {
|
||||
copy.dst_offset += download_staging->offset;
|
||||
const std::array copies{copy};
|
||||
Buffer& buffer = slot_buffers[buffer_id];
|
||||
BufferCopy record{copy};
|
||||
record.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
|
||||
const DAddr orig_device_addr = static_cast<DAddr>(record.src_offset);
|
||||
async_downloads.Add(orig_device_addr, copy.size);
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
|
||||
normalized_copies.push_back(second_copy);
|
||||
runtime.CopyBuffer(download_staging->buffer, buffer, copies, false);
|
||||
batch.staging_copies.push_back(record);
|
||||
}
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
for (const auto& queued : unified_copy_queue) {
|
||||
const std::span<const BufferCopy> group_span(queued.copies.data(),
|
||||
queued.copies.size());
|
||||
runtime.CopyToUnifiedMemory(queued.window, slot_buffers[queued.buffer_id], group_span);
|
||||
}
|
||||
if (!unified_copy_queue.empty()) {
|
||||
runtime.UnifiedMemoryHostBarrier();
|
||||
}
|
||||
}
|
||||
runtime.PostCopyBarrier();
|
||||
pending_downloads.emplace_back(std::move(normalized_copies));
|
||||
async_buffers.emplace_back(download_staging);
|
||||
pending_downloads.emplace_back(std::move(batch));
|
||||
async_buffers.emplace_back(std::move(download_staging));
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -673,32 +734,49 @@ void BufferCache<P>::PopAsyncFlushes() {
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::PopAsyncBuffers() {
|
||||
if (async_buffers.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!async_buffers.front().has_value()) {
|
||||
struct Writeback {
|
||||
DAddr addr;
|
||||
const u8* src;
|
||||
u64 size;
|
||||
};
|
||||
boost::container::small_vector<Writeback, 8> writebacks;
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
if (async_buffers.empty()) {
|
||||
return;
|
||||
}
|
||||
auto& batch = pending_downloads.front();
|
||||
auto& async_buffer = async_buffers.front();
|
||||
if (async_buffer.has_value()) {
|
||||
const u8* base = async_buffer->mapped_span.data();
|
||||
const size_t base_offset = async_buffer->offset;
|
||||
for (const auto& copy : batch.staging_copies) {
|
||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||
const u64 dst_offset = copy.dst_offset - base_offset;
|
||||
const u8* read_mapped_memory = base + dst_offset;
|
||||
async_downloads.ForEachInRange(
|
||||
device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
|
||||
writebacks.push_back(
|
||||
{start, &read_mapped_memory[start - device_addr], end - start});
|
||||
});
|
||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
||||
gpu_modified_ranges.Subtract(start, end - start);
|
||||
});
|
||||
}
|
||||
async_buffers_death_ring.emplace_back(*async_buffer);
|
||||
}
|
||||
for (const auto& copy : batch.unified_copies) {
|
||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
||||
gpu_modified_ranges.Subtract(start, end - start);
|
||||
});
|
||||
}
|
||||
async_buffers.pop_front();
|
||||
return;
|
||||
pending_downloads.pop_front();
|
||||
}
|
||||
auto& downloads = pending_downloads.front();
|
||||
auto& async_buffer = async_buffers.front();
|
||||
u8* base = async_buffer->mapped_span.data();
|
||||
const size_t base_offset = async_buffer->offset;
|
||||
for (const auto& copy : downloads) {
|
||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||
const u64 dst_offset = copy.dst_offset - base_offset;
|
||||
const u8* read_mapped_memory = base + dst_offset;
|
||||
async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
|
||||
device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr],
|
||||
end - start);
|
||||
});
|
||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
||||
gpu_modified_ranges.Subtract(start, end - start);
|
||||
});
|
||||
for (const auto& wb : writebacks) {
|
||||
device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size);
|
||||
}
|
||||
async_buffers_death_ring.emplace_back(*async_buffer);
|
||||
async_buffers.pop_front();
|
||||
pending_downloads.pop_front();
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -1699,6 +1777,98 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::ResolveUnifiedWindows(
|
||||
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
|
||||
[[maybe_unused]] u64 size, [[maybe_unused]] boost::container::small_vector<u64, 4>& window_ids,
|
||||
[[maybe_unused]] UnifiedWindowGroups& groups) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
const u8* const physical_base = device_memory.GetPhysicalBase();
|
||||
const u64 unified_base = runtime.UnifiedMemoryBase();
|
||||
const u64 unified_size = runtime.UnifiedMemorySize();
|
||||
const u64 window_size = runtime.UnifiedMemoryWindowSize();
|
||||
if (window_size == 0) {
|
||||
return false;
|
||||
}
|
||||
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
if (window_ids[i] == window) {
|
||||
return groups[i];
|
||||
}
|
||||
}
|
||||
window_ids.push_back(window);
|
||||
groups.emplace_back();
|
||||
return groups.back();
|
||||
};
|
||||
u64 downloaded = 0;
|
||||
while (downloaded < size) {
|
||||
const DAddr page_addr = device_addr + downloaded;
|
||||
const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
|
||||
if (ptr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
|
||||
u64 chunk = (std::min)(size - downloaded,
|
||||
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
|
||||
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
|
||||
if (phys_offset < unified_base || phys_offset - unified_base + chunk > unified_size) {
|
||||
return false;
|
||||
}
|
||||
const u64 relative = phys_offset - unified_base;
|
||||
const u64 window = relative / window_size;
|
||||
const u64 local_offset = relative % window_size;
|
||||
chunk = (std::min)(chunk, window_size - local_offset);
|
||||
auto& group = group_for(window);
|
||||
if (!group.empty()) {
|
||||
BufferCopy& last = group.back();
|
||||
if (last.src_offset + last.size == buffer_offset + downloaded &&
|
||||
last.dst_offset + last.size == local_offset) {
|
||||
last.size += chunk;
|
||||
downloaded += chunk;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
group.push_back(BufferCopy{
|
||||
.src_offset = buffer_offset + downloaded,
|
||||
.dst_offset = local_offset,
|
||||
.size = chunk,
|
||||
});
|
||||
downloaded += chunk;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
|
||||
[[maybe_unused]] std::span<BufferCopy> copies) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
boost::container::small_vector<u64, 4> window_ids;
|
||||
UnifiedWindowGroups groups;
|
||||
for (const BufferCopy& copy : copies) {
|
||||
if (!ResolveUnifiedWindows(buffer.CpuAddr() + copy.src_offset, copy.src_offset,
|
||||
copy.size, window_ids, groups)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const BufferCopy& copy : copies) {
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
}
|
||||
runtime.PreCopyBarrier();
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
|
||||
runtime.CopyToUnifiedMemory(window_ids[i], buffer, group_span);
|
||||
}
|
||||
runtime.UnifiedMemoryHostBarrier();
|
||||
runtime.Finish();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
|
||||
[[maybe_unused]] u64 total_size_bytes,
|
||||
@@ -1802,6 +1972,12 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
|
||||
}
|
||||
|
||||
if constexpr (USE_MEMORY_MAPS) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
if (runtime.HasUnifiedMemory() &&
|
||||
TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
|
||||
const u8* const mapped_memory = download_staging.mapped_span.data();
|
||||
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
|
||||
|
||||
@@ -180,6 +180,7 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
|
||||
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
|
||||
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
|
||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
|
||||
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
|
||||
|
||||
#ifdef YUZU_LEGACY
|
||||
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
|
||||
@@ -443,6 +444,15 @@ private:
|
||||
|
||||
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
|
||||
|
||||
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
|
||||
|
||||
using UnifiedWindowGroups =
|
||||
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4>;
|
||||
|
||||
bool ResolveUnifiedWindows(DAddr device_addr, u64 buffer_offset, u64 size,
|
||||
boost::container::small_vector<u64, 4>& window_ids,
|
||||
UnifiedWindowGroups& groups);
|
||||
|
||||
void DownloadBufferMemory(Buffer& buffer_id);
|
||||
|
||||
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
|
||||
@@ -498,9 +508,14 @@ private:
|
||||
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
|
||||
|
||||
// Async Buffers
|
||||
struct AsyncDownloadBatch {
|
||||
boost::container::small_vector<BufferCopy, 4> staging_copies;
|
||||
boost::container::small_vector<BufferCopy, 4> unified_copies;
|
||||
};
|
||||
|
||||
Common::OverlapRangeSet<DAddr> async_downloads;
|
||||
std::deque<std::optional<Async_Buffer>> async_buffers;
|
||||
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
|
||||
std::deque<AsyncDownloadBatch> pending_downloads;
|
||||
std::optional<Async_Buffer> current_buffer;
|
||||
|
||||
std::deque<Async_Buffer> async_buffers_death_ring;
|
||||
|
||||
@@ -195,6 +195,7 @@ private:
|
||||
void ReleaseThreadFunc(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("GPUFencingThread");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
|
||||
TFence current_fence;
|
||||
std::deque<std::function<void()>> current_operations;
|
||||
|
||||
+58
-30
@@ -10,6 +10,7 @@
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/settings.h"
|
||||
@@ -39,6 +40,19 @@
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
namespace {
|
||||
constexpr u64 GpuClockMultiplier(Settings::GpuClock clock) {
|
||||
switch (clock) {
|
||||
case Settings::GpuClock::Boost:
|
||||
return 256;
|
||||
case Settings::GpuClock::Overclock:
|
||||
return 512;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
struct GPU::Impl {
|
||||
explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_)
|
||||
: system{system_}
|
||||
@@ -116,7 +130,7 @@ struct GPU::Impl {
|
||||
[[nodiscard]] u64 RequestSyncOperation(Func&& action) {
|
||||
std::unique_lock lck{sync_request_mutex};
|
||||
const u64 fence = ++last_sync_fence;
|
||||
sync_requests.emplace_back(action);
|
||||
sync_requests.emplace_back(std::forward<Func>(action));
|
||||
return fence;
|
||||
}
|
||||
|
||||
@@ -145,14 +159,8 @@ struct GPU::Impl {
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 GetTicks() const {
|
||||
u64 gpu_tick = system.CoreTiming().GetGPUTicks();
|
||||
Settings::GpuOverclock overclock = Settings::values.fast_gpu_time.GetValue();
|
||||
|
||||
if (overclock != Settings::GpuOverclock::Normal) {
|
||||
gpu_tick /= 256 * u64(overclock);
|
||||
}
|
||||
|
||||
return gpu_tick;
|
||||
const u64 gpu_tick = system.CoreTiming().GetGPUTicks();
|
||||
return gpu_tick / GpuClockMultiplier(Settings::values.gpu_clock.GetValue());
|
||||
}
|
||||
|
||||
void RendererFrameEndNotify() {
|
||||
@@ -225,9 +233,9 @@ struct GPU::Impl {
|
||||
}
|
||||
|
||||
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers, std::vector<Service::Nvidia::NvFence>&& fences) {
|
||||
size_t num_fences{fences.size()};
|
||||
const size_t num_fences{fences.size()};
|
||||
size_t current_request_counter{};
|
||||
{
|
||||
if (num_fences != 0) {
|
||||
std::unique_lock<std::mutex> lk(request_swap_mutex);
|
||||
if (free_swap_counters.empty()) {
|
||||
current_request_counter = request_swap_counters.size();
|
||||
@@ -238,27 +246,42 @@ struct GPU::Impl {
|
||||
free_swap_counters.pop_front();
|
||||
}
|
||||
}
|
||||
const auto wait_fence = RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] {
|
||||
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
|
||||
if (num_fences == 0) {
|
||||
renderer->Composite(layers);
|
||||
}
|
||||
const auto executer = [this, current_request_counter, layers_copy = layers]() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(request_swap_mutex);
|
||||
if (--request_swap_counters[current_request_counter] != 0) {
|
||||
return;
|
||||
}
|
||||
free_swap_counters.push_back(current_request_counter);
|
||||
pending_composite_fence = RequestSyncOperation(
|
||||
[this, current_request_counter, num_fences, composite_layers = std::move(layers),
|
||||
composite_fences = std::move(fences)] {
|
||||
if (num_fences == 0) {
|
||||
renderer->Composite(composite_layers);
|
||||
return;
|
||||
}
|
||||
renderer->Composite(layers_copy);
|
||||
};
|
||||
for (size_t i = 0; i < num_fences; i++) {
|
||||
syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
|
||||
}
|
||||
});
|
||||
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
|
||||
const auto executer = [this, current_request_counter, composite_layers]() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(request_swap_mutex);
|
||||
if (--request_swap_counters[current_request_counter] != 0) {
|
||||
return;
|
||||
}
|
||||
free_swap_counters.push_back(current_request_counter);
|
||||
}
|
||||
renderer->Composite(composite_layers);
|
||||
};
|
||||
for (size_t i = 0; i < num_fences; i++) {
|
||||
syncpoint_manager.RegisterGuestAction(composite_fences[i].id,
|
||||
composite_fences[i].value, executer);
|
||||
}
|
||||
});
|
||||
gpu_thread.TickGPU(is_async);
|
||||
WaitForSyncOperation(wait_fence);
|
||||
}
|
||||
|
||||
void WaitForComposite() {
|
||||
const u64 fence = pending_composite_fence;
|
||||
if (fence == 0) {
|
||||
return;
|
||||
}
|
||||
pending_composite_fence = 0;
|
||||
if (shutting_down.load(std::memory_order_relaxed)) {
|
||||
return;
|
||||
}
|
||||
WaitForSyncOperation(fence);
|
||||
}
|
||||
|
||||
std::vector<u8> GetAppletCaptureBuffer() {
|
||||
@@ -311,6 +334,7 @@ struct GPU::Impl {
|
||||
std::deque<size_t> free_swap_counters;
|
||||
std::deque<size_t> request_swap_counters;
|
||||
std::mutex request_swap_mutex;
|
||||
u64 pending_composite_fence{};
|
||||
};
|
||||
|
||||
GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
|
||||
@@ -428,6 +452,10 @@ void GPU::RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
|
||||
impl->RequestComposite(std::move(layers), std::move(fences));
|
||||
}
|
||||
|
||||
void GPU::WaitForComposite() {
|
||||
impl->WaitForComposite();
|
||||
}
|
||||
|
||||
std::vector<u8> GPU::GetAppletCaptureBuffer() {
|
||||
return impl->GetAppletCaptureBuffer();
|
||||
}
|
||||
|
||||
@@ -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 2018 yuzu Emulator Project
|
||||
@@ -218,6 +218,8 @@ public:
|
||||
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
|
||||
std::vector<Service::Nvidia::NvFence>&& fences);
|
||||
|
||||
void WaitForComposite();
|
||||
|
||||
std::vector<u8> GetAppletCaptureBuffer();
|
||||
|
||||
/// Performs any additional setup necessary in order to begin GPU emulation.
|
||||
|
||||
@@ -30,6 +30,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
|
||||
thread = std::jthread([&](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("GPU");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
system.RegisterHostThread();
|
||||
|
||||
auto current_context = context.Acquire();
|
||||
|
||||
@@ -77,14 +77,8 @@ uvec4 local_buff;
|
||||
uvec4 color_endpoint_data;
|
||||
int color_bitsread = 0;
|
||||
|
||||
// Global "vector" to be pushed into when decoding
|
||||
// At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode
|
||||
// At most will require BLOCK_WIDTH x BLOCK_HEIGHT x 2 in dual plane mode
|
||||
// So the maximum would be 144 (12 x 12) elements, x 2 for two planes
|
||||
#define DIVCEIL(number, divisor) (number + divisor - 1) / divisor
|
||||
#define ARRAY_NUM_ELEMENTS 144
|
||||
#define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4)
|
||||
uint result_vector[ARRAY_NUM_ELEMENTS * 2];
|
||||
#define MAX_WEIGHT_VALUES 64
|
||||
uint result_vector[MAX_WEIGHT_VALUES];
|
||||
|
||||
int result_index = 0;
|
||||
uint result_vector_max_index;
|
||||
@@ -492,7 +486,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, o
|
||||
A = ReplicateBitTo9((bitval & 1));
|
||||
switch (encoding) {
|
||||
case JUST_BITS:
|
||||
color_values[++out_index] = FastReplicateTo8(bitval, bitlen);
|
||||
color_values[out_index++] = FastReplicateTo8(bitval, bitlen);
|
||||
break;
|
||||
case TRIT: {
|
||||
D = QuintTritValue(val);
|
||||
@@ -571,7 +565,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, o
|
||||
uint T = (D * C) + B;
|
||||
T ^= A;
|
||||
T = (A & 0x80) | (T >> 2);
|
||||
color_values[++out_index] = T;
|
||||
color_values[out_index++] = T;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -753,12 +747,12 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, ui
|
||||
#define READ_UINT_VALUES(N) \
|
||||
uvec4 V[2]; \
|
||||
for (uint i = 0; i < N; i++) { \
|
||||
V[i / 4][i % 4] = color_values[++colvals_index]; \
|
||||
V[i / 4][i % 4] = color_values[colvals_index++]; \
|
||||
}
|
||||
#define READ_INT_VALUES(N) \
|
||||
ivec4 V[2]; \
|
||||
for (uint i = 0; i < N; i++) { \
|
||||
V[i / 4][i % 4] = int(color_values[++colvals_index]); \
|
||||
V[i / 4][i % 4] = int(color_values[colvals_index++]); \
|
||||
}
|
||||
|
||||
switch (color_endpoint_mode) {
|
||||
@@ -1225,6 +1219,10 @@ void DecompressBlock(ivec3 coord) {
|
||||
FillError(coord);
|
||||
return;
|
||||
}
|
||||
if (GetNumWeightValues(size_params, dual_plane) > MAX_WEIGHT_VALUES) {
|
||||
FillError(coord);
|
||||
return;
|
||||
}
|
||||
uint partition_index = 1;
|
||||
uvec4 color_endpoint_mode = uvec4(0);
|
||||
uint ced_pointer = 0;
|
||||
@@ -1239,6 +1237,10 @@ void DecompressBlock(ivec3 coord) {
|
||||
const uint base_mode = base_cem & 3;
|
||||
const uint max_weight = DecodeMaxWeight(mode);
|
||||
const uint weight_bits = GetPackedBitSize(size_params, dual_plane, max_weight);
|
||||
if (weight_bits < 24 || weight_bits > 96) {
|
||||
FillError(coord);
|
||||
return;
|
||||
}
|
||||
uint remaining_bits = 128 - weight_bits - total_bitsread;
|
||||
uint extra_cem_bits = 0;
|
||||
if (base_mode > 0) {
|
||||
@@ -1253,6 +1255,7 @@ void DecompressBlock(ivec3 coord) {
|
||||
extra_cem_bits += 8;
|
||||
break;
|
||||
default:
|
||||
FillError(coord);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1262,6 +1265,7 @@ void DecompressBlock(ivec3 coord) {
|
||||
if (remaining_bits > 128) {
|
||||
// Bad data, more remaining bits than 4 bytes
|
||||
// return early
|
||||
FillError(coord);
|
||||
return;
|
||||
}
|
||||
// Read color data...
|
||||
@@ -1384,11 +1388,7 @@ void DecompressBlock(ivec3 coord) {
|
||||
p = Cf / 65535.0f;
|
||||
}
|
||||
|
||||
#ifdef VULKAN
|
||||
imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar);
|
||||
#else
|
||||
imageStore(dest_image, coord + ivec3(i, j, 0), clamp(p, 0.0f, 1.0f).gbar);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +261,7 @@ struct BufferCacheParams {
|
||||
|
||||
// TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads
|
||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
|
||||
static constexpr bool USE_UNIFIED_MEMORY = false;
|
||||
};
|
||||
|
||||
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
|
||||
|
||||
@@ -43,6 +43,7 @@ using Shader::Backend::GLASM::EmitGLASM;
|
||||
using Shader::Backend::GLSL::EmitGLSL;
|
||||
using Shader::Backend::SPIRV::EmitSPIRV;
|
||||
using Shader::Maxwell::ConvertLegacyToGeneric;
|
||||
using Shader::Maxwell::GenerateGeometryPassthrough;
|
||||
using Shader::Maxwell::MergeDualVertexPrograms;
|
||||
using Shader::Maxwell::TranslateProgram;
|
||||
using VideoCommon::ComputeEnvironment;
|
||||
@@ -315,7 +316,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
|
||||
ComputePipelineKey key;
|
||||
file.read(reinterpret_cast<char*>(&key), sizeof(key));
|
||||
queue_work([this, key, env_ = std::move(env), &state, &callback](Context* ctx) mutable {
|
||||
ctx->pools.clear();
|
||||
ctx->pools.ReleaseContents();
|
||||
auto pipeline{CreateComputePipeline(ctx->pools, key, env_, true)};
|
||||
std::scoped_lock lock{state.mutex};
|
||||
if (pipeline) {
|
||||
@@ -336,7 +337,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
|
||||
for (auto& env : envs_) {
|
||||
env_ptrs.push_back(&env);
|
||||
}
|
||||
ctx->pools.clear();
|
||||
ctx->pools.ReleaseContents();
|
||||
auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false, true)};
|
||||
std::scoped_lock lock{state.mutex};
|
||||
if (pipeline) {
|
||||
@@ -446,8 +447,9 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline() {
|
||||
GraphicsEnvironments environments;
|
||||
GetGraphicsEnvironments(environments, graphics_key.unique_hashes);
|
||||
|
||||
main_pools.clear();
|
||||
auto pipeline{CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(), use_asynchronous_shaders)};
|
||||
main_pools.ReleaseContents();
|
||||
auto pipeline{CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(),
|
||||
use_asynchronous_shaders)};
|
||||
if (!pipeline || shader_cache_filename.empty()) {
|
||||
return pipeline;
|
||||
}
|
||||
@@ -461,7 +463,10 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline() {
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(Shader::Maxwell::ShaderPools& pools, const GraphicsPipelineKey& key, std::span<Shader::Environment* const> envs, bool use_shader_workers, bool force_context_flush) try {
|
||||
std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
|
||||
ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key,
|
||||
std::span<Shader::Environment* const> envs, bool use_shader_workers,
|
||||
bool force_context_flush) try {
|
||||
auto hash = key.Hash();
|
||||
LOG_INFO(Render_OpenGL, "{:#016x}", hash);
|
||||
size_t env_index{};
|
||||
@@ -474,10 +479,12 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(Shader::Ma
|
||||
Shader::IR::Program* layer_source_program{};
|
||||
|
||||
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
const bool is_emulated_stage = layer_source_program != nullptr && index == u32(Maxwell::ShaderType::Geometry);
|
||||
const bool is_emulated_stage = layer_source_program != nullptr
|
||||
&& index == u32(Maxwell::ShaderType::Geometry);
|
||||
if (key.unique_hashes[index] == 0 && is_emulated_stage) {
|
||||
auto topology = MaxwellToOutputTopology(key.gs_input_topology);
|
||||
programs[index] = Shader::Maxwell::GenerateGeometryPassthrough(pools, host_info, *layer_source_program, topology);
|
||||
programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
|
||||
*layer_source_program, topology);
|
||||
continue;
|
||||
}
|
||||
if (key.unique_hashes[index] == 0) {
|
||||
@@ -495,13 +502,13 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(Shader::Ma
|
||||
|
||||
if (!uses_vertex_a || index != 1) {
|
||||
// Normal path
|
||||
programs[index] = TranslateProgram(pools, env, cfg, host_info);
|
||||
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
|
||||
|
||||
total_storage_buffers += Shader::NumDescriptors(programs[index].info.storage_buffers_descriptors);
|
||||
} else {
|
||||
// VertexB path when VertexA is present.
|
||||
auto& program_va{programs[0]};
|
||||
auto program_vb{TranslateProgram(pools, env, cfg, host_info)};
|
||||
auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
|
||||
total_storage_buffers += Shader::NumDescriptors(program_vb.info.storage_buffers_descriptors);
|
||||
programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
|
||||
}
|
||||
@@ -568,7 +575,7 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
|
||||
ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
|
||||
env.SetCachedSize(shader->size_bytes);
|
||||
|
||||
main_pools.clear();
|
||||
main_pools.ReleaseContents();
|
||||
auto pipeline{CreateComputePipeline(main_pools, key, env)};
|
||||
if (!pipeline || shader_cache_filename.empty()) {
|
||||
return pipeline;
|
||||
@@ -578,7 +585,9 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(Shader::Maxwell::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env, bool force_context_flush) try {
|
||||
std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
|
||||
ShaderContext::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env,
|
||||
bool force_context_flush) try {
|
||||
auto hash = key.Hash();
|
||||
LOG_INFO(Render_OpenGL, "{:#016x}", hash);
|
||||
|
||||
@@ -588,7 +597,7 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(Shader::Maxw
|
||||
env.Dump(hash, key.unique_hash);
|
||||
}
|
||||
|
||||
auto program{TranslateProgram(pools, env, cfg, host_info)};
|
||||
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
|
||||
const u32 num_storage_buffers{Shader::NumDescriptors(program.info.storage_buffers_descriptors)};
|
||||
Shader::RuntimeInfo info;
|
||||
info.glasm_use_storage_buffers = num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread_worker.h"
|
||||
#include "shader_recompiler/host_translate_info.h"
|
||||
#include "shader_recompiler/shader_pool.h"
|
||||
#include "shader_recompiler/profile.h"
|
||||
#include "video_core/renderer_opengl/gl_compute_pipeline.h"
|
||||
#include "video_core/renderer_opengl/gl_graphics_pipeline.h"
|
||||
@@ -52,9 +51,20 @@ private:
|
||||
[[nodiscard]] GraphicsPipeline* BuiltPipeline(GraphicsPipeline* pipeline) const noexcept;
|
||||
|
||||
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline();
|
||||
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline(Shader::Maxwell::ShaderPools& pools, const GraphicsPipelineKey& key, std::span<Shader::Environment* const> envs, bool use_shader_workers, bool force_context_flush = false);
|
||||
std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineKey& key, const VideoCommon::ShaderInfo* shader);
|
||||
std::unique_ptr<ComputePipeline> CreateComputePipeline(Shader::Maxwell::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env, bool force_context_flush = false);
|
||||
|
||||
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline(
|
||||
ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key,
|
||||
std::span<Shader::Environment* const> envs, bool use_shader_workers,
|
||||
bool force_context_flush = false);
|
||||
|
||||
std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineKey& key,
|
||||
const VideoCommon::ShaderInfo* shader);
|
||||
|
||||
std::unique_ptr<ComputePipeline> CreateComputePipeline(ShaderContext::ShaderPools& pools,
|
||||
const ComputePipelineKey& key,
|
||||
Shader::Environment& env,
|
||||
bool force_context_flush = false);
|
||||
|
||||
std::unique_ptr<ShaderWorker> CreateWorkers() const;
|
||||
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
@@ -70,7 +80,7 @@ private:
|
||||
GraphicsPipelineKey graphics_key{};
|
||||
GraphicsPipeline* current_pipeline{};
|
||||
|
||||
Shader::Maxwell::ShaderPools main_pools;
|
||||
ShaderContext::ShaderPools main_pools;
|
||||
ankerl::unordered_dense::map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>> graphics_cache;
|
||||
ankerl::unordered_dense::map<ComputePipelineKey, std::unique_ptr<ComputePipeline>> compute_cache;
|
||||
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/container/stable_vector.hpp>
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/graphics_context.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate_program.h"
|
||||
|
||||
namespace OpenGL::ShaderContext {
|
||||
struct ShaderPools {
|
||||
void ReleaseContents() {
|
||||
flow_block.ReleaseContents();
|
||||
block.ReleaseContents();
|
||||
inst.ReleaseContents();
|
||||
}
|
||||
|
||||
Shader::ObjectPool<Shader::IR::Inst> inst{8192};
|
||||
Shader::ObjectPool<Shader::IR::Block> block{32};
|
||||
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
|
||||
};
|
||||
|
||||
struct Context {
|
||||
explicit Context(Core::Frontend::EmuWindow& emu_window) : gl_context{emu_window.CreateSharedContext()}, scoped{*gl_context} {}
|
||||
explicit Context(Core::Frontend::EmuWindow& emu_window)
|
||||
: gl_context{emu_window.CreateSharedContext()}, scoped{*gl_context} {}
|
||||
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> gl_context;
|
||||
Core::Frontend::GraphicsContext::Scoped scoped;
|
||||
Shader::Maxwell::ShaderPools pools;
|
||||
ShaderPools pools;
|
||||
};
|
||||
|
||||
} // namespace OpenGL::ShaderContext
|
||||
|
||||
@@ -47,6 +47,93 @@ using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline VkDeviceSize DescriptorSizeForType(const Device& device,
|
||||
VkDescriptorType type) {
|
||||
const auto& props = device.DescriptorBufferProperties();
|
||||
const bool robust = device.IsRobustBufferAccessEnabled();
|
||||
switch (type) {
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
|
||||
return robust ? props.robustUniformBufferDescriptorSize : props.uniformBufferDescriptorSize;
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
|
||||
return robust ? props.robustStorageBufferDescriptorSize : props.storageBufferDescriptorSize;
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
|
||||
return robust ? props.robustUniformTexelBufferDescriptorSize
|
||||
: props.uniformTexelBufferDescriptorSize;
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
|
||||
return robust ? props.robustStorageTexelBufferDescriptorSize
|
||||
: props.storageTexelBufferDescriptorSize;
|
||||
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
|
||||
return props.combinedImageSamplerDescriptorSize;
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
|
||||
return props.storageImageDescriptorSize;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct DescriptorBufferBinding {
|
||||
VkDescriptorType type;
|
||||
u32 count;
|
||||
VkDeviceSize offset;
|
||||
VkDeviceSize stride;
|
||||
};
|
||||
|
||||
struct DescriptorBufferLayout {
|
||||
VkDeviceSize size{};
|
||||
boost::container::small_vector<DescriptorBufferBinding, 32> bindings;
|
||||
|
||||
[[nodiscard]] bool Empty() const noexcept {
|
||||
return bindings.empty();
|
||||
}
|
||||
};
|
||||
|
||||
inline void WriteDescriptorBuffer(const Device& device, const DescriptorBufferLayout& layout,
|
||||
const DescriptorUpdateEntry* payload, u8* host) {
|
||||
const vk::Device& dev = device.GetLogical();
|
||||
for (const DescriptorBufferBinding& binding : layout.bindings) {
|
||||
for (u32 index = 0; index < binding.count; ++index) {
|
||||
const DescriptorUpdateEntry& entry = *(payload++);
|
||||
const VkDescriptorAddressInfoEXT address_info{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.address = entry.address.address,
|
||||
.range = entry.address.range,
|
||||
.format = entry.address.format,
|
||||
};
|
||||
VkDescriptorGetInfoEXT get_info{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.type = binding.type,
|
||||
.data{},
|
||||
};
|
||||
switch (binding.type) {
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
|
||||
get_info.data.pUniformBuffer = &address_info;
|
||||
break;
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
|
||||
get_info.data.pStorageBuffer = &address_info;
|
||||
break;
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
|
||||
get_info.data.pUniformTexelBuffer = &address_info;
|
||||
break;
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
|
||||
get_info.data.pStorageTexelBuffer = &address_info;
|
||||
break;
|
||||
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
|
||||
get_info.data.pCombinedImageSampler = &entry.image;
|
||||
break;
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
|
||||
get_info.data.pStorageImage = &entry.image;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
dev.GetDescriptorEXT(get_info, binding.stride,
|
||||
host + binding.offset + index * binding.stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline u32 NumDescriptorEntries(const Shader::Info& info) {
|
||||
return Shader::NumDescriptors(info.constant_buffer_descriptors) +
|
||||
Shader::NumDescriptors(info.storage_buffers_descriptors) +
|
||||
@@ -61,20 +148,70 @@ public:
|
||||
DescriptorLayoutBuilder(const Device& device_) : device{&device_} {}
|
||||
|
||||
bool CanUsePushDescriptor() const noexcept {
|
||||
return device->IsKhrPushDescriptorSupported() &&
|
||||
num_descriptors <= device->MaxPushDescriptors();
|
||||
if (!device->IsKhrPushDescriptorSupported() ||
|
||||
num_descriptors > device->MaxPushDescriptors()) {
|
||||
return false;
|
||||
}
|
||||
return !device->IsExtDescriptorBufferSupported() ||
|
||||
device->DescriptorBufferProperties().bufferlessPushDescriptors;
|
||||
}
|
||||
|
||||
// TODO(crueter): utilize layout binding flags
|
||||
vk::DescriptorSetLayout CreateDescriptorSetLayout(bool use_push_descriptor) const {
|
||||
bool CanUseDescriptorBuffer() const noexcept {
|
||||
const auto& props = device->DescriptorBufferProperties();
|
||||
if (!device->IsExtDescriptorBufferSupported() || bindings.empty() ||
|
||||
!props.combinedImageSamplerDescriptorSingleArray) {
|
||||
return false;
|
||||
}
|
||||
return !props.bufferlessPushDescriptors || !CanUsePushDescriptor();
|
||||
}
|
||||
|
||||
DescriptorBufferLayout MakeDescriptorBufferLayout(VkDescriptorSetLayout layout) const {
|
||||
DescriptorBufferLayout result;
|
||||
if (!layout) {
|
||||
return result;
|
||||
}
|
||||
const vk::Device& dev = device->GetLogical();
|
||||
result.size = dev.GetDescriptorSetLayoutSizeEXT(layout);
|
||||
result.bindings.reserve(bindings.size());
|
||||
for (const VkDescriptorSetLayoutBinding& entry : bindings) {
|
||||
result.bindings.push_back(DescriptorBufferBinding{
|
||||
.type = entry.descriptorType,
|
||||
.count = entry.descriptorCount,
|
||||
.offset = dev.GetDescriptorSetLayoutBindingOffsetEXT(layout, entry.binding),
|
||||
.stride = DescriptorSizeForType(*device, entry.descriptorType),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout CreateDescriptorSetLayout(bool use_push_descriptor,
|
||||
bool use_descriptor_buffer = false) const {
|
||||
if (bindings.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
const VkDescriptorSetLayoutCreateFlags flags =
|
||||
use_push_descriptor ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR : 0;
|
||||
VkDescriptorSetLayoutCreateFlags flags = 0;
|
||||
if (use_push_descriptor) {
|
||||
flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;
|
||||
}
|
||||
if (use_descriptor_buffer) {
|
||||
flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
}
|
||||
boost::container::small_vector<VkDescriptorBindingFlags, 32> binding_flags;
|
||||
VkDescriptorSetLayoutBindingFlagsCreateInfo binding_flags_ci{};
|
||||
const void* pnext = nullptr;
|
||||
if (!use_push_descriptor && device->IsDescriptorBindingPartiallyBoundSupported()) {
|
||||
binding_flags.assign(bindings.size(), VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT);
|
||||
binding_flags_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = static_cast<u32>(binding_flags.size()),
|
||||
.pBindingFlags = binding_flags.data(),
|
||||
};
|
||||
pnext = &binding_flags_ci;
|
||||
}
|
||||
return device->GetLogical().CreateDescriptorSetLayout({
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.pNext = pnext,
|
||||
.flags = flags,
|
||||
.bindingCount = static_cast<u32>(bindings.size()),
|
||||
.pBindings = bindings.data(),
|
||||
|
||||
@@ -69,6 +69,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
|
||||
if (device.IsExtConditionalRendering()) {
|
||||
flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
|
||||
}
|
||||
if (device.IsBufferDeviceAddressSupported()) {
|
||||
flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
||||
}
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -91,6 +94,9 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p
|
||||
device = &runtime.device;
|
||||
buffer = runtime.CreateNullBuffer();
|
||||
is_null = true;
|
||||
if (device->IsBufferDeviceAddressSupported()) {
|
||||
device_address = device->GetLogical().GetBufferDeviceAddress(*buffer);
|
||||
}
|
||||
}
|
||||
|
||||
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
|
||||
@@ -100,6 +106,9 @@ Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
|
||||
if (runtime.device.HasDebuggingToolAttached()) {
|
||||
buffer.SetObjectNameEXT(fmt::format("Buffer {:#x}", CpuAddr()).c_str());
|
||||
}
|
||||
if (device->IsBufferDeviceAddressSupported()) {
|
||||
device_address = device->GetLogical().GetBufferDeviceAddress(*buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::MarkUsage(u64 offset, u64 size) noexcept {
|
||||
@@ -356,6 +365,93 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
||||
scheduler_, staging_pool_);
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::TryEnableUnifiedMemory(void* base, size_t size,
|
||||
std::span<AHardwareBuffer* const> hardware_buffers,
|
||||
size_t hardware_buffer_window,
|
||||
size_t hardware_buffer_base) {
|
||||
unified_memory = std::make_unique<HostMemoryImport>(
|
||||
device, base, size, hardware_buffers, hardware_buffer_window, hardware_buffer_base);
|
||||
if (!unified_memory->IsValid()) {
|
||||
unified_memory.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::CopyToUnifiedMemory(
|
||||
size_t window_index, VkBuffer src_buffer,
|
||||
std::span<const VideoCommon::BufferCopy> copies) {
|
||||
if (!unified_memory || src_buffer == VK_NULL_HANDLE || copies.empty() ||
|
||||
window_index >= unified_memory->GetWindowCount()) {
|
||||
return;
|
||||
}
|
||||
const VkBuffer dst_buffer = unified_memory->GetWindowBuffer(window_index);
|
||||
if (dst_buffer == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
VkDeviceSize covered_begin = std::numeric_limits<VkDeviceSize>::max();
|
||||
VkDeviceSize covered_end = 0;
|
||||
for (const VideoCommon::BufferCopy& copy : copies) {
|
||||
covered_begin = (std::min)(covered_begin, static_cast<VkDeviceSize>(copy.dst_offset));
|
||||
covered_end = (std::max)(covered_end,
|
||||
static_cast<VkDeviceSize>(copy.dst_offset + copy.size));
|
||||
}
|
||||
|
||||
boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size());
|
||||
std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy);
|
||||
|
||||
const bool foreign = unified_memory->NeedsForeignOwnershipTransfer();
|
||||
const u32 queue_family = device.GetGraphicsFamily();
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([src_buffer, dst_buffer, vk_copies, foreign, queue_family, covered_begin,
|
||||
covered_end](vk::CommandBuffer cmdbuf) {
|
||||
if (foreign) {
|
||||
const VkBufferMemoryBarrier acquire{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = 0,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
|
||||
.dstQueueFamilyIndex = queue_family,
|
||||
.buffer = dst_buffer,
|
||||
.offset = covered_begin,
|
||||
.size = covered_end - covered_begin,
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, acquire);
|
||||
}
|
||||
cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies));
|
||||
if (foreign) {
|
||||
const VkBufferMemoryBarrier release{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = 0,
|
||||
.srcQueueFamilyIndex = queue_family,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
|
||||
.buffer = dst_buffer,
|
||||
.offset = covered_begin,
|
||||
.size = covered_end - covered_begin,
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, release);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::UnifiedMemoryHostBarrier() {
|
||||
static constexpr VkMemoryBarrier HOST_BARRIER{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0,
|
||||
HOST_BARRIER);
|
||||
});
|
||||
}
|
||||
|
||||
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
|
||||
return staging_pool.Request(size, MemoryUsage::Upload);
|
||||
}
|
||||
@@ -364,6 +460,10 @@ StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool def
|
||||
return staging_pool.Request(size, MemoryUsage::Download, deferred);
|
||||
}
|
||||
|
||||
VkFormat BufferCacheRuntime::TexelBufferFormat(VideoCore::Surface::PixelFormat format) const {
|
||||
return MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, false, format).format;
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) {
|
||||
staging_pool.FreeDeferred(ref);
|
||||
}
|
||||
@@ -690,6 +790,9 @@ vk::Buffer BufferCacheRuntime::CreateNullBuffer() {
|
||||
if (device.IsExtTransformFeedbackSupported()) {
|
||||
create_info.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
||||
}
|
||||
if (device.IsBufferDeviceAddressSupported()) {
|
||||
create_info.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
||||
}
|
||||
vk::Buffer ret = memory_allocator.CreateBuffer(create_info, MemoryUsage::DeviceLocal);
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
ret.SetObjectNameEXT("Null buffer");
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
#include "video_core/buffer_cache/buffer_cache_base.h"
|
||||
#include "video_core/buffer_cache/memory_tracker_base.h"
|
||||
@@ -39,6 +41,10 @@ public:
|
||||
return *buffer;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkDeviceAddress DeviceAddress() const noexcept {
|
||||
return device_address;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept {
|
||||
return tracker.IsUsed(offset, size);
|
||||
}
|
||||
@@ -70,6 +76,7 @@ private:
|
||||
vk::Buffer buffer;
|
||||
std::vector<BufferView> views;
|
||||
VideoCommon::UsageTracker tracker;
|
||||
VkDeviceAddress device_address{};
|
||||
u64 last_usage_tick{};
|
||||
bool is_null{};
|
||||
};
|
||||
@@ -92,6 +99,31 @@ public:
|
||||
|
||||
void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept;
|
||||
|
||||
void TryEnableUnifiedMemory(void* base, size_t size,
|
||||
std::span<AHardwareBuffer* const> hardware_buffers,
|
||||
size_t hardware_buffer_window, size_t hardware_buffer_base);
|
||||
|
||||
[[nodiscard]] bool HasUnifiedMemory() const noexcept {
|
||||
return unified_memory != nullptr && unified_memory->IsValid();
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 UnifiedMemorySize() const noexcept {
|
||||
return unified_memory ? unified_memory->GetSize() : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 UnifiedMemoryBase() const noexcept {
|
||||
return unified_memory ? unified_memory->GetBaseOffset() : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 UnifiedMemoryWindowSize() const noexcept {
|
||||
return unified_memory ? unified_memory->GetWindowSize() : 0;
|
||||
}
|
||||
|
||||
void CopyToUnifiedMemory(size_t window_index, VkBuffer src_buffer,
|
||||
std::span<const VideoCommon::BufferCopy> copies);
|
||||
|
||||
void UnifiedMemoryHostBarrier();
|
||||
|
||||
u64 CurrentTick();
|
||||
|
||||
u64 KnownGpuTick();
|
||||
@@ -145,22 +177,25 @@ public:
|
||||
[[maybe_unused]] u32 binding_index,
|
||||
u32 size) {
|
||||
const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload);
|
||||
BindBuffer(ref.buffer, static_cast<u32>(ref.offset), size);
|
||||
guest_descriptor_queue.AddBuffer(ref.buffer, ref.device_address,
|
||||
static_cast<u32>(ref.offset), size);
|
||||
return ref.mapped_span;
|
||||
}
|
||||
|
||||
void BindUniformBuffer(VkBuffer buffer, u32 offset, u32 size) {
|
||||
void BindUniformBuffer(const Buffer& buffer, u32 offset, u32 size) {
|
||||
BindBuffer(buffer, offset, size);
|
||||
}
|
||||
|
||||
void BindStorageBuffer(VkBuffer buffer, u32 offset, u32 size,
|
||||
void BindStorageBuffer(const Buffer& buffer, u32 offset, u32 size,
|
||||
[[maybe_unused]] bool is_written) {
|
||||
BindBuffer(buffer, offset, size);
|
||||
}
|
||||
|
||||
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
|
||||
VideoCore::Surface::PixelFormat format) {
|
||||
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format));
|
||||
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format),
|
||||
buffer.DeviceAddress(), offset, size,
|
||||
TexelBufferFormat(format));
|
||||
}
|
||||
|
||||
bool ShouldLimitDynamicStorageBuffers() const {
|
||||
@@ -172,14 +207,17 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
|
||||
if (buffer == VK_NULL_HANDLE) {
|
||||
guest_descriptor_queue.AddBuffer(buffer, 0, VK_WHOLE_SIZE);
|
||||
void BindBuffer(const Buffer& buffer, u32 offset, u32 size) {
|
||||
const VkBuffer handle = buffer.Handle();
|
||||
if (handle == VK_NULL_HANDLE) {
|
||||
guest_descriptor_queue.AddBuffer(handle, 0, 0, VK_WHOLE_SIZE);
|
||||
} else {
|
||||
guest_descriptor_queue.AddBuffer(buffer, offset, size);
|
||||
guest_descriptor_queue.AddBuffer(handle, buffer.DeviceAddress(), offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat TexelBufferFormat(VideoCore::Surface::PixelFormat format) const;
|
||||
|
||||
void ReserveNullBuffer();
|
||||
vk::Buffer CreateNullBuffer();
|
||||
|
||||
@@ -193,6 +231,7 @@ private:
|
||||
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
|
||||
|
||||
vk::Buffer null_buffer;
|
||||
std::unique_ptr<HostMemoryImport> unified_memory;
|
||||
|
||||
std::unique_ptr<Uint8Pass> uint8_pass;
|
||||
QuadIndexedPass quad_index_pass;
|
||||
@@ -215,6 +254,7 @@ struct BufferCacheParams {
|
||||
static constexpr bool USE_MEMORY_MAPS = true;
|
||||
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false;
|
||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true;
|
||||
static constexpr bool USE_UNIFIED_MEMORY = true;
|
||||
};
|
||||
|
||||
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
|
||||
|
||||
@@ -34,12 +34,14 @@ using Tegra::Texture::TexturePair;
|
||||
ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk::PipelineCache& pipeline_cache_,
|
||||
DescriptorPool& descriptor_pool,
|
||||
GuestDescriptorQueue& guest_descriptor_queue_,
|
||||
DescriptorBufferRing& descriptor_buffer_ring_,
|
||||
Common::ThreadWorker* thread_worker,
|
||||
PipelineStatistics* pipeline_statistics,
|
||||
VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_,
|
||||
vk::ShaderModule spv_module_, u64 shader_hash_)
|
||||
: device{device_},
|
||||
pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_}, info{info_},
|
||||
pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_},
|
||||
descriptor_buffer_ring{descriptor_buffer_ring_}, info{info_},
|
||||
shader_hash{shader_hash_}, spv_module(std::move(spv_module_)) {
|
||||
if (shader_notify) {
|
||||
shader_notify->MarkShaderBuilding();
|
||||
@@ -48,18 +50,36 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk
|
||||
uniform_buffer_sizes.begin());
|
||||
num_descriptor_entries = NumDescriptorEntries(info);
|
||||
|
||||
auto func{[this, &scheduler, &descriptor_pool, shader_notify, pipeline_statistics] {
|
||||
DescriptorLayoutBuilder builder{device};
|
||||
builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
DescriptorLayoutBuilder builder{device};
|
||||
builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
|
||||
pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout);
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
uses_descriptor_buffer = builder.CanUseDescriptorBuffer() && descriptor_buffer_ring.IsValid();
|
||||
descriptor_set_layout =
|
||||
builder.CreateDescriptorSetLayout(uses_push_descriptor, uses_descriptor_buffer);
|
||||
if (uses_descriptor_buffer) {
|
||||
descriptor_buffer_layout = builder.MakeDescriptorBufferLayout(*descriptor_set_layout);
|
||||
if (!descriptor_buffer_ring.CanAllocate(descriptor_buffer_layout.size)) {
|
||||
LOG_DEBUG(Render_Vulkan,
|
||||
"Compute shader {:016X} needs {} descriptor bytes per dispatch, falling "
|
||||
"back to sets",
|
||||
shader_hash, descriptor_buffer_layout.size);
|
||||
uses_descriptor_buffer = false;
|
||||
descriptor_buffer_layout = {};
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(false);
|
||||
}
|
||||
}
|
||||
pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout);
|
||||
if (!uses_descriptor_buffer) {
|
||||
descriptor_update_template =
|
||||
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor);
|
||||
if (!uses_push_descriptor) {
|
||||
descriptor_allocator = descriptor_pool.Allocator(device, scheduler, *descriptor_set_layout, info);
|
||||
descriptor_allocator =
|
||||
descriptor_pool.Allocator(device, scheduler, *descriptor_set_layout, info);
|
||||
}
|
||||
}
|
||||
|
||||
auto func{[this, shader_notify, pipeline_statistics] {
|
||||
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
@@ -69,6 +89,9 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk
|
||||
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
|
||||
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
||||
}
|
||||
if (uses_descriptor_buffer) {
|
||||
flags |= VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
}
|
||||
const VkComputePipelineCreateInfo compute_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -125,10 +148,10 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk
|
||||
}
|
||||
}
|
||||
|
||||
void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
Tegra::MemoryManager& gpu_memory, Scheduler& scheduler,
|
||||
BufferCache& buffer_cache, TextureCache& texture_cache) {
|
||||
guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries);
|
||||
guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries, uses_descriptor_buffer);
|
||||
|
||||
buffer_cache.SetComputeUniformBufferState(info.constant_buffer_mask, &uniform_buffer_sizes);
|
||||
buffer_cache.UnbindComputeStorageBuffers();
|
||||
@@ -249,10 +272,33 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineBind(true, "compute pipeline");
|
||||
}
|
||||
|
||||
const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
|
||||
const DescriptorUpdateEntry* const descriptor_data{guest_descriptor_queue.UpdateData()};
|
||||
VkDeviceSize descriptor_buffer_offset{};
|
||||
u32 descriptor_buffer_chunk{};
|
||||
if (uses_descriptor_buffer) {
|
||||
const DescriptorBufferRing::Allocation alloc{
|
||||
descriptor_buffer_ring.Allocate(scheduler, descriptor_buffer_layout.size)};
|
||||
if (!alloc.host) {
|
||||
LOG_DEBUG(Render_Vulkan, "Failed to reserve descriptor memory, skipping dispatch");
|
||||
return false;
|
||||
}
|
||||
WriteDescriptorBuffer(device, descriptor_buffer_layout, descriptor_data, alloc.host);
|
||||
descriptor_buffer_offset = alloc.offset;
|
||||
descriptor_buffer_chunk = alloc.chunk;
|
||||
}
|
||||
|
||||
const bool bind_descriptor_buffer{
|
||||
uses_descriptor_buffer && scheduler.UpdateDescriptorBufferChunk(descriptor_buffer_chunk)};
|
||||
|
||||
const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty();
|
||||
scheduler.Record([this, descriptor_data, is_rescaling,
|
||||
scheduler.Record([this, descriptor_data, is_rescaling, descriptor_buffer_offset,
|
||||
descriptor_buffer_chunk, bind_descriptor_buffer,
|
||||
rescaling_data = rescaling.Data()](vk::CommandBuffer cmdbuf) {
|
||||
if (bind_descriptor_buffer) {
|
||||
const VkDescriptorBufferBindingInfoEXT binding_info{
|
||||
descriptor_buffer_ring.BindingInfo(descriptor_buffer_chunk)};
|
||||
cmdbuf.BindDescriptorBuffersEXT(binding_info);
|
||||
}
|
||||
if (!pipeline) {
|
||||
return;
|
||||
}
|
||||
@@ -265,7 +311,11 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data),
|
||||
rescaling_data.data());
|
||||
}
|
||||
if (uses_push_descriptor) {
|
||||
if (uses_descriptor_buffer) {
|
||||
const u32 buffer_index{};
|
||||
cmdbuf.SetDescriptorBufferOffsetsEXT(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout,
|
||||
0, buffer_index, descriptor_buffer_offset);
|
||||
} else if (uses_push_descriptor) {
|
||||
cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout,
|
||||
0, descriptor_data);
|
||||
} else {
|
||||
@@ -276,6 +326,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
descriptor_set, nullptr);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread_worker.h"
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
#include "video_core/renderer_vulkan/pipeline_helper.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
@@ -34,6 +36,7 @@ public:
|
||||
explicit ComputePipeline(const Device& device, Scheduler& scheduler, vk::PipelineCache& pipeline_cache,
|
||||
DescriptorPool& descriptor_pool,
|
||||
GuestDescriptorQueue& guest_descriptor_queue,
|
||||
DescriptorBufferRing& descriptor_buffer_ring,
|
||||
Common::ThreadWorker* thread_worker,
|
||||
PipelineStatistics* pipeline_statistics,
|
||||
VideoCore::ShaderNotify* shader_notify, const Shader::Info& info,
|
||||
@@ -45,8 +48,9 @@ public:
|
||||
ComputePipeline& operator=(const ComputePipeline&) = delete;
|
||||
ComputePipeline(const ComputePipeline&) = delete;
|
||||
|
||||
void Configure(Tegra::Engines::KeplerCompute& kepler_compute, Tegra::MemoryManager& gpu_memory,
|
||||
Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache);
|
||||
[[nodiscard]] bool Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
Tegra::MemoryManager& gpu_memory, Scheduler& scheduler,
|
||||
BufferCache& buffer_cache, TextureCache& texture_cache);
|
||||
|
||||
bool IsBound() const noexcept {
|
||||
return static_cast<bool>(pipeline);
|
||||
@@ -56,6 +60,7 @@ private:
|
||||
const Device& device;
|
||||
vk::PipelineCache& pipeline_cache;
|
||||
GuestDescriptorQueue& guest_descriptor_queue;
|
||||
DescriptorBufferRing& descriptor_buffer_ring;
|
||||
Shader::Info info;
|
||||
u64 shader_hash{};
|
||||
u32 num_descriptor_entries{};
|
||||
@@ -65,6 +70,8 @@ private:
|
||||
vk::ShaderModule spv_module;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
bool uses_push_descriptor{false};
|
||||
bool uses_descriptor_buffer{false};
|
||||
DescriptorBufferLayout descriptor_buffer_layout;
|
||||
DescriptorAllocator descriptor_allocator;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::DescriptorUpdateTemplate descriptor_update_template;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
DescriptorBufferRing::DescriptorBufferRing(const Device& device_,
|
||||
MemoryAllocator& memory_allocator)
|
||||
: device{device_} {
|
||||
if (!device.IsExtDescriptorBufferSupported() || !device.IsBufferDeviceAddressSupported()) {
|
||||
return;
|
||||
}
|
||||
const VkPhysicalDeviceDescriptorBufferPropertiesEXT& props{device.DescriptorBufferProperties()};
|
||||
alignment = std::max<VkDeviceSize>(props.descriptorBufferOffsetAlignment, 1);
|
||||
|
||||
const VkDeviceSize max_bound{(std::min)({props.maxSamplerDescriptorBufferRange,
|
||||
props.maxResourceDescriptorBufferRange,
|
||||
props.samplerDescriptorBufferAddressSpaceSize,
|
||||
props.resourceDescriptorBufferAddressSpaceSize,
|
||||
props.descriptorBufferAddressSpaceSize})};
|
||||
const VkDeviceSize frame_size{device.IsTiler() ? TILER_FRAME_SIZE : DESKTOP_FRAME_SIZE};
|
||||
const VkDeviceSize chunk_size{
|
||||
Common::AlignDown((std::min)(frame_size, max_bound), alignment)};
|
||||
if (chunk_size <= alignment) {
|
||||
LOG_DEBUG(Render_Vulkan, "Descriptor buffer binding limit of {} is unusable, disabling",
|
||||
max_bound);
|
||||
return;
|
||||
}
|
||||
chunk_capacity = chunk_size - alignment;
|
||||
chunks_per_frame = static_cast<size_t>(frame_size / chunk_size);
|
||||
|
||||
const VkBufferCreateInfo buffer_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = chunk_size,
|
||||
.usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT |
|
||||
VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT |
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
};
|
||||
const size_t total_chunks{chunks_per_frame * FRAMES_IN_FLIGHT};
|
||||
chunks.reserve(total_chunks);
|
||||
chunk_addresses.reserve(total_chunks);
|
||||
chunk_hosts.reserve(total_chunks);
|
||||
for (size_t index = 0; index < total_chunks; ++index) {
|
||||
vk::Buffer buffer{memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::Upload)};
|
||||
if (!buffer.IsHostVisible()) {
|
||||
LOG_DEBUG(Render_Vulkan, "Descriptor buffer is not host visible, disabling");
|
||||
chunks.clear();
|
||||
return;
|
||||
}
|
||||
if (!buffer.IsHostCoherent()) {
|
||||
LOG_DEBUG(Render_Vulkan, "Descriptor buffer is not host coherent, disabling");
|
||||
chunks.clear();
|
||||
return;
|
||||
}
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
buffer.SetObjectNameEXT("Descriptor buffer");
|
||||
}
|
||||
const VkDeviceAddress raw_address{device.GetLogical().GetBufferDeviceAddress(*buffer)};
|
||||
const VkDeviceAddress address{Common::AlignUp(raw_address, alignment)};
|
||||
chunk_addresses.push_back(address);
|
||||
chunk_hosts.push_back(buffer.Mapped().data() + (address - raw_address));
|
||||
chunks.push_back(std::move(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
DescriptorBufferRing::~DescriptorBufferRing() = default;
|
||||
|
||||
void DescriptorBufferRing::TickFrame() {
|
||||
if (++frame_index >= FRAMES_IN_FLIGHT) {
|
||||
frame_index = 0;
|
||||
}
|
||||
chunk_cursor = 0;
|
||||
cursor = 0;
|
||||
++generation;
|
||||
frame_reused = true;
|
||||
}
|
||||
|
||||
void DescriptorBufferRing::TouchFrame(Scheduler& scheduler) {
|
||||
frame_ticks[frame_index] = scheduler.CurrentTick();
|
||||
}
|
||||
|
||||
DescriptorBufferRing::Allocation DescriptorBufferRing::Allocate(Scheduler& scheduler,
|
||||
VkDeviceSize size) {
|
||||
ASSERT(!chunks.empty());
|
||||
if (!CanAllocate(size)) {
|
||||
LOG_DEBUG(Render_Vulkan, "Descriptor set of {} bytes exceeds chunk capacity {}", size,
|
||||
chunk_capacity);
|
||||
return Allocation{};
|
||||
}
|
||||
const VkDeviceSize needed{Common::AlignUp(size, alignment)};
|
||||
if (frame_reused) {
|
||||
frame_reused = false;
|
||||
scheduler.Wait(frame_ticks[frame_index]);
|
||||
}
|
||||
if (cursor + needed > chunk_capacity) {
|
||||
if (chunk_cursor + 1 < chunks_per_frame) {
|
||||
++chunk_cursor;
|
||||
} else {
|
||||
LOG_DEBUG(Render_Vulkan, "Descriptor buffer frame exhausted, stalling on the GPU");
|
||||
scheduler.Finish();
|
||||
chunk_cursor = 0;
|
||||
++generation;
|
||||
}
|
||||
cursor = 0;
|
||||
}
|
||||
const size_t chunk{frame_index * chunks_per_frame + chunk_cursor};
|
||||
const VkDeviceSize offset{cursor};
|
||||
cursor += needed;
|
||||
frame_ticks[frame_index] = scheduler.CurrentTick();
|
||||
return Allocation{
|
||||
.host = chunk_hosts[chunk] + offset,
|
||||
.offset = offset,
|
||||
.chunk = static_cast<u32>(chunk),
|
||||
.generation = generation,
|
||||
};
|
||||
}
|
||||
|
||||
VkDescriptorBufferBindingInfoEXT DescriptorBufferRing::BindingInfo(u32 chunk) const noexcept {
|
||||
return VkDescriptorBufferBindingInfoEXT{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.address = chunk_addresses[chunk],
|
||||
.usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT |
|
||||
VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class Scheduler;
|
||||
|
||||
class DescriptorBufferRing final {
|
||||
static constexpr size_t FRAMES_IN_FLIGHT = 8;
|
||||
static constexpr VkDeviceSize TILER_FRAME_SIZE = 2 * 1024 * 1024;
|
||||
static constexpr VkDeviceSize DESKTOP_FRAME_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
public:
|
||||
explicit DescriptorBufferRing(const Device& device_, MemoryAllocator& memory_allocator);
|
||||
~DescriptorBufferRing();
|
||||
|
||||
struct Allocation {
|
||||
u8* host{};
|
||||
VkDeviceSize offset{};
|
||||
u32 chunk{};
|
||||
u64 generation{};
|
||||
};
|
||||
|
||||
[[nodiscard]] u64 CurrentGeneration() const noexcept {
|
||||
return generation;
|
||||
}
|
||||
|
||||
void TouchFrame(Scheduler& scheduler);
|
||||
|
||||
[[nodiscard]] bool CanAllocate(VkDeviceSize size) const noexcept {
|
||||
return Common::AlignUp(size, alignment) <= chunk_capacity;
|
||||
}
|
||||
|
||||
void TickFrame();
|
||||
|
||||
[[nodiscard]] Allocation Allocate(Scheduler& scheduler, VkDeviceSize size);
|
||||
|
||||
[[nodiscard]] VkDescriptorBufferBindingInfoEXT BindingInfo(u32 chunk) const noexcept;
|
||||
|
||||
[[nodiscard]] bool IsValid() const noexcept {
|
||||
return !chunks.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
const Device& device;
|
||||
std::vector<vk::Buffer> chunks;
|
||||
std::vector<VkDeviceAddress> chunk_addresses;
|
||||
std::vector<u8*> chunk_hosts;
|
||||
VkDeviceSize alignment{1};
|
||||
VkDeviceSize chunk_capacity{};
|
||||
size_t chunks_per_frame{};
|
||||
size_t frame_index{};
|
||||
size_t chunk_cursor{};
|
||||
VkDeviceSize cursor{};
|
||||
u64 generation{1};
|
||||
std::array<u64, FRAMES_IN_FLIGHT> frame_ticks{};
|
||||
bool frame_reused{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -5,6 +5,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
|
||||
@@ -250,13 +251,15 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
Scheduler& scheduler_, BufferCache& buffer_cache_, TextureCache& texture_cache_,
|
||||
vk::PipelineCache& pipeline_cache_, VideoCore::ShaderNotify* shader_notify,
|
||||
const Device& device_, DescriptorPool& descriptor_pool,
|
||||
GuestDescriptorQueue& guest_descriptor_queue_, Common::ThreadWorker* worker_thread,
|
||||
GuestDescriptorQueue& guest_descriptor_queue_, DescriptorBufferRing& descriptor_buffer_ring_,
|
||||
Common::ThreadWorker* worker_thread,
|
||||
PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache,
|
||||
const GraphicsPipelineCacheKey& key_, std::array<vk::ShaderModule, NUM_STAGES> stages,
|
||||
const std::array<const Shader::Info*, NUM_STAGES>& infos)
|
||||
: key{key_}, device{device_}, texture_cache{texture_cache_}, buffer_cache{buffer_cache_},
|
||||
pipeline_cache(pipeline_cache_), scheduler{scheduler_},
|
||||
guest_descriptor_queue{guest_descriptor_queue_}, spv_modules{std::move(stages)} {
|
||||
guest_descriptor_queue{guest_descriptor_queue_},
|
||||
descriptor_buffer_ring{descriptor_buffer_ring_}, spv_modules{std::move(stages)} {
|
||||
if (shader_notify) {
|
||||
shader_notify->MarkShaderBuilding();
|
||||
}
|
||||
@@ -276,20 +279,37 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
num_descriptor_entries += NumDescriptorEntries(*info);
|
||||
}
|
||||
fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0];
|
||||
auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] {
|
||||
DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)};
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
|
||||
|
||||
if (!uses_push_descriptor) {
|
||||
descriptor_allocator = descriptor_pool.Allocator(device, scheduler, *descriptor_set_layout, stage_infos);
|
||||
DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)};
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
uses_descriptor_buffer = builder.CanUseDescriptorBuffer() && descriptor_buffer_ring.IsValid();
|
||||
descriptor_set_layout =
|
||||
builder.CreateDescriptorSetLayout(uses_push_descriptor, uses_descriptor_buffer);
|
||||
if (uses_descriptor_buffer) {
|
||||
descriptor_buffer_layout = builder.MakeDescriptorBufferLayout(*descriptor_set_layout);
|
||||
if (!descriptor_buffer_ring.CanAllocate(descriptor_buffer_layout.size)) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Graphics pipeline {:016X} needs {} descriptor bytes per draw, falling back "
|
||||
"to sets",
|
||||
key.Hash(), descriptor_buffer_layout.size);
|
||||
uses_descriptor_buffer = false;
|
||||
descriptor_buffer_layout = {};
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
const VkDescriptorSetLayout set_layout{*descriptor_set_layout};
|
||||
pipeline_layout = builder.CreatePipelineLayout(set_layout);
|
||||
const VkDescriptorSetLayout set_layout{*descriptor_set_layout};
|
||||
pipeline_layout = builder.CreatePipelineLayout(set_layout);
|
||||
if (!uses_descriptor_buffer) {
|
||||
descriptor_update_template =
|
||||
builder.CreateTemplate(set_layout, *pipeline_layout, uses_push_descriptor);
|
||||
if (!uses_push_descriptor) {
|
||||
descriptor_allocator =
|
||||
descriptor_pool.Allocator(device, scheduler, set_layout, stage_infos);
|
||||
}
|
||||
}
|
||||
|
||||
auto func{[this, shader_notify, &render_pass_cache, pipeline_statistics] {
|
||||
const VkRenderPass render_pass{render_pass_cache.Get(MakeRenderPassKey(key.state, device))};
|
||||
Validate();
|
||||
try {
|
||||
@@ -496,7 +516,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
buffer_cache.UpdateGraphicsBuffers(is_indexed);
|
||||
buffer_cache.BindHostGeometryBuffers(is_indexed);
|
||||
|
||||
guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries);
|
||||
guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries, uses_descriptor_buffer);
|
||||
|
||||
RescalingPushConstant rescaling;
|
||||
RenderAreaPushConstant render_area;
|
||||
@@ -538,13 +558,43 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
if (IsBuilt() && !pipeline) {
|
||||
return false;
|
||||
}
|
||||
ConfigureDraw(rescaling, render_area);
|
||||
|
||||
return true;
|
||||
return ConfigureDraw(rescaling, render_area);
|
||||
}
|
||||
|
||||
void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
bool GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
const RenderAreaPushConstant& render_area) {
|
||||
const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
|
||||
|
||||
VkDeviceSize descriptor_buffer_offset{};
|
||||
u32 descriptor_buffer_chunk{};
|
||||
if (descriptor_set_layout && uses_descriptor_buffer) {
|
||||
const auto* const entries = static_cast<const DescriptorUpdateEntry*>(descriptor_data);
|
||||
const bool reuse_allocation =
|
||||
last_descriptor_buffer_generation == descriptor_buffer_ring.CurrentGeneration() &&
|
||||
last_descriptor_payload.size() == num_descriptor_entries &&
|
||||
std::memcmp(last_descriptor_payload.data(), entries,
|
||||
num_descriptor_entries * sizeof(DescriptorUpdateEntry)) == 0;
|
||||
if (reuse_allocation) {
|
||||
descriptor_buffer_offset = last_descriptor_buffer_offset;
|
||||
descriptor_buffer_chunk = last_descriptor_buffer_chunk;
|
||||
descriptor_buffer_ring.TouchFrame(scheduler);
|
||||
} else {
|
||||
const DescriptorBufferRing::Allocation alloc{
|
||||
descriptor_buffer_ring.Allocate(scheduler, descriptor_buffer_layout.size)};
|
||||
if (!alloc.host) {
|
||||
LOG_DEBUG(Render_Vulkan, "Failed to reserve descriptor memory, skipping draw");
|
||||
return false;
|
||||
}
|
||||
WriteDescriptorBuffer(device, descriptor_buffer_layout, entries, alloc.host);
|
||||
descriptor_buffer_offset = alloc.offset;
|
||||
descriptor_buffer_chunk = alloc.chunk;
|
||||
last_descriptor_buffer_offset = alloc.offset;
|
||||
last_descriptor_buffer_chunk = alloc.chunk;
|
||||
last_descriptor_buffer_generation = alloc.generation;
|
||||
last_descriptor_payload.assign(entries, entries + num_descriptor_entries);
|
||||
}
|
||||
}
|
||||
|
||||
scheduler.RequestRenderpass(texture_cache.GetFramebuffer());
|
||||
if (!is_built.load(std::memory_order::relaxed)) {
|
||||
// Wait for the pipeline to be built
|
||||
@@ -556,6 +606,9 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
const bool is_rescaling{texture_cache.IsRescaling()};
|
||||
const bool update_rescaling{scheduler.UpdateRescaling(is_rescaling)};
|
||||
const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)};
|
||||
const bool bind_descriptor_buffer{
|
||||
descriptor_set_layout && uses_descriptor_buffer &&
|
||||
scheduler.UpdateDescriptorBufferChunk(descriptor_buffer_chunk)};
|
||||
|
||||
// Log graphics pipeline binding
|
||||
if (bind_pipeline && GPU::Logging::IsActive() &&
|
||||
@@ -564,11 +617,27 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineBind(false, pipeline_info);
|
||||
}
|
||||
|
||||
const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
|
||||
scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(),
|
||||
is_rescaling, update_rescaling,
|
||||
bool update_descriptors = true;
|
||||
if (descriptor_set_layout && !uses_push_descriptor && !uses_descriptor_buffer) {
|
||||
const auto* const entries = static_cast<const DescriptorUpdateEntry*>(descriptor_data);
|
||||
update_descriptors =
|
||||
bind_pipeline || last_descriptor_payload.size() != num_descriptor_entries ||
|
||||
std::memcmp(last_descriptor_payload.data(), entries,
|
||||
num_descriptor_entries * sizeof(DescriptorUpdateEntry)) != 0;
|
||||
if (update_descriptors) {
|
||||
last_descriptor_payload.assign(entries, entries + num_descriptor_entries);
|
||||
}
|
||||
}
|
||||
scheduler.Record([this, descriptor_data, bind_pipeline, update_descriptors,
|
||||
descriptor_buffer_offset, descriptor_buffer_chunk, bind_descriptor_buffer,
|
||||
rescaling_data = rescaling.Data(), is_rescaling, update_rescaling,
|
||||
uses_render_area = render_area.uses_render_area,
|
||||
render_area_data = render_area.words](vk::CommandBuffer cmdbuf) {
|
||||
if (bind_descriptor_buffer) {
|
||||
const VkDescriptorBufferBindingInfoEXT binding_info{
|
||||
descriptor_buffer_ring.BindingInfo(descriptor_buffer_chunk)};
|
||||
cmdbuf.BindDescriptorBuffersEXT(binding_info);
|
||||
}
|
||||
if (bind_pipeline) {
|
||||
if (!pipeline) {
|
||||
return;
|
||||
@@ -593,10 +662,14 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
if (!descriptor_set_layout) {
|
||||
return;
|
||||
}
|
||||
if (uses_push_descriptor) {
|
||||
if (uses_descriptor_buffer) {
|
||||
const u32 buffer_index{};
|
||||
cmdbuf.SetDescriptorBufferOffsetsEXT(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout,
|
||||
0, buffer_index, descriptor_buffer_offset);
|
||||
} else if (uses_push_descriptor) {
|
||||
cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout,
|
||||
0, descriptor_data);
|
||||
} else {
|
||||
} else if (update_descriptors) {
|
||||
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
|
||||
const vk::Device& dev{device.GetLogical()};
|
||||
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
|
||||
@@ -604,6 +677,7 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
descriptor_set, nullptr);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
@@ -995,6 +1069,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
|
||||
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
||||
}
|
||||
if (uses_descriptor_buffer) {
|
||||
flags |= VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
}
|
||||
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
|
||||
@@ -12,14 +12,18 @@
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "common/thread_worker.h"
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
|
||||
#include "video_core/renderer_vulkan/pipeline_helper.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
namespace VideoCore {
|
||||
@@ -76,7 +80,8 @@ public:
|
||||
Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache,
|
||||
vk::PipelineCache& pipeline_cache, VideoCore::ShaderNotify* shader_notify,
|
||||
const Device& device, DescriptorPool& descriptor_pool,
|
||||
GuestDescriptorQueue& guest_descriptor_queue, Common::ThreadWorker* worker_thread,
|
||||
GuestDescriptorQueue& guest_descriptor_queue,
|
||||
DescriptorBufferRing& descriptor_buffer_ring, Common::ThreadWorker* worker_thread,
|
||||
PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache,
|
||||
const GraphicsPipelineCacheKey& key, std::array<vk::ShaderModule, NUM_STAGES> stages,
|
||||
const std::array<const Shader::Info*, NUM_STAGES>& infos);
|
||||
@@ -132,7 +137,7 @@ private:
|
||||
template <typename Spec>
|
||||
bool ConfigureImpl(bool is_indexed);
|
||||
|
||||
void ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
bool ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
const RenderAreaPushConstant& render_are);
|
||||
|
||||
void MakePipeline(VkRenderPass render_pass);
|
||||
@@ -148,6 +153,7 @@ private:
|
||||
vk::PipelineCache& pipeline_cache;
|
||||
Scheduler& scheduler;
|
||||
GuestDescriptorQueue& guest_descriptor_queue;
|
||||
DescriptorBufferRing& descriptor_buffer_ring;
|
||||
|
||||
bool (*configure_func)(GraphicsPipeline*, bool){};
|
||||
|
||||
@@ -170,10 +176,17 @@ private:
|
||||
vk::DescriptorUpdateTemplate descriptor_update_template;
|
||||
vk::Pipeline pipeline;
|
||||
|
||||
DescriptorBufferLayout descriptor_buffer_layout;
|
||||
std::vector<DescriptorUpdateEntry> last_descriptor_payload;
|
||||
VkDeviceSize last_descriptor_buffer_offset{};
|
||||
u32 last_descriptor_buffer_chunk{};
|
||||
u64 last_descriptor_buffer_generation{};
|
||||
|
||||
std::condition_variable build_condvar;
|
||||
std::mutex build_mutex;
|
||||
std::atomic_bool is_built{false};
|
||||
bool uses_push_descriptor{false};
|
||||
bool uses_descriptor_buffer{false};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -52,6 +52,15 @@
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
using Shader::Backend::SPIRV::EmitSPIRV;
|
||||
using Shader::Maxwell::ConvertLegacyToGeneric;
|
||||
using Shader::Maxwell::GenerateGeometryPassthrough;
|
||||
using Shader::Maxwell::MergeDualVertexPrograms;
|
||||
using Shader::Maxwell::TranslateProgram;
|
||||
using VideoCommon::ComputeEnvironment;
|
||||
using VideoCommon::FileEnvironment;
|
||||
using VideoCommon::GenericEnvironment;
|
||||
using VideoCommon::GraphicsEnvironment;
|
||||
|
||||
constexpr u32 CACHE_VERSION = 18;
|
||||
constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
|
||||
@@ -296,7 +305,7 @@ size_t GetTotalPipelineWorkers() {
|
||||
std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL;
|
||||
#ifdef __ANDROID__
|
||||
const int configured = AndroidSettings::values.pipeline_worker_count.GetValue();
|
||||
const int clamped = std::clamp(configured, 4, 8);
|
||||
const int clamped = std::clamp(configured, 2, 8);
|
||||
const size_t desired = static_cast<size_t>(clamped);
|
||||
if (desired == 0) {
|
||||
return 1ULL;
|
||||
@@ -331,17 +340,20 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
const Device& device_, Scheduler& scheduler_,
|
||||
DescriptorPool& descriptor_pool_,
|
||||
GuestDescriptorQueue& guest_descriptor_queue_,
|
||||
DescriptorBufferRing& descriptor_buffer_ring_,
|
||||
RenderPassCache& render_pass_cache_, BufferCache& buffer_cache_,
|
||||
TextureCache& texture_cache_, VideoCore::ShaderNotify& shader_notify_)
|
||||
: VideoCommon::ShaderCache{device_memory_}, device{device_}, scheduler{scheduler_},
|
||||
descriptor_pool{descriptor_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
|
||||
descriptor_buffer_ring{descriptor_buffer_ring_},
|
||||
render_pass_cache{render_pass_cache_}, buffer_cache{buffer_cache_},
|
||||
texture_cache{texture_cache_}, shader_notify{shader_notify_},
|
||||
use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
|
||||
use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()},
|
||||
workers(device.HasBrokenParallelShaderCompiling() ? 1ULL : GetTotalPipelineWorkers(),
|
||||
"VkPipelineBuilder"),
|
||||
serialization_thread(1, "VkPipelineSerialization") {
|
||||
"VkPipelineBuilder", {}, Common::ThreadPlacement::Background),
|
||||
serialization_thread(1, "VkPipelineSerialization", {},
|
||||
Common::ThreadPlacement::Background) {
|
||||
const auto& float_control{device.FloatControlProperties()};
|
||||
const VkDriverId driver_id{device.GetDriverID()};
|
||||
const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()};
|
||||
@@ -602,12 +614,12 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
if (device.IsKhrPipelineExecutablePropertiesEnabled()) {
|
||||
state.statistics = std::make_unique<PipelineStatistics>(device);
|
||||
}
|
||||
const auto load_compute{[&](std::ifstream& file, VideoCommon::FileEnvironment env) {
|
||||
const auto load_compute{[&](std::ifstream& file, FileEnvironment env) {
|
||||
ComputePipelineCacheKey key;
|
||||
file.read(reinterpret_cast<char*>(&key), sizeof(key));
|
||||
|
||||
workers.QueueWork([this, key, env_ = std::move(env), &state, &callback]() mutable {
|
||||
Shader::Maxwell::ShaderPools pools;
|
||||
ShaderPools pools;
|
||||
auto pipeline{CreateComputePipeline(pools, key, env_, state.statistics.get(), false)};
|
||||
std::scoped_lock lock{state.mutex};
|
||||
if (pipeline) {
|
||||
@@ -620,7 +632,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
});
|
||||
++state.total;
|
||||
}};
|
||||
const auto load_graphics{[&](std::ifstream& file, std::vector<VideoCommon::FileEnvironment> envs) {
|
||||
const auto load_graphics{[&](std::ifstream& file, std::vector<FileEnvironment> envs) {
|
||||
GraphicsPipelineCacheKey key;
|
||||
file.read(reinterpret_cast<char*>(&key), sizeof(key));
|
||||
|
||||
@@ -653,7 +665,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
}
|
||||
|
||||
workers.QueueWork([this, key, envs_ = std::move(envs), &state, &callback]() mutable {
|
||||
Shader::Maxwell::ShaderPools pools;
|
||||
ShaderPools pools;
|
||||
boost::container::static_vector<Shader::Environment*, 5> env_ptrs;
|
||||
for (auto& env : envs_) {
|
||||
env_ptrs.push_back(&env);
|
||||
@@ -727,7 +739,10 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(Shader::Maxwell::ShaderPools& pools, const GraphicsPipelineCacheKey& key, std::span<Shader::Environment* const> envs, PipelineStatistics* statistics, bool build_in_parallel) try {
|
||||
std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
|
||||
ShaderPools& pools, const GraphicsPipelineCacheKey& key,
|
||||
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
|
||||
bool build_in_parallel) try {
|
||||
auto hash = key.Hash();
|
||||
LOG_INFO(Render_Vulkan, "{:#016x}", hash);
|
||||
size_t env_index{0};
|
||||
@@ -739,10 +754,12 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(Shader::
|
||||
Shader::IR::Program* layer_source_program{};
|
||||
|
||||
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
const bool is_emulated_stage = layer_source_program != nullptr && index == u32(Maxwell::ShaderType::Geometry);
|
||||
const bool is_emulated_stage = layer_source_program != nullptr &&
|
||||
index == static_cast<u32>(Maxwell::ShaderType::Geometry);
|
||||
if (key.unique_hashes[index] == 0 && is_emulated_stage) {
|
||||
auto topology = MaxwellToOutputTopology(key.state.topology);
|
||||
programs[index] = Shader::Maxwell::GenerateGeometryPassthrough(pools, host_info, *layer_source_program, topology);
|
||||
programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
|
||||
*layer_source_program, topology);
|
||||
continue;
|
||||
}
|
||||
if (key.unique_hashes[index] == 0) {
|
||||
@@ -751,16 +768,16 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(Shader::
|
||||
Shader::Environment& env{*envs[env_index]};
|
||||
++env_index;
|
||||
|
||||
const u32 cfg_offset{u32(env.StartAddress() + sizeof(Shader::ProgramHeader))};
|
||||
const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
|
||||
Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
|
||||
if (!uses_vertex_a || index != 1) {
|
||||
// Normal path
|
||||
programs[index] = Shader::Maxwell::TranslateProgram(pools, env, cfg, host_info);
|
||||
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
|
||||
} else {
|
||||
// VertexB path when VertexA is present.
|
||||
auto& program_va{programs[0]};
|
||||
auto program_vb{Shader::Maxwell::TranslateProgram(pools, env, cfg, host_info)};
|
||||
programs[index] = Shader::Maxwell::MergeDualVertexPrograms(program_va, program_vb, env);
|
||||
auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
|
||||
programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
|
||||
}
|
||||
|
||||
if (Settings::values.dump_guest_shaders) {
|
||||
@@ -790,8 +807,8 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(Shader::
|
||||
infos[stage_index] = &program.info;
|
||||
|
||||
const auto runtime_info{MakeRuntimeInfo(programs, key, program, previous_stage, device)};
|
||||
Shader::Maxwell::ConvertLegacyToGeneric(program, runtime_info);
|
||||
const std::vector<u32> code{Shader::Backend::SPIRV::EmitSPIRV(profile, runtime_info, program, binding)};
|
||||
ConvertLegacyToGeneric(program, runtime_info);
|
||||
const std::vector<u32> code{EmitSPIRV(profile, runtime_info, program, binding)};
|
||||
device.SaveShader(code);
|
||||
modules[stage_index] = BuildShader(device, code);
|
||||
|
||||
@@ -822,8 +839,8 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(Shader::
|
||||
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
|
||||
return std::make_unique<GraphicsPipeline>(
|
||||
scheduler, buffer_cache, texture_cache, vulkan_pipeline_cache, &shader_notify, device,
|
||||
descriptor_pool, guest_descriptor_queue, thread_worker, statistics, render_pass_cache, key,
|
||||
std::move(modules), infos);
|
||||
descriptor_pool, guest_descriptor_queue, descriptor_buffer_ring, thread_worker, statistics,
|
||||
render_pass_cache, key, std::move(modules), infos);
|
||||
|
||||
} catch (const Shader::Exception& exception) {
|
||||
auto hash = key.Hash();
|
||||
@@ -847,14 +864,14 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
|
||||
GraphicsEnvironments environments;
|
||||
GetGraphicsEnvironments(environments, graphics_key.unique_hashes);
|
||||
|
||||
main_pools.clear();
|
||||
main_pools.ReleaseContents();
|
||||
auto pipeline{
|
||||
CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(), nullptr, true)};
|
||||
if (!pipeline || pipeline_cache_filename.empty()) {
|
||||
return pipeline;
|
||||
}
|
||||
serialization_thread.QueueWork([this, key = graphics_key, envs = std::move(environments.envs)] {
|
||||
boost::container::static_vector<const VideoCommon::GenericEnvironment*, Maxwell::MaxShaderProgram>
|
||||
boost::container::static_vector<const GenericEnvironment*, Maxwell::MaxShaderProgram>
|
||||
env_ptrs;
|
||||
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
if (key.unique_hashes[index] != 0) {
|
||||
@@ -870,22 +887,24 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
const ComputePipelineCacheKey& key, const ShaderInfo* shader) {
|
||||
const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()};
|
||||
const auto& qmd{kepler_compute->launch_description};
|
||||
VideoCommon::ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
|
||||
ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
|
||||
env.SetCachedSize(shader->size_bytes);
|
||||
|
||||
main_pools.clear();
|
||||
main_pools.ReleaseContents();
|
||||
auto pipeline{CreateComputePipeline(main_pools, key, env, nullptr, true)};
|
||||
if (!pipeline || pipeline_cache_filename.empty()) {
|
||||
return pipeline;
|
||||
}
|
||||
serialization_thread.QueueWork([this, key, env_ = std::move(env)] {
|
||||
SerializePipeline(key, std::array<const VideoCommon::GenericEnvironment*, 1>{&env_},
|
||||
SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env_},
|
||||
pipeline_cache_filename, CACHE_VERSION);
|
||||
});
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(Shader::Maxwell::ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env, PipelineStatistics* statistics, bool build_in_parallel) try {
|
||||
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env,
|
||||
PipelineStatistics* statistics, bool build_in_parallel) try {
|
||||
auto hash = key.Hash();
|
||||
if (device.HasBrokenCompute()) {
|
||||
LOG_ERROR(Render_Vulkan, "Skipping {:#016x}", hash);
|
||||
@@ -901,9 +920,11 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(Shader::Ma
|
||||
env.Dump(hash, key.unique_hash);
|
||||
}
|
||||
|
||||
auto program{Shader::Maxwell::TranslateProgram(pools, env, cfg, host_info)};
|
||||
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
|
||||
const VkDriverIdKHR driver_id = device.GetDriverID();
|
||||
const bool needs_shared_mem_clamp = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||
const bool needs_shared_mem_clamp =
|
||||
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||
const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize();
|
||||
if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
@@ -913,7 +934,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(Shader::Ma
|
||||
max_shared_memory / 1024);
|
||||
program.shared_memory_size = max_shared_memory;
|
||||
}
|
||||
const std::vector<u32> code{Shader::Backend::SPIRV::EmitSPIRV(profile, program)};
|
||||
const std::vector<u32> code{EmitSPIRV(profile, program)};
|
||||
device.SaveShader(code);
|
||||
vk::ShaderModule spv_module{BuildShader(device, code)};
|
||||
|
||||
@@ -939,7 +960,8 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(Shader::Ma
|
||||
}
|
||||
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
|
||||
return std::make_unique<ComputePipeline>(device, scheduler, vulkan_pipeline_cache, descriptor_pool,
|
||||
guest_descriptor_queue, thread_worker, statistics,
|
||||
guest_descriptor_queue, descriptor_buffer_ring,
|
||||
thread_worker, statistics,
|
||||
&shader_notify, program.info, std::move(spv_module),
|
||||
key.unique_hash);
|
||||
|
||||
|
||||
@@ -11,20 +11,17 @@
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <boost/container/stable_vector.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread_worker.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
#include "shader_recompiler/frontend/maxwell/control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
|
||||
#include "shader_recompiler/frontend/maxwell/translate_program.h"
|
||||
#include "shader_recompiler/host_translate_info.h"
|
||||
#include "shader_recompiler/object_pool.h"
|
||||
#include "shader_recompiler/profile.h"
|
||||
#include "shader_recompiler/shader_pool.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/host1x/gpu_device_memory_manager.h"
|
||||
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
|
||||
@@ -91,11 +88,24 @@ class Scheduler;
|
||||
|
||||
using VideoCommon::ShaderInfo;
|
||||
|
||||
struct ShaderPools {
|
||||
void ReleaseContents() {
|
||||
flow_block.ReleaseContents();
|
||||
block.ReleaseContents();
|
||||
inst.ReleaseContents();
|
||||
}
|
||||
|
||||
Shader::ObjectPool<Shader::IR::Inst> inst{8192};
|
||||
Shader::ObjectPool<Shader::IR::Block> block{32};
|
||||
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
|
||||
};
|
||||
|
||||
class PipelineCache : public VideoCommon::ShaderCache {
|
||||
public:
|
||||
explicit PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device,
|
||||
Scheduler& scheduler, DescriptorPool& descriptor_pool,
|
||||
GuestDescriptorQueue& guest_descriptor_queue,
|
||||
DescriptorBufferRing& descriptor_buffer_ring,
|
||||
RenderPassCache& render_pass_cache, BufferCache& buffer_cache,
|
||||
TextureCache& texture_cache, VideoCore::ShaderNotify& shader_notify_);
|
||||
~PipelineCache();
|
||||
@@ -115,14 +125,14 @@ private:
|
||||
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline();
|
||||
|
||||
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline(
|
||||
Shader::Maxwell::ShaderPools& pools, const GraphicsPipelineCacheKey& key,
|
||||
ShaderPools& pools, const GraphicsPipelineCacheKey& key,
|
||||
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
|
||||
bool build_in_parallel);
|
||||
|
||||
std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineCacheKey& key,
|
||||
const ShaderInfo* shader);
|
||||
|
||||
std::unique_ptr<ComputePipeline> CreateComputePipeline(Shader::Maxwell::ShaderPools& pools,
|
||||
std::unique_ptr<ComputePipeline> CreateComputePipeline(ShaderPools& pools,
|
||||
const ComputePipelineCacheKey& key,
|
||||
Shader::Environment& env,
|
||||
PipelineStatistics* statistics,
|
||||
@@ -138,6 +148,7 @@ private:
|
||||
Scheduler& scheduler;
|
||||
DescriptorPool& descriptor_pool;
|
||||
GuestDescriptorQueue& guest_descriptor_queue;
|
||||
DescriptorBufferRing& descriptor_buffer_ring;
|
||||
RenderPassCache& render_pass_cache;
|
||||
BufferCache& buffer_cache;
|
||||
TextureCache& texture_cache;
|
||||
@@ -151,7 +162,7 @@ private:
|
||||
ankerl::unordered_dense::map<ComputePipelineCacheKey, std::unique_ptr<ComputePipeline>> compute_cache;
|
||||
ankerl::unordered_dense::map<GraphicsPipelineCacheKey, std::unique_ptr<GraphicsPipeline>> graphics_cache;
|
||||
|
||||
Shader::Maxwell::ShaderPools main_pools;
|
||||
ShaderPools main_pools;
|
||||
|
||||
Shader::Profile profile;
|
||||
Shader::HostTranslateInfo host_info;
|
||||
|
||||
@@ -266,6 +266,8 @@ void PresentManager::WaitPresent() {
|
||||
|
||||
void PresentManager::PresentThread(std::stop_token token) {
|
||||
Common::SetCurrentThreadName("VulkanPresent");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
while (!token.stop_requested()) {
|
||||
std::unique_lock lock{queue_mutex};
|
||||
// Wait for presentation frames
|
||||
|
||||
@@ -203,7 +203,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
|
||||
: gpu{gpu_}, device_memory{device_memory_}, device{device_},
|
||||
memory_allocator{memory_allocator_}, state_tracker{state_tracker_}, scheduler{scheduler_},
|
||||
staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler),
|
||||
guest_descriptor_queue(device), compute_pass_descriptor_queue(device),
|
||||
guest_descriptor_queue(device, UpdateDescriptorQueue::GUEST_FRAME_PAYLOAD_SIZE,
|
||||
device.IsExtDescriptorBufferSupported()),
|
||||
compute_pass_descriptor_queue(device, UpdateDescriptorQueue::COMPUTE_FRAME_PAYLOAD_SIZE),
|
||||
descriptor_buffer_ring(device, memory_allocator),
|
||||
blit_image(device, scheduler, state_tracker, descriptor_pool), render_pass_cache(device),
|
||||
texture_cache_runtime{
|
||||
device, scheduler, memory_allocator, staging_pool,
|
||||
@@ -216,11 +219,19 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
|
||||
staging_pool, compute_pass_descriptor_queue, descriptor_pool, texture_cache),
|
||||
query_cache(gpu, *this, device_memory, query_cache_runtime),
|
||||
pipeline_cache(device_memory, device, scheduler, descriptor_pool, guest_descriptor_queue,
|
||||
render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()),
|
||||
descriptor_buffer_ring, render_pass_cache, buffer_cache, texture_cache,
|
||||
gpu.ShaderNotify()),
|
||||
accelerate_dma(buffer_cache, texture_cache, scheduler),
|
||||
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
|
||||
wfi_event(device.GetLogical().CreateEvent()) {
|
||||
scheduler.SetQueryCache(query_cache);
|
||||
if (Settings::values.use_unified_memory.GetValue() && device_memory.IsBackingShared()) {
|
||||
buffer_cache_runtime.TryEnableUnifiedMemory(
|
||||
device_memory.GetPhysicalBase(), device_memory.GetPhysicalSize(),
|
||||
device_memory.GetBackingHardwareBuffers(),
|
||||
device_memory.GetBackingHardwareBufferWindowSize(),
|
||||
device_memory.GetBackingHardwareBufferBase());
|
||||
}
|
||||
}
|
||||
|
||||
RasterizerVulkan::~RasterizerVulkan() {
|
||||
@@ -583,7 +594,10 @@ void RasterizerVulkan::DispatchCompute() {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex};
|
||||
pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache);
|
||||
if (!pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache,
|
||||
texture_cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& qmd{kepler_compute->launch_description};
|
||||
auto indirect_address = kepler_compute->GetIndirectComputeAddress();
|
||||
@@ -882,6 +896,7 @@ void RasterizerVulkan::TickFrame() {
|
||||
draw_counter = 0;
|
||||
guest_descriptor_queue.TickFrame();
|
||||
compute_pass_descriptor_queue.TickFrame();
|
||||
descriptor_buffer_ring.TickFrame();
|
||||
fence_manager.TickFrame();
|
||||
staging_pool.TickFrame();
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_vulkan/blit_image.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_fence_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||
@@ -207,6 +208,7 @@ private:
|
||||
DescriptorPool descriptor_pool;
|
||||
GuestDescriptorQueue guest_descriptor_queue;
|
||||
ComputePassDescriptorQueue compute_pass_descriptor_queue;
|
||||
DescriptorBufferRing descriptor_buffer_ring;
|
||||
BlitImageHelper blit_image;
|
||||
RenderPassCache render_pass_cache;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,10 +15,7 @@ ResourcePool::ResourcePool(MasterSemaphore& master_semaphore_, size_t grow_step_
|
||||
: master_semaphore{&master_semaphore_}, grow_step{grow_step_} {}
|
||||
|
||||
size_t ResourcePool::CommitResource() {
|
||||
// Refresh semaphore to query updated results
|
||||
master_semaphore->Refresh();
|
||||
const u64 gpu_tick = master_semaphore->KnownGpuTick();
|
||||
const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional<size_t> {
|
||||
const auto search = [this](size_t begin, size_t end, u64 gpu_tick) -> std::optional<size_t> {
|
||||
for (size_t iterator = begin; iterator < end; ++iterator) {
|
||||
if (gpu_tick >= ticks[iterator]) {
|
||||
ticks[iterator] = master_semaphore->CurrentTick();
|
||||
@@ -24,11 +24,17 @@ size_t ResourcePool::CommitResource() {
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
// Try to find a free resource from the hinted position to the end.
|
||||
std::optional<size_t> found = search(hint_iterator, ticks.size());
|
||||
const auto find_free = [&](u64 gpu_tick) -> std::optional<size_t> {
|
||||
std::optional<size_t> result = search(hint_iterator, ticks.size(), gpu_tick);
|
||||
if (!result) {
|
||||
result = search(0, hint_iterator, gpu_tick);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
std::optional<size_t> found = find_free(master_semaphore->KnownGpuTick());
|
||||
if (!found) {
|
||||
// Search from beginning to the hinted position.
|
||||
found = search(0, hint_iterator);
|
||||
master_semaphore->Refresh();
|
||||
found = find_free(master_semaphore->KnownGpuTick());
|
||||
if (!found) {
|
||||
// Both searches failed, the pool is full; handle it.
|
||||
const size_t free_resource = ManageOverflow();
|
||||
|
||||
@@ -247,8 +247,19 @@ bool Scheduler::UpdateRescaling(bool is_rescaling) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Scheduler::UpdateDescriptorBufferChunk(u32 descriptor_chunk) {
|
||||
if (state.descriptor_buffer_bound && descriptor_chunk == state.descriptor_buffer_chunk) {
|
||||
return false;
|
||||
}
|
||||
state.descriptor_buffer_bound = true;
|
||||
state.descriptor_buffer_chunk = descriptor_chunk;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Scheduler::WorkerThread(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("VulkanWorker");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
|
||||
const auto TryPopQueue{[this](auto& work) -> bool {
|
||||
if (work_queue.empty()) {
|
||||
@@ -369,6 +380,7 @@ void Scheduler::AllocateNewContext() {
|
||||
void Scheduler::InvalidateState() {
|
||||
state.graphics_pipeline = nullptr;
|
||||
state.rescaling_defined = false;
|
||||
state.descriptor_buffer_bound = false;
|
||||
state_tracker.InvalidateCommandBufferState();
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,9 @@ public:
|
||||
/// Update the rescaling state. Returns true if the state has to be updated.
|
||||
bool UpdateRescaling(bool is_rescaling);
|
||||
|
||||
/// Returns true when the descriptor buffer chunk has to be bound into the command buffer.
|
||||
bool UpdateDescriptorBufferChunk(u32 descriptor_chunk);
|
||||
|
||||
/// Invalidates current command buffer state except for render passes
|
||||
void InvalidateState();
|
||||
|
||||
@@ -255,6 +258,8 @@ private:
|
||||
bool is_rescaling = false;
|
||||
bool rescaling_defined = false;
|
||||
bool needs_state_enable_refresh = false;
|
||||
u32 descriptor_buffer_chunk = 0;
|
||||
bool descriptor_buffer_bound = false;
|
||||
};
|
||||
|
||||
struct DeferredClear {
|
||||
|
||||
@@ -84,10 +84,16 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
|
||||
if (device.IsExtTransformFeedbackSupported()) {
|
||||
stream_ci.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
||||
}
|
||||
if (device.IsBufferDeviceAddressSupported()) {
|
||||
stream_ci.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
||||
}
|
||||
stream_buffer = memory_allocator.CreateBuffer(stream_ci, MemoryUsage::Stream);
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
stream_buffer.SetObjectNameEXT("Stream Buffer");
|
||||
}
|
||||
if (device.IsBufferDeviceAddressSupported()) {
|
||||
stream_buffer_address = device.GetLogical().GetBufferDeviceAddress(*stream_buffer);
|
||||
}
|
||||
stream_pointer = stream_buffer.Mapped();
|
||||
ASSERT_MSG(!stream_pointer.empty(), "Stream buffer must be host visible!");
|
||||
}
|
||||
@@ -149,6 +155,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
|
||||
iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT);
|
||||
return StagingBufferRef{
|
||||
.buffer = *stream_buffer,
|
||||
.device_address = stream_buffer_address,
|
||||
.offset = static_cast<VkDeviceSize>(offset),
|
||||
.mapped_span = stream_pointer.subspan(offset, size),
|
||||
.usage{},
|
||||
@@ -212,14 +219,22 @@ StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage
|
||||
if (device.IsExtTransformFeedbackSupported()) {
|
||||
buffer_ci.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
||||
}
|
||||
if (device.IsBufferDeviceAddressSupported()) {
|
||||
buffer_ci.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
||||
}
|
||||
vk::Buffer buffer = memory_allocator.CreateBuffer(buffer_ci, usage);
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
++buffer_index;
|
||||
buffer.SetObjectNameEXT(fmt::format("Staging Buffer {}", buffer_index).c_str());
|
||||
}
|
||||
const std::span<u8> mapped_span = buffer.Mapped();
|
||||
const VkDeviceAddress buffer_address =
|
||||
device.IsBufferDeviceAddressSupported()
|
||||
? device.GetLogical().GetBufferDeviceAddress(*buffer)
|
||||
: VkDeviceAddress{};
|
||||
StagingBuffer& entry = GetCache(usage)[log2_size].entries.emplace_back(StagingBuffer{
|
||||
.buffer = std::move(buffer),
|
||||
.device_address = buffer_address,
|
||||
.mapped_span = mapped_span,
|
||||
.usage = usage,
|
||||
.log2_level = log2_size,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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-3.0-or-later
|
||||
|
||||
@@ -18,6 +21,7 @@ class Scheduler;
|
||||
|
||||
struct StagingBufferRef {
|
||||
VkBuffer buffer;
|
||||
VkDeviceAddress device_address;
|
||||
VkDeviceSize offset;
|
||||
std::span<u8> mapped_span;
|
||||
MemoryUsage usage;
|
||||
@@ -50,6 +54,7 @@ private:
|
||||
|
||||
struct StagingBuffer {
|
||||
vk::Buffer buffer;
|
||||
VkDeviceAddress device_address;
|
||||
std::span<u8> mapped_span;
|
||||
MemoryUsage usage;
|
||||
u32 log2_level;
|
||||
@@ -60,6 +65,7 @@ private:
|
||||
StagingBufferRef Ref() const noexcept {
|
||||
return {
|
||||
.buffer = *buffer,
|
||||
.device_address = device_address,
|
||||
.offset = 0,
|
||||
.mapped_span = mapped_span,
|
||||
.usage = usage,
|
||||
@@ -103,6 +109,7 @@ private:
|
||||
Scheduler& scheduler;
|
||||
|
||||
vk::Buffer stream_buffer;
|
||||
VkDeviceAddress stream_buffer_address{};
|
||||
std::span<u8> stream_pointer;
|
||||
VkDeviceSize stream_buffer_size;
|
||||
VkDeviceSize region_size;
|
||||
|
||||
@@ -144,11 +144,6 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
info.size.depth == 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool WillUseWidenedAstcFormat(const Device& device, const ImageInfo& info) {
|
||||
return WillUseAcceleratedAstcDecode(device, info) &&
|
||||
!VideoCore::Surface::IsPixelFormatSRGB(info.format);
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info,
|
||||
std::optional<VkFormat> format_override = {}) {
|
||||
auto format_info =
|
||||
@@ -269,6 +264,10 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsLdrAstcFormat(VkFormat format) {
|
||||
return format >= VK_FORMAT_ASTC_4x4_UNORM_BLOCK && format <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageAspectFlags ImageViewAspectMask(const VideoCommon::ImageViewInfo& info) {
|
||||
if (info.IsRenderTarget()) {
|
||||
return ImageAspectMask(info.format);
|
||||
@@ -1780,12 +1779,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
||||
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
|
||||
runtime{&runtime_},
|
||||
original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
|
||||
WillUseWidenedAstcFormat(runtime_.device, info)
|
||||
? std::span<const VkFormat>{}
|
||||
: runtime->ViewFormats(info.format),
|
||||
WillUseWidenedAstcFormat(runtime_.device, info)
|
||||
? std::make_optional(VK_FORMAT_R32G32B32A32_SFLOAT)
|
||||
: std::nullopt)),
|
||||
runtime->ViewFormats(info.format))),
|
||||
aspect_mask(ImageAspectMask(info.format)) {
|
||||
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
|
||||
switch (Settings::values.accelerate_astc.GetValue()) {
|
||||
@@ -1812,13 +1806,9 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
||||
}
|
||||
current_image = &Image::original_image;
|
||||
storage_image_views.resize(info.resources.levels);
|
||||
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() &&
|
||||
Settings::values.astc_recompression.GetValue() ==
|
||||
Settings::AstcRecompression::Uncompressed) {
|
||||
if (WillUseAcceleratedAstcDecode(runtime->device, info)) {
|
||||
const auto& device = runtime->device.GetLogical();
|
||||
const VkFormat storage_format = WillUseWidenedAstcFormat(runtime->device, info)
|
||||
? VK_FORMAT_R32G32B32A32_SFLOAT
|
||||
: VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
const VkFormat storage_format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
for (s32 level = 0; level < info.resources.levels; ++level) {
|
||||
storage_image_views[level] =
|
||||
MakeStorageView(device, level, *original_image, storage_format);
|
||||
@@ -2204,9 +2194,7 @@ VkImageView Image::StorageImageView(s32 level) noexcept {
|
||||
auto format_info =
|
||||
MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
|
||||
if (WillUseAcceleratedAstcDecode(runtime->device, info)) {
|
||||
format_info.format = WillUseWidenedAstcFormat(runtime->device, info)
|
||||
? VK_FORMAT_R32G32B32A32_SFLOAT
|
||||
: VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
format_info.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
}
|
||||
view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image),
|
||||
format_info.format);
|
||||
@@ -2382,11 +2370,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
|
||||
SanitizeDepthStencilSwizzle(swizzle, device->SupportsDepthStencilSwizzleOne());
|
||||
}
|
||||
}
|
||||
uses_widened_astc_format = WillUseWidenedAstcFormat(*device, image.info);
|
||||
auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
|
||||
if (uses_widened_astc_format) {
|
||||
format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
}
|
||||
if (device->ApiVersion() >= VK_API_VERSION_1_3) {
|
||||
const VkFormatProperties3 properties3 =
|
||||
device->GetPhysical().GetFormatProperties3(format_info.format);
|
||||
@@ -2404,9 +2388,18 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
|
||||
.pNext = nullptr,
|
||||
.usage = clamped_view_usage,
|
||||
};
|
||||
const VkImageViewASTCDecodeModeEXT astc_decode_mode{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_ASTC_DECODE_MODE_EXT,
|
||||
.pNext = &image_view_usage,
|
||||
.decodeMode = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
};
|
||||
const void* view_next = &image_view_usage;
|
||||
if (device->IsExtAstcDecodeModeSupported() && IsLdrAstcFormat(format_info.format)) {
|
||||
view_next = &astc_decode_mode;
|
||||
}
|
||||
const VkImageViewCreateInfo create_info{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = &image_view_usage,
|
||||
.pNext = view_next,
|
||||
.flags = 0,
|
||||
.image = image.Handle(),
|
||||
.viewType = VkImageViewType{},
|
||||
@@ -2531,9 +2524,6 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type,
|
||||
if (image_format == Shader::ImageFormat::Typeless) {
|
||||
if (!typeless_storage_view) {
|
||||
auto info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
|
||||
if (uses_widened_astc_format) {
|
||||
info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
}
|
||||
typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT, texture_type);
|
||||
}
|
||||
return *typeless_storage_view;
|
||||
|
||||
@@ -436,7 +436,6 @@ private:
|
||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
u32 buffer_size = 0;
|
||||
|
||||
bool uses_widened_astc_format = false;
|
||||
bool supports_depth_comparison = false;
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user