mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge branch 'master' into vk-fixes
This commit is contained in:
		
						commit
						2fe2dd1482
					
				
					 35 changed files with 445 additions and 185 deletions
				
			
		|  | @ -9,10 +9,13 @@ import android.app.Application | |||
| import android.app.NotificationChannel | ||||
| import android.app.NotificationManager | ||||
| import android.content.Context | ||||
| import android.os.Build | ||||
| import org.citra.citra_emu.utils.DirectoryInitialization | ||||
| import org.citra.citra_emu.utils.DocumentsTree | ||||
| import org.citra.citra_emu.utils.GpuDriverHelper | ||||
| import org.citra.citra_emu.utils.PermissionsHandler | ||||
| import org.citra.citra_emu.utils.Log | ||||
| import org.citra.citra_emu.utils.MemoryUtil | ||||
| 
 | ||||
| class CitraApplication : Application() { | ||||
|     private fun createNotificationChannel() { | ||||
|  | @ -53,9 +56,20 @@ class CitraApplication : Application() { | |||
|         } | ||||
| 
 | ||||
|         NativeLibrary.logDeviceInfo() | ||||
|         logDeviceInfo() | ||||
|         createNotificationChannel() | ||||
|     } | ||||
| 
 | ||||
|     fun logDeviceInfo() { | ||||
|         Log.info("Device Manufacturer - ${Build.MANUFACTURER}") | ||||
|         Log.info("Device Model - ${Build.MODEL}") | ||||
|         if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) { | ||||
|             Log.info("SoC Manufacturer - ${Build.SOC_MANUFACTURER}") | ||||
|             Log.info("SoC Model - ${Build.SOC_MODEL}") | ||||
|         } | ||||
|         Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}") | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         private var application: CitraApplication? = null | ||||
| 
 | ||||
|  |  | |||
|  | @ -413,12 +413,12 @@ object NativeLibrary { | |||
|     } | ||||
| 
 | ||||
|     fun setEmulationActivity(emulationActivity: EmulationActivity?) { | ||||
|         Log.verbose("[NativeLibrary] Registering EmulationActivity.") | ||||
|         Log.debug("[NativeLibrary] Registering EmulationActivity.") | ||||
|         sEmulationActivity = WeakReference(emulationActivity) | ||||
|     } | ||||
| 
 | ||||
|     fun clearEmulationActivity() { | ||||
|         Log.verbose("[NativeLibrary] Unregistering EmulationActivity.") | ||||
|         Log.debug("[NativeLibrary] Unregistering EmulationActivity.") | ||||
|         sEmulationActivity.clear() | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -94,14 +94,14 @@ object DirectoryInitialization { | |||
|         val dataPath = PermissionsHandler.citraDirectory | ||||
|         if (dataPath.toString().isNotEmpty()) { | ||||
|             userPath = dataPath.toString() | ||||
|             Log.debug("[DirectoryInitialization] User Dir: $userPath") | ||||
|             android.util.Log.d("[Citra Frontend]", "[DirectoryInitialization] User Dir: $userPath") | ||||
|             return true | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
| 
 | ||||
|     private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) { | ||||
|         Log.verbose("[DirectoryInitialization] Copying File $asset to $output") | ||||
|         Log.debug("[DirectoryInitialization] Copying File $asset to $output") | ||||
|         try { | ||||
|             if (!output.exists() || overwrite) { | ||||
|                 val inputStream = context.assets.open(asset) | ||||
|  | @ -121,7 +121,7 @@ object DirectoryInitialization { | |||
|         overwrite: Boolean, | ||||
|         context: Context | ||||
|     ) { | ||||
|         Log.verbose("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder") | ||||
|         Log.debug("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder") | ||||
|         try { | ||||
|             var createdFolder = false | ||||
|             for (file in context.assets.list(assetFolder)!!) { | ||||
|  |  | |||
|  | @ -4,34 +4,17 @@ | |||
| 
 | ||||
| package org.citra.citra_emu.utils | ||||
| 
 | ||||
| import android.util.Log | ||||
| import org.citra.citra_emu.BuildConfig | ||||
| 
 | ||||
| /** | ||||
|  * Contains methods that call through to [android.util.Log], but | ||||
|  * with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log | ||||
|  * levels in release builds. | ||||
|  */ | ||||
| object Log { | ||||
|     // Tracks whether we should share the old log or the current log | ||||
|     var gameLaunched = false | ||||
|     private const val TAG = "Citra Frontend" | ||||
| 
 | ||||
|     fun verbose(message: String?) { | ||||
|         if (BuildConfig.DEBUG) { | ||||
|             Log.v(TAG, message!!) | ||||
|         } | ||||
|     } | ||||
|     external fun debug(message: String) | ||||
| 
 | ||||
|     fun debug(message: String?) { | ||||
|         if (BuildConfig.DEBUG) { | ||||
|             Log.d(TAG, message!!) | ||||
|         } | ||||
|     } | ||||
|     external fun warning(message: String) | ||||
| 
 | ||||
|     fun info(message: String?) = Log.i(TAG, message!!) | ||||
|     external fun info(message: String) | ||||
| 
 | ||||
|     fun warning(message: String?) = Log.w(TAG, message!!) | ||||
|     external fun error(message: String) | ||||
| 
 | ||||
|     fun error(message: String?) = Log.e(TAG, message!!) | ||||
|     external fun critical(message: String) | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,108 @@ | |||
| // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
| 
 | ||||
| package org.citra.citra_emu.utils | ||||
| 
 | ||||
| import android.app.ActivityManager | ||||
| import android.content.Context | ||||
| import android.os.Build | ||||
| import org.citra.citra_emu.CitraApplication | ||||
| import org.citra.citra_emu.R | ||||
| import java.util.Locale | ||||
| import kotlin.math.ceil | ||||
| 
 | ||||
| object MemoryUtil { | ||||
|     private val context get() = CitraApplication.appContext | ||||
| 
 | ||||
|     private val Float.hundredths: String | ||||
|         get() = String.format(Locale.ROOT, "%.2f", this) | ||||
| 
 | ||||
|     const val Kb: Float = 1024F | ||||
|     const val Mb = Kb * 1024 | ||||
|     const val Gb = Mb * 1024 | ||||
|     const val Tb = Gb * 1024 | ||||
|     const val Pb = Tb * 1024 | ||||
|     const val Eb = Pb * 1024 | ||||
| 
 | ||||
|     fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String = | ||||
|         when { | ||||
|             size < Kb -> { | ||||
|                 context.getString( | ||||
|                     R.string.memory_formatted, | ||||
|                     size.hundredths, | ||||
|                     context.getString(R.string.memory_byte_shorthand) | ||||
|                 ) | ||||
|             } | ||||
|             size < Mb -> { | ||||
|                 context.getString( | ||||
|                     R.string.memory_formatted, | ||||
|                     if (roundUp) ceil(size / Kb) else (size / Kb).hundredths, | ||||
|                     context.getString(R.string.memory_kilobyte) | ||||
|                 ) | ||||
|             } | ||||
|             size < Gb -> { | ||||
|                 context.getString( | ||||
|                     R.string.memory_formatted, | ||||
|                     if (roundUp) ceil(size / Mb) else (size / Mb).hundredths, | ||||
|                     context.getString(R.string.memory_megabyte) | ||||
|                 ) | ||||
|             } | ||||
|             size < Tb -> { | ||||
|                 context.getString( | ||||
|                     R.string.memory_formatted, | ||||
|                     if (roundUp) ceil(size / Gb) else (size / Gb).hundredths, | ||||
|                     context.getString(R.string.memory_gigabyte) | ||||
|                 ) | ||||
|             } | ||||
|             size < Pb -> { | ||||
|                 context.getString( | ||||
|                     R.string.memory_formatted, | ||||
|                     if (roundUp) ceil(size / Tb) else (size / Tb).hundredths, | ||||
|                     context.getString(R.string.memory_terabyte) | ||||
|                 ) | ||||
|             } | ||||
|             size < Eb -> { | ||||
|                 context.getString( | ||||
|                     R.string.memory_formatted, | ||||
|                     if (roundUp) ceil(size / Pb) else (size / Pb).hundredths, | ||||
|                     context.getString(R.string.memory_petabyte) | ||||
|                 ) | ||||
|             } | ||||
|             else -> { | ||||
|                 context.getString( | ||||
|                     R.string.memory_formatted, | ||||
|                     if (roundUp) ceil(size / Eb) else (size / Eb).hundredths, | ||||
|                     context.getString(R.string.memory_exabyte) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     val totalMemory: Float | ||||
|         get() { | ||||
|             val memInfo = ActivityManager.MemoryInfo() | ||||
|             with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) { | ||||
|                 getMemoryInfo(memInfo) | ||||
|             } | ||||
| 
 | ||||
|             return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { | ||||
|                 memInfo.advertisedMem.toFloat() | ||||
|             } else { | ||||
|                 memInfo.totalMem.toFloat() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     fun isLessThan(minimum: Int, size: Float): Boolean = | ||||
|         when (size) { | ||||
|             Kb -> totalMemory < Mb && totalMemory < minimum | ||||
|             Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum | ||||
|             Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum | ||||
|             Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum | ||||
|             Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum | ||||
|             Eb -> totalMemory / Eb < minimum | ||||
|             else -> totalMemory < Kb && totalMemory < minimum | ||||
|         } | ||||
| 
 | ||||
|     // Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for | ||||
|     // the potential error created by memInfo.totalMem | ||||
|     fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory, true) | ||||
| } | ||||
|  | @ -28,6 +28,7 @@ add_library(citra-android SHARED | |||
|     ndk_motion.cpp | ||||
|     ndk_motion.h | ||||
|     system_save_game.cpp | ||||
|     native_log.cpp | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network) | ||||
|  |  | |||
							
								
								
									
										30
									
								
								src/android/app/src/main/jni/native_log.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/android/app/src/main/jni/native_log.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
 | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| 
 | ||||
| #include <common/logging/log.h> | ||||
| #include <jni.h> | ||||
| #include "android_common/android_common.h" | ||||
| 
 | ||||
| extern "C" { | ||||
| 
 | ||||
| void Java_org_citra_citra_1emu_utils_Log_debug(JNIEnv* env, jobject obj, jstring jmessage) { | ||||
|     LOG_DEBUG(Frontend, "{}", GetJString(env, jmessage)); | ||||
| } | ||||
| 
 | ||||
| void Java_org_citra_citra_1emu_utils_Log_warning(JNIEnv* env, jobject obj, jstring jmessage) { | ||||
|     LOG_WARNING(Frontend, "{}", GetJString(env, jmessage)); | ||||
| } | ||||
| 
 | ||||
| void Java_org_citra_citra_1emu_utils_Log_info(JNIEnv* env, jobject obj, jstring jmessage) { | ||||
|     LOG_INFO(Frontend, "{}", GetJString(env, jmessage)); | ||||
| } | ||||
| 
 | ||||
| void Java_org_citra_citra_1emu_utils_Log_error(JNIEnv* env, jobject obj, jstring jmessage) { | ||||
|     LOG_ERROR(Frontend, "{}", GetJString(env, jmessage)); | ||||
| } | ||||
| 
 | ||||
| void Java_org_citra_citra_1emu_utils_Log_critical(JNIEnv* env, jobject obj, jstring jmessage) { | ||||
|     LOG_CRITICAL(Frontend, "{}", GetJString(env, jmessage)); | ||||
| } | ||||
| 
 | ||||
| } // extern "C"
 | ||||
|  | @ -442,6 +442,17 @@ | |||
|     <string name="cia_install_error_encrypted">\"%s\" must be decrypted before being used with Citra.\n A real 3DS is required</string> | ||||
|     <string name="cia_install_error_unknown">An unknown error occurred while installing \"%s\".\n Please see the log for more details</string> | ||||
| 
 | ||||
|     <!-- Memory Sizes --> | ||||
|     <string name="memory_formatted">%1$s %2$s</string> | ||||
|     <string name="memory_byte">Byte</string> | ||||
|     <string name="memory_byte_shorthand">B</string> | ||||
|     <string name="memory_kilobyte">KB</string> | ||||
|     <string name="memory_megabyte">MB</string> | ||||
|     <string name="memory_gigabyte">GB</string> | ||||
|     <string name="memory_terabyte">TB</string> | ||||
|     <string name="memory_petabyte">PB</string> | ||||
|     <string name="memory_exabyte">EB</string> | ||||
| 
 | ||||
|     <!-- Theme Modes --> | ||||
|     <string name="change_theme_mode">Change Theme Mode</string> | ||||
|     <string name="theme_mode_follow_system">Follow System</string> | ||||
|  |  | |||
|  | @ -298,9 +298,9 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, | |||
|                         b.buffer_id, | ||||
|                         state.mono_or_stereo, | ||||
|                         state.format, | ||||
|                         true, | ||||
|                         {}, // 0 in u32_dsp
 | ||||
|                         false, | ||||
|                         true,  // from_queue
 | ||||
|                         0,     // play_position
 | ||||
|                         false, // has_played
 | ||||
|                     }); | ||||
|                 } | ||||
|                 LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i, | ||||
|  | @ -321,7 +321,11 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, | |||
| void Source::GenerateFrame() { | ||||
|     current_frame.fill({}); | ||||
| 
 | ||||
|     if (state.current_buffer.empty() && !DequeueBuffer()) { | ||||
|     if (state.current_buffer.empty()) { | ||||
|         // TODO(SachinV): Should dequeue happen at the end of the frame generation?
 | ||||
|         if (DequeueBuffer()) { | ||||
|             return; | ||||
|         } | ||||
|         state.enabled = false; | ||||
|         state.buffer_update = true; | ||||
|         state.last_buffer_id = state.current_buffer_id; | ||||
|  | @ -330,8 +334,6 @@ void Source::GenerateFrame() { | |||
|     } | ||||
| 
 | ||||
|     std::size_t frame_position = 0; | ||||
| 
 | ||||
|     state.current_sample_number = state.next_sample_number; | ||||
|     while (frame_position < current_frame.size()) { | ||||
|         if (state.current_buffer.empty() && !DequeueBuffer()) { | ||||
|             break; | ||||
|  | @ -358,7 +360,7 @@ void Source::GenerateFrame() { | |||
|     } | ||||
|     // TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision
 | ||||
|     // over time
 | ||||
|     state.next_sample_number += static_cast<u32>(frame_position * state.rate_multiplier); | ||||
|     state.current_sample_number += static_cast<u32>(frame_position * state.rate_multiplier); | ||||
| 
 | ||||
|     state.filters.ProcessFrame(current_frame); | ||||
| } | ||||
|  | @ -409,7 +411,6 @@ bool Source::DequeueBuffer() { | |||
| 
 | ||||
|     // the first playthrough starts at play_position, loops start at the beginning of the buffer
 | ||||
|     state.current_sample_number = (!buf.has_played) ? buf.play_position : 0; | ||||
|     state.next_sample_number = state.current_sample_number; | ||||
|     state.current_buffer_physical_address = buf.physical_address; | ||||
|     state.current_buffer_id = buf.buffer_id; | ||||
|     state.last_buffer_id = 0; | ||||
|  | @ -420,8 +421,17 @@ bool Source::DequeueBuffer() { | |||
|         state.input_queue.push(buf); | ||||
|     } | ||||
| 
 | ||||
|     LOG_TRACE(Audio_DSP, "source_id={} buffer_id={} from_queue={} current_buffer.size()={}", | ||||
|               source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size()); | ||||
|     // Because our interpolation consumes samples instead of using an index,
 | ||||
|     // let's just consume the samples up to the current sample number.
 | ||||
|     state.current_buffer.erase( | ||||
|         state.current_buffer.begin(), | ||||
|         std::next(state.current_buffer.begin(), state.current_sample_number)); | ||||
| 
 | ||||
|     LOG_TRACE(Audio_DSP, | ||||
|               "source_id={} buffer_id={} from_queue={} current_buffer.size()={}, " | ||||
|               "buf.has_played={}, buf.play_position={}", | ||||
|               source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size(), buf.has_played, | ||||
|               buf.play_position); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -87,8 +87,8 @@ private: | |||
|         Format format; | ||||
| 
 | ||||
|         bool from_queue; | ||||
|         u32_dsp play_position; // = 0;
 | ||||
|         bool has_played;       // = false;
 | ||||
|         u32 play_position; // = 0;
 | ||||
|         bool has_played;   // = false;
 | ||||
| 
 | ||||
|     private: | ||||
|         template <class Archive> | ||||
|  | @ -136,7 +136,6 @@ private: | |||
|         // Current buffer
 | ||||
| 
 | ||||
|         u32 current_sample_number = 0; | ||||
|         u32 next_sample_number = 0; | ||||
|         PAddr current_buffer_physical_address = 0; | ||||
|         AudioInterp::StereoBuffer16 current_buffer = {}; | ||||
| 
 | ||||
|  | @ -171,7 +170,6 @@ private: | |||
|             ar& mono_or_stereo; | ||||
|             ar& format; | ||||
|             ar& current_sample_number; | ||||
|             ar& next_sample_number; | ||||
|             ar& current_buffer_physical_address; | ||||
|             ar& current_buffer; | ||||
|             ar& buffer_update; | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config: | |||
| // This must be in alphabetical order according to action name as it must have the same order as
 | ||||
| // UISetting::values.shortcuts, which is alphabetically ordered.
 | ||||
| // clang-format off
 | ||||
| const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{ | ||||
| const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{ | ||||
|      {QStringLiteral("Advance Frame"),            QStringLiteral("Main Window"), {QStringLiteral(""),       Qt::ApplicationShortcut}}, | ||||
|      {QStringLiteral("Audio Mute/Unmute"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, | ||||
|      {QStringLiteral("Audio Volume Down"),        QStringLiteral("Main Window"), {QStringLiteral(""),       Qt::WindowShortcut}}, | ||||
|  | @ -71,6 +71,11 @@ const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{ | |||
|      {QStringLiteral("Load Amiibo"),              QStringLiteral("Main Window"), {QStringLiteral("F2"),     Qt::WidgetWithChildrenShortcut}}, | ||||
|      {QStringLiteral("Load File"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, | ||||
|      {QStringLiteral("Load from Newest Slot"),    QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}}, | ||||
|      {QStringLiteral("Multiplayer Browse Public Game Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}}, | ||||
|      {QStringLiteral("Multiplayer Create Room"),              QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}}, | ||||
|      {QStringLiteral("Multiplayer Direct Connect to Room"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}}, | ||||
|      {QStringLiteral("Multiplayer Leave Room"),               QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}}, | ||||
|      {QStringLiteral("Multiplayer Show Current Room"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}}, | ||||
|      {QStringLiteral("Remove Amiibo"),            QStringLiteral("Main Window"), {QStringLiteral("F3"),     Qt::ApplicationShortcut}}, | ||||
|      {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"),     Qt::WindowShortcut}}, | ||||
|      {QStringLiteral("Rotate Screens Upright"),   QStringLiteral("Main Window"), {QStringLiteral("F8"),     Qt::WindowShortcut}}, | ||||
|  | @ -557,6 +562,15 @@ void Config::ReadMultiplayerValues() { | |||
|     UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong(); | ||||
|     UISettings::values.room_description = | ||||
|         ReadSetting(QStringLiteral("room_description"), QString{}).toString(); | ||||
|     UISettings::values.multiplayer_filter_text = | ||||
|         ReadSetting(QStringLiteral("multiplayer_filter_text"), QString{}).toString(); | ||||
|     UISettings::values.multiplayer_filter_games_owned = | ||||
|         ReadSetting(QStringLiteral("multiplayer_filter_games_owned"), false).toBool(); | ||||
|     UISettings::values.multiplayer_filter_hide_empty = | ||||
|         ReadSetting(QStringLiteral("multiplayer_filter_hide_empty"), false).toBool(); | ||||
|     UISettings::values.multiplayer_filter_hide_full = | ||||
|         ReadSetting(QStringLiteral("multiplayer_filter_hide_full"), false).toBool(); | ||||
| 
 | ||||
|     // Read ban list back
 | ||||
|     int size = qt_config->beginReadArray(QStringLiteral("username_ban_list")); | ||||
|     UISettings::values.ban_list.first.resize(size); | ||||
|  | @ -1074,6 +1088,15 @@ void Config::SaveMultiplayerValues() { | |||
|     WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0); | ||||
|     WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description, | ||||
|                  QString{}); | ||||
|     WriteSetting(QStringLiteral("multiplayer_filter_text"), | ||||
|                  UISettings::values.multiplayer_filter_text, QString{}); | ||||
|     WriteSetting(QStringLiteral("multiplayer_filter_games_owned"), | ||||
|                  UISettings::values.multiplayer_filter_games_owned, false); | ||||
|     WriteSetting(QStringLiteral("multiplayer_filter_hide_empty"), | ||||
|                  UISettings::values.multiplayer_filter_hide_empty, false); | ||||
|     WriteSetting(QStringLiteral("multiplayer_filter_hide_full"), | ||||
|                  UISettings::values.multiplayer_filter_hide_full, false); | ||||
| 
 | ||||
|     // Write ban list
 | ||||
|     qt_config->beginWriteArray(QStringLiteral("username_ban_list")); | ||||
|     for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) { | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ public: | |||
| 
 | ||||
|     static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; | ||||
|     static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; | ||||
|     static const std::array<UISettings::Shortcut, 30> default_hotkeys; | ||||
|     static const std::array<UISettings::Shortcut, 35> default_hotkeys; | ||||
| 
 | ||||
| private: | ||||
|     void Initialize(const std::string& config_name); | ||||
|  |  | |||
|  | @ -647,6 +647,13 @@ void GMainWindow::InitializeHotkeys() { | |||
|     link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame")); | ||||
|     link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot")); | ||||
|     link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot")); | ||||
|     link_action_shortcut(ui->action_View_Lobby, | ||||
|                          QStringLiteral("Multiplayer Browse Public Game Lobby")); | ||||
|     link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room")); | ||||
|     link_action_shortcut(ui->action_Connect_To_Room, | ||||
|                          QStringLiteral("Multiplayer Direct Connect to Room")); | ||||
|     link_action_shortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room")); | ||||
|     link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room")); | ||||
| 
 | ||||
|     const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) { | ||||
|         // This action will fire specifically when secondary_window is in focus
 | ||||
|  | @ -3190,8 +3197,10 @@ int main(int argc, char* argv[]) { | |||
|     QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy); | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
|     std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; | ||||
|     chdir(bin_path.c_str()); | ||||
|     auto bundle_dir = FileUtil::GetBundleDirectory(); | ||||
|     if (bundle_dir) { | ||||
|         FileUtil::SetCurrentDir(bundle_dir.value() + ".."); | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
| #ifdef ENABLE_OPENGL | ||||
|  |  | |||
|  | @ -80,9 +80,8 @@ void DirectConnectWindow::Connect() { | |||
|     // Store settings
 | ||||
|     UISettings::values.nickname = ui->nickname->text(); | ||||
|     UISettings::values.ip = ui->ip->text(); | ||||
|     UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty()) | ||||
|                                   ? ui->port->text() | ||||
|                                   : UISettings::values.port; | ||||
|     UISettings::values.port = | ||||
|         !ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port; | ||||
| 
 | ||||
|     // attempt to connect in a different thread
 | ||||
|     QFuture<void> f = QtConcurrent::run([&] { | ||||
|  |  | |||
|  | @ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list, | |||
| 
 | ||||
|     // UI Buttons
 | ||||
|     connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby); | ||||
|     connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch); | ||||
|     connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned); | ||||
|     connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty); | ||||
|     connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull); | ||||
|     connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch); | ||||
|     connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom); | ||||
|     connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom); | ||||
| 
 | ||||
|  | @ -74,6 +74,12 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list, | |||
|     connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this, | ||||
|             &Lobby::OnRefreshLobby); | ||||
| 
 | ||||
|     // Load persistent filters after events are connected to make sure they apply
 | ||||
|     ui->search->setText(UISettings::values.multiplayer_filter_text); | ||||
|     ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned); | ||||
|     ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty); | ||||
|     ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full); | ||||
| 
 | ||||
|     // manually start a refresh when the window is opening
 | ||||
|     // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
 | ||||
|     // part of the constructor, but offload the refresh until after the window shown. perhaps emit a
 | ||||
|  | @ -180,6 +186,10 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { | |||
|     UISettings::values.nickname = ui->nickname->text(); | ||||
|     UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString(); | ||||
|     UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toString(); | ||||
|     UISettings::values.multiplayer_filter_text = ui->search->text(); | ||||
|     UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked(); | ||||
|     UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked(); | ||||
|     UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked(); | ||||
| } | ||||
| 
 | ||||
| void Lobby::ResetModel() { | ||||
|  |  | |||
|  | @ -188,12 +188,37 @@ public: | |||
|     } | ||||
| 
 | ||||
|     QVariant data(int role) const override { | ||||
|         if (role != Qt::DisplayRole) { | ||||
|         switch (role) { | ||||
|         case Qt::DisplayRole: { | ||||
|             auto members = data(MemberListRole).toList(); | ||||
|             return QStringLiteral("%1 / %2").arg(QString::number(members.size()), | ||||
|                                                  data(MaxPlayerRole).toString()); | ||||
|         } | ||||
|         case Qt::ForegroundRole: { | ||||
|             auto members = data(MemberListRole).toList(); | ||||
|             auto max_players = data(MaxPlayerRole).toInt(); | ||||
|             const QColor room_full_color(255, 48, 32); | ||||
|             const QColor room_almost_full_color(255, 140, 32); | ||||
|             const QColor room_has_players_color(32, 160, 32); | ||||
|             const QColor room_empty_color(128, 128, 128); | ||||
| 
 | ||||
|             if (members.size() >= max_players) { | ||||
|                 return QBrush(room_full_color); | ||||
|             } else if (members.size() == (max_players - 1)) { | ||||
|                 return QBrush(room_almost_full_color); | ||||
|             } else if (members.size() == 0) { | ||||
|                 return QBrush(room_empty_color); | ||||
|             } else if (members.size() > 0 && members.size() < (max_players - 1)) { | ||||
|                 return QBrush(room_has_players_color); | ||||
|             } | ||||
| 
 | ||||
|             // FIXME: How to return a value that tells Qt not to modify the
 | ||||
|             // text color from the default (as if Qt::ForegroundRole wasn't overridden)?
 | ||||
|             return QBrush(nullptr); | ||||
|         } | ||||
|         default: | ||||
|             return LobbyItem::data(role); | ||||
|         } | ||||
|         auto members = data(MemberListRole).toList(); | ||||
|         return QStringLiteral("%1 / %2").arg(QString::number(members.size()), | ||||
|                                              data(MaxPlayerRole).toString()); | ||||
|     } | ||||
| 
 | ||||
|     bool operator<(const QStandardItem& other) const override { | ||||
|  |  | |||
|  | @ -138,6 +138,11 @@ struct Values { | |||
|     QString room_description; | ||||
|     std::pair<std::vector<std::string>, std::vector<std::string>> ban_list; | ||||
| 
 | ||||
|     QString multiplayer_filter_text; | ||||
|     bool multiplayer_filter_games_owned; | ||||
|     bool multiplayer_filter_hide_empty; | ||||
|     bool multiplayer_filter_hide_full; | ||||
| 
 | ||||
|     // logging
 | ||||
|     Settings::Setting<bool> show_console{false, "showConsole"}; | ||||
| }; | ||||
|  |  | |||
|  | @ -13,7 +13,6 @@ | |||
| #endif | ||||
| 
 | ||||
| // The user data dir
 | ||||
| #define ROOT_DIR "." | ||||
| #define USERDATA_DIR "user" | ||||
| #ifdef USER_DIR | ||||
| #define EMU_DATA_DIR USER_DIR | ||||
|  |  | |||
|  | @ -634,6 +634,10 @@ std::optional<std::string> GetCurrentDir() { | |||
|     std::string strDir = dir; | ||||
| #endif | ||||
|     free(dir); | ||||
| 
 | ||||
|     if (!strDir.ends_with(DIR_SEP)) { | ||||
|         strDir += DIR_SEP; | ||||
|     } | ||||
|     return strDir; | ||||
| } // namespace FileUtil
 | ||||
| 
 | ||||
|  | @ -646,17 +650,36 @@ bool SetCurrentDir(const std::string& directory) { | |||
| } | ||||
| 
 | ||||
| #if defined(__APPLE__) | ||||
| std::string GetBundleDirectory() { | ||||
|     CFURLRef BundleRef; | ||||
|     char AppBundlePath[MAXPATHLEN]; | ||||
| std::optional<std::string> GetBundleDirectory() { | ||||
|     // Get the main bundle for the app
 | ||||
|     BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); | ||||
|     CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle); | ||||
|     CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath)); | ||||
|     CFRelease(BundleRef); | ||||
|     CFRelease(BundlePath); | ||||
|     CFBundleRef bundle_ref = CFBundleGetMainBundle(); | ||||
|     if (!bundle_ref) { | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     return AppBundlePath; | ||||
|     CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref); | ||||
|     if (!bundle_url_ref) { | ||||
|         return {}; | ||||
|     } | ||||
|     SCOPE_EXIT({ CFRelease(bundle_url_ref); }); | ||||
| 
 | ||||
|     CFStringRef bundle_path_ref = CFURLCopyFileSystemPath(bundle_url_ref, kCFURLPOSIXPathStyle); | ||||
|     if (!bundle_path_ref) { | ||||
|         return {}; | ||||
|     } | ||||
|     SCOPE_EXIT({ CFRelease(bundle_path_ref); }); | ||||
| 
 | ||||
|     char app_bundle_path[MAXPATHLEN]; | ||||
|     if (!CFStringGetFileSystemRepresentation(bundle_path_ref, app_bundle_path, | ||||
|                                              sizeof(app_bundle_path))) { | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     std::string path_str(app_bundle_path); | ||||
|     if (!path_str.ends_with(DIR_SEP)) { | ||||
|         path_str += DIR_SEP; | ||||
|     } | ||||
|     return path_str; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
|  | @ -732,22 +755,6 @@ static const std::string& GetHomeDirectory() { | |||
| } | ||||
| #endif | ||||
| 
 | ||||
| std::string GetSysDirectory() { | ||||
|     std::string sysDir; | ||||
| 
 | ||||
| #if defined(__APPLE__) | ||||
|     sysDir = GetBundleDirectory(); | ||||
|     sysDir += DIR_SEP; | ||||
|     sysDir += SYSDATA_DIR; | ||||
| #else | ||||
|     sysDir = SYSDATA_DIR; | ||||
| #endif | ||||
|     sysDir += DIR_SEP; | ||||
| 
 | ||||
|     LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir); | ||||
|     return sysDir; | ||||
| } | ||||
| 
 | ||||
| namespace { | ||||
| std::unordered_map<UserPath, std::string> g_paths; | ||||
| std::unordered_map<UserPath, std::string> g_default_paths; | ||||
|  | @ -777,8 +784,10 @@ void SetUserPath(const std::string& path) { | |||
|         g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); | ||||
|         g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); | ||||
| #else | ||||
|         if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { | ||||
|             user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; | ||||
|         auto current_dir = FileUtil::GetCurrentDir(); | ||||
|         if (current_dir.has_value() && | ||||
|             FileUtil::Exists(current_dir.value() + USERDATA_DIR DIR_SEP)) { | ||||
|             user_path = current_dir.value() + USERDATA_DIR DIR_SEP; | ||||
|             g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); | ||||
|             g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); | ||||
|         } else { | ||||
|  |  | |||
|  | @ -193,11 +193,8 @@ void SetCurrentRomPath(const std::string& path); | |||
| // Update the Global Path with the new value
 | ||||
| void UpdateUserPath(UserPath path, const std::string& filename); | ||||
| 
 | ||||
| // Returns the path to where the sys file are
 | ||||
| [[nodiscard]] std::string GetSysDirectory(); | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
| [[nodiscard]] std::string GetBundleDirectory(); | ||||
| [[nodiscard]] std::optional<std::string> GetBundleDirectory(); | ||||
| #endif | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
|  |  | |||
|  | @ -14,18 +14,18 @@ namespace ConfigMem { | |||
| Handler::Handler() { | ||||
|     std::memset(&config_mem, 0, sizeof(config_mem)); | ||||
| 
 | ||||
|     // Values extracted from firmware 11.2.0-35E
 | ||||
|     config_mem.kernel_version_min = 0x34; | ||||
|     // Values extracted from firmware 11.17.0-50E
 | ||||
|     config_mem.kernel_version_min = 0x3a; | ||||
|     config_mem.kernel_version_maj = 0x2; | ||||
|     config_mem.ns_tid = 0x0004013000008002; | ||||
|     config_mem.sys_core_ver = 0x2; | ||||
|     config_mem.unit_info = 0x1; // Bit 0 set for Retail
 | ||||
|     config_mem.prev_firm = 0x1; | ||||
|     config_mem.ctr_sdk_ver = 0x0000F297; | ||||
|     config_mem.firm_version_min = 0x34; | ||||
|     config_mem.ctr_sdk_ver = 0x0000F450; | ||||
|     config_mem.firm_version_min = 0x3a; | ||||
|     config_mem.firm_version_maj = 0x2; | ||||
|     config_mem.firm_sys_core_ver = 0x2; | ||||
|     config_mem.firm_ctr_sdk_ver = 0x0000F297; | ||||
|     config_mem.firm_ctr_sdk_ver = 0x0000F450; | ||||
| } | ||||
| 
 | ||||
| ConfigMemDef& Handler::GetConfigMem() { | ||||
|  |  | |||
|  | @ -210,10 +210,10 @@ void Process::Set3dsxKernelCaps() { | |||
|     }; | ||||
| 
 | ||||
|     // Similar to Rosalina, we set kernel version to a recent one.
 | ||||
|     // This is 11.2.0, to be consistent with core/hle/kernel/config_mem.cpp
 | ||||
|     // This is 11.17.0, to be consistent with core/hle/kernel/config_mem.cpp
 | ||||
|     // TODO: refactor kernel version out so it is configurable and consistent
 | ||||
|     // among all relevant places.
 | ||||
|     kernel_version = 0x234; | ||||
|     kernel_version = 0x23a; | ||||
| } | ||||
| 
 | ||||
| void Process::Run(s32 main_thread_priority, u32 stack_size) { | ||||
|  |  | |||
|  | @ -373,7 +373,10 @@ ResultVal<AppletManager::InitializeResult> AppletManager::Initialize(AppletId ap | |||
|     if (active_slot == AppletSlot::Error) { | ||||
|         active_slot = slot; | ||||
| 
 | ||||
|         // Wake up the application.
 | ||||
|         // APT automatically calls enable on the first registered applet.
 | ||||
|         Enable(attributes); | ||||
| 
 | ||||
|         // Wake up the applet.
 | ||||
|         SendParameter({ | ||||
|             .sender_id = AppletId::None, | ||||
|             .destination_id = app_id, | ||||
|  | @ -398,7 +401,8 @@ Result AppletManager::Enable(AppletAttributes attributes) { | |||
|     auto slot_data = GetAppletSlot(slot); | ||||
|     slot_data->registered = true; | ||||
| 
 | ||||
|     if (slot_data->attributes.applet_pos == AppletPos::System && | ||||
|     if (slot_data->applet_id != AppletId::None && | ||||
|         slot_data->attributes.applet_pos == AppletPos::System && | ||||
|         slot_data->attributes.is_home_menu) { | ||||
|         slot_data->attributes.raw |= attributes.raw; | ||||
|         LOG_DEBUG(Service_APT, "Updated home menu attributes to {:08X}.", | ||||
|  | @ -786,16 +790,23 @@ Result AppletManager::PrepareToStartSystemApplet(AppletId applet_id) { | |||
| 
 | ||||
| Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object, | ||||
|                                         const std::vector<u8>& buffer) { | ||||
|     auto source_applet_id = AppletId::None; | ||||
|     auto source_applet_id = AppletId::Application; | ||||
|     if (last_system_launcher_slot != AppletSlot::Error) { | ||||
|         const auto slot_data = GetAppletSlot(last_system_launcher_slot); | ||||
|         source_applet_id = slot_data->applet_id; | ||||
|         const auto launcher_slot_data = GetAppletSlot(last_system_launcher_slot); | ||||
|         source_applet_id = launcher_slot_data->applet_id; | ||||
| 
 | ||||
|         // If a system applet is launching another system applet, reset the slot to avoid conflicts.
 | ||||
|         // This is needed because system applets won't necessarily call CloseSystemApplet before
 | ||||
|         // exiting.
 | ||||
|         if (last_system_launcher_slot == AppletSlot::SystemApplet) { | ||||
|             slot_data->Reset(); | ||||
|         // APT generally clears and terminates the caller of StartSystemApplet. This helps in
 | ||||
|         // situations such as a system applet launching another system applet, which would
 | ||||
|         // otherwise deadlock.
 | ||||
|         // TODO: In real APT, the check for AppletSlot::Application does not exist; there is
 | ||||
|         // TODO: something wrong with our implementation somewhere that makes this necessary.
 | ||||
|         // TODO: Otherwise, games that attempt to launch system applets will be cleared and
 | ||||
|         // TODO: emulation will crash.
 | ||||
|         if (!launcher_slot_data->registered || | ||||
|             (last_system_launcher_slot != AppletSlot::Application && | ||||
|              !launcher_slot_data->attributes.no_exit_on_system_applet)) { | ||||
|             launcher_slot_data->Reset(); | ||||
|             // TODO: Implement launcher process termination.
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -152,6 +152,7 @@ union AppletAttributes { | |||
|     u32 raw; | ||||
| 
 | ||||
|     BitField<0, 3, AppletPos> applet_pos; | ||||
|     BitField<28, 1, u32> no_exit_on_system_applet; | ||||
|     BitField<29, 1, u32> is_home_menu; | ||||
| 
 | ||||
|     AppletAttributes() : raw(0) {} | ||||
|  |  | |||
|  | @ -148,8 +148,6 @@ inline GLenum BlendFunc(Pica::FramebufferRegs::BlendFactor factor) { | |||
|     // Range check table for input
 | ||||
|     if (index >= blend_func_table.size()) { | ||||
|         LOG_CRITICAL(Render_OpenGL, "Unknown blend factor {}", index); | ||||
|         UNREACHABLE(); | ||||
| 
 | ||||
|         return GL_ONE; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -96,7 +96,10 @@ inline vk::BlendFactor BlendFunc(Pica::FramebufferRegs::BlendFactor factor) { | |||
|     }}; | ||||
| 
 | ||||
|     const auto index = static_cast<std::size_t>(factor); | ||||
|     ASSERT_MSG(index < blend_func_table.size(), "Unknown blend factor {}", index); | ||||
|     if (index >= blend_func_table.size()) { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Unknown blend factor {}", index); | ||||
|         return vk::BlendFactor::eOne; | ||||
|     } | ||||
|     return blend_func_table[index]; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| 
 | ||||
| #include <span> | ||||
| #include <boost/container/static_vector.hpp> | ||||
| #include <fmt/format.h> | ||||
| 
 | ||||
| #include "common/assert.h" | ||||
| #include "common/settings.h" | ||||
|  | @ -153,6 +154,12 @@ Instance::Instance(Core::TelemetrySession& telemetry, Frontend::EmuWindow& windo | |||
|     physical_device = physical_devices[physical_device_index]; | ||||
|     available_extensions = GetSupportedExtensions(physical_device); | ||||
|     properties = physical_device.getProperties(); | ||||
|     if (properties.apiVersion < TargetVulkanApiVersion) { | ||||
|         throw std::runtime_error(fmt::format( | ||||
|             "Vulkan {}.{} is required, but only {}.{} is supported by device!", | ||||
|             VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion), | ||||
|             VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion))); | ||||
|     } | ||||
| 
 | ||||
|     CollectTelemetryParameters(telemetry); | ||||
|     CreateDevice(); | ||||
|  | @ -629,7 +636,7 @@ void Instance::CreateAllocator() { | |||
|         .device = *device, | ||||
|         .pVulkanFunctions = &functions, | ||||
|         .instance = *instance, | ||||
|         .vulkanApiVersion = properties.apiVersion, | ||||
|         .vulkanApiVersion = TargetVulkanApiVersion, | ||||
|     }; | ||||
| 
 | ||||
|     const VkResult result = vmaCreateAllocator(&allocator_info, &allocator); | ||||
|  | @ -670,7 +677,7 @@ void Instance::CollectToolingInfo() { | |||
|     if (!tooling_info) { | ||||
|         return; | ||||
|     } | ||||
|     const auto tools = physical_device.getToolProperties(); | ||||
|     const auto tools = physical_device.getToolPropertiesEXT(); | ||||
|     for (const vk::PhysicalDeviceToolProperties& tool : tools) { | ||||
|         const std::string_view name = tool.name; | ||||
|         LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name); | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ | |||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <boost/container/static_vector.hpp> | ||||
| #include <fmt/format.h> | ||||
| 
 | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
|  | @ -291,13 +292,14 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library, | |||
|     } | ||||
|     VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); | ||||
| 
 | ||||
|     if (!VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion) { | ||||
|         throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!"); | ||||
|     } | ||||
| 
 | ||||
|     const u32 available_version = vk::enumerateInstanceVersion(); | ||||
|     if (available_version < VK_API_VERSION_1_1) { | ||||
|         throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!"); | ||||
|     const u32 available_version = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion | ||||
|                                       ? vk::enumerateInstanceVersion() | ||||
|                                       : VK_API_VERSION_1_0; | ||||
|     if (available_version < TargetVulkanApiVersion) { | ||||
|         throw std::runtime_error(fmt::format( | ||||
|             "Vulkan {}.{} is required, but only {}.{} is supported by instance!", | ||||
|             VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion), | ||||
|             VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version))); | ||||
|     } | ||||
| 
 | ||||
|     const auto extensions = GetInstanceExtensions(window_type, enable_validation); | ||||
|  | @ -307,7 +309,7 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library, | |||
|         .applicationVersion = VK_MAKE_VERSION(1, 0, 0), | ||||
|         .pEngineName = "Citra Vulkan", | ||||
|         .engineVersion = VK_MAKE_VERSION(1, 0, 0), | ||||
|         .apiVersion = VK_API_VERSION_1_3, | ||||
|         .apiVersion = TargetVulkanApiVersion, | ||||
|     }; | ||||
| 
 | ||||
|     boost::container::static_vector<const char*, 2> layers; | ||||
|  |  | |||
|  | @ -19,6 +19,8 @@ enum class WindowSystemType : u8; | |||
| 
 | ||||
| namespace Vulkan { | ||||
| 
 | ||||
| constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_1; | ||||
| 
 | ||||
| using DebugCallback = | ||||
|     std::variant<vk::UniqueDebugUtilsMessengerEXT, vk::UniqueDebugReportCallbackEXT>; | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <boost/container/small_vector.hpp> | ||||
| #include <boost/container/static_vector.hpp> | ||||
| 
 | ||||
| #include "common/literals.h" | ||||
| #include "common/microprofile.h" | ||||
|  | @ -118,9 +119,9 @@ u32 UnpackDepthStencil(const VideoCore::StagingData& data, vk::Format dest) { | |||
| } | ||||
| 
 | ||||
| boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers( | ||||
|     vk::ImageAspectFlags aspect, std::span<const vk::Image> images, std::size_t num_images) { | ||||
|     vk::ImageAspectFlags aspect, std::span<const vk::Image> images) { | ||||
|     boost::container::small_vector<vk::ImageMemoryBarrier, 3> barriers; | ||||
|     for (std::size_t i = 0; i < num_images; i++) { | ||||
|     for (const vk::Image& image : images) { | ||||
|         barriers.push_back(vk::ImageMemoryBarrier{ | ||||
|             .srcAccessMask = vk::AccessFlagBits::eNone, | ||||
|             .dstAccessMask = vk::AccessFlagBits::eNone, | ||||
|  | @ -128,7 +129,7 @@ boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers( | |||
|             .newLayout = vk::ImageLayout::eGeneral, | ||||
|             .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .image = images[i], | ||||
|             .image = image, | ||||
|             .subresourceRange{ | ||||
|                 .aspectMask = aspect, | ||||
|                 .baseMipLevel = 0, | ||||
|  | @ -218,11 +219,10 @@ Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, T | |||
| } | ||||
| 
 | ||||
| vk::UniqueFramebuffer MakeFramebuffer(vk::Device device, vk::RenderPass render_pass, u32 width, | ||||
|                                       u32 height, std::span<const vk::ImageView> attachments, | ||||
|                                       u32 num_attachments) { | ||||
|                                       u32 height, std::span<const vk::ImageView> attachments) { | ||||
|     const vk::FramebufferCreateInfo framebuffer_info = { | ||||
|         .renderPass = render_pass, | ||||
|         .attachmentCount = num_attachments, | ||||
|         .attachmentCount = static_cast<u32>(attachments.size()), | ||||
|         .pAttachments = attachments.data(), | ||||
|         .width = width, | ||||
|         .height = height, | ||||
|  | @ -710,8 +710,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param | |||
|     ASSERT_MSG(format != vk::Format::eUndefined && levels >= 1, | ||||
|                "Image allocation parameters are invalid"); | ||||
| 
 | ||||
|     u32 num_images = 0; | ||||
|     std::array<vk::Image, 3> raw_images; | ||||
|     boost::container::static_vector<vk::Image, 3> raw_images; | ||||
| 
 | ||||
|     vk::ImageCreateFlags flags{}; | ||||
|     if (texture_type == VideoCore::TextureType::CubeMap) { | ||||
|  | @ -724,18 +723,18 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param | |||
|     const bool need_format_list = is_mutable && instance->IsImageFormatListSupported(); | ||||
|     handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage, | ||||
|                             flags, traits.aspect, need_format_list, DebugName(false)); | ||||
|     raw_images[num_images++] = handles[0].image; | ||||
|     raw_images.emplace_back(handles[0].image); | ||||
| 
 | ||||
|     if (res_scale != 1) { | ||||
|         handles[1] = | ||||
|             MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format, | ||||
|                        traits.usage, flags, traits.aspect, need_format_list, DebugName(true)); | ||||
|         raw_images[num_images++] = handles[1].image; | ||||
|         raw_images.emplace_back(handles[1].image); | ||||
|     } | ||||
| 
 | ||||
|     runtime->render_manager.EndRendering(); | ||||
|     scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { | ||||
|         const auto barriers = MakeInitBarriers(aspect, raw_images, num_images); | ||||
|     scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { | ||||
|         const auto barriers = MakeInitBarriers(aspect, raw_images); | ||||
|         cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, | ||||
|                                vk::PipelineStageFlagBits::eTopOfPipe, | ||||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, barriers); | ||||
|  | @ -753,8 +752,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface | |||
|     const bool has_normal = mat && mat->Map(MapType::Normal); | ||||
|     const vk::Format format = traits.native; | ||||
| 
 | ||||
|     u32 num_images = 0; | ||||
|     std::array<vk::Image, 2> raw_images; | ||||
|     boost::container::static_vector<vk::Image, 2> raw_images; | ||||
| 
 | ||||
|     vk::ImageCreateFlags flags{}; | ||||
|     if (texture_type == VideoCore::TextureType::CubeMap) { | ||||
|  | @ -764,23 +762,23 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface | |||
|     const std::string debug_name = DebugName(false, true); | ||||
|     handles[0] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format, | ||||
|                             traits.usage, flags, traits.aspect, false, debug_name); | ||||
|     raw_images[num_images++] = handles[0].image; | ||||
|     raw_images.emplace_back(handles[0].image); | ||||
| 
 | ||||
|     if (res_scale != 1) { | ||||
|         handles[1] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, | ||||
|                                 vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect, | ||||
|                                 false, debug_name); | ||||
|         raw_images[num_images++] = handles[1].image; | ||||
|         raw_images.emplace_back(handles[1].image); | ||||
|     } | ||||
|     if (has_normal) { | ||||
|         handles[2] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format, | ||||
|                                 traits.usage, flags, traits.aspect, false, debug_name); | ||||
|         raw_images[num_images++] = handles[2].image; | ||||
|         raw_images.emplace_back(handles[2].image); | ||||
|     } | ||||
| 
 | ||||
|     runtime->render_manager.EndRendering(); | ||||
|     scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { | ||||
|         const auto barriers = MakeInitBarriers(aspect, raw_images, num_images); | ||||
|     scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { | ||||
|         const auto barriers = MakeInitBarriers(aspect, raw_images); | ||||
|         cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, | ||||
|                                vk::PipelineStageFlagBits::eTopOfPipe, | ||||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, barriers); | ||||
|  | @ -817,11 +815,10 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | |||
| 
 | ||||
|     scheduler->Record([buffer = runtime->upload_buffer.Handle(), format = traits.native, params, | ||||
|                        staging, upload](vk::CommandBuffer cmdbuf) { | ||||
|         u32 num_copies = 1; | ||||
|         std::array<vk::BufferImageCopy, 2> buffer_image_copies; | ||||
|         boost::container::static_vector<vk::BufferImageCopy, 2> buffer_image_copies; | ||||
| 
 | ||||
|         const auto rect = upload.texture_rect; | ||||
|         buffer_image_copies[0] = vk::BufferImageCopy{ | ||||
|         buffer_image_copies.emplace_back(vk::BufferImageCopy{ | ||||
|             .bufferOffset = upload.buffer_offset, | ||||
|             .bufferRowLength = rect.GetWidth(), | ||||
|             .bufferImageHeight = rect.GetHeight(), | ||||
|  | @ -833,15 +830,16 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | |||
|             }, | ||||
|             .imageOffset = {static_cast<s32>(rect.left), static_cast<s32>(rect.bottom), 0}, | ||||
|             .imageExtent = {rect.GetWidth(), rect.GetHeight(), 1}, | ||||
|         }; | ||||
|         }); | ||||
| 
 | ||||
|         if (params.aspect & vk::ImageAspectFlagBits::eStencil) { | ||||
|             buffer_image_copies[0].imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth; | ||||
|             vk::BufferImageCopy& stencil_copy = buffer_image_copies[1]; | ||||
| 
 | ||||
|             vk::BufferImageCopy& stencil_copy = | ||||
|                 buffer_image_copies.emplace_back(buffer_image_copies[0]); | ||||
|             stencil_copy = buffer_image_copies[0]; | ||||
|             stencil_copy.bufferOffset += UnpackDepthStencil(staging, format); | ||||
|             stencil_copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil; | ||||
|             num_copies++; | ||||
|         } | ||||
| 
 | ||||
|         const vk::ImageMemoryBarrier read_barrier = { | ||||
|  | @ -869,7 +867,7 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | |||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier); | ||||
| 
 | ||||
|         cmdbuf.copyBufferToImage(buffer, params.src_image, vk::ImageLayout::eTransferDstOptimal, | ||||
|                                  num_copies, buffer_image_copies.data()); | ||||
|                                  buffer_image_copies); | ||||
| 
 | ||||
|         cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags, | ||||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier); | ||||
|  | @ -1077,7 +1075,7 @@ void Surface::ScaleUp(u32 new_scale) { | |||
|     runtime->render_manager.EndRendering(); | ||||
|     scheduler->Record( | ||||
|         [raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { | ||||
|             const auto barriers = MakeInitBarriers(aspect, raw_images, raw_images.size()); | ||||
|             const auto barriers = MakeInitBarriers(aspect, raw_images); | ||||
|             cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, | ||||
|                                    vk::PipelineStageFlagBits::eTopOfPipe, | ||||
|                                    vk::DependencyFlagBits::eByRegion, {}, {}, barriers); | ||||
|  | @ -1343,7 +1341,7 @@ vk::Framebuffer Surface::Framebuffer() noexcept { | |||
|         runtime->render_manager.GetRenderpass(color_format, depth_format, false); | ||||
|     const auto attachments = std::array{ImageView()}; | ||||
|     framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(), | ||||
|                                           GetScaledHeight(), attachments, 1); | ||||
|                                           GetScaledHeight(), attachments); | ||||
|     return framebuffers[index].get(); | ||||
| } | ||||
| 
 | ||||
|  | @ -1473,17 +1471,16 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa | |||
|         image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView(); | ||||
|     }; | ||||
| 
 | ||||
|     u32 num_attachments = 0; | ||||
|     std::array<vk::ImageView, 2> attachments; | ||||
|     boost::container::static_vector<vk::ImageView, 2> attachments; | ||||
| 
 | ||||
|     if (color) { | ||||
|         prepare(0, color); | ||||
|         attachments[num_attachments++] = image_views[0]; | ||||
|         attachments.emplace_back(image_views[0]); | ||||
|     } | ||||
| 
 | ||||
|     if (depth) { | ||||
|         prepare(1, depth); | ||||
|         attachments[num_attachments++] = image_views[1]; | ||||
|         attachments.emplace_back(image_views[1]); | ||||
|     } | ||||
| 
 | ||||
|     const vk::Device device = runtime.GetInstance().GetDevice(); | ||||
|  | @ -1491,11 +1488,10 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa | |||
|         render_pass = | ||||
|             render_manager.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false); | ||||
|         framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(), | ||||
|                                       color->GetScaledHeight(), {}, 0); | ||||
|                                       color->GetScaledHeight(), {}); | ||||
|     } else { | ||||
|         render_pass = render_manager.GetRenderpass(formats[0], formats[1], false); | ||||
|         framebuffer = | ||||
|             MakeFramebuffer(device, render_pass, width, height, attachments, num_attachments); | ||||
|         framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue