diff --git a/.github/workflows/rolling-release.yml b/.github/workflows/rolling-release.yml index 46f815090..a7fe7af5a 100644 --- a/.github/workflows/rolling-release.yml +++ b/.github/workflows/rolling-release.yml @@ -233,9 +233,46 @@ jobs: name: "android" path: "duckstation-android-aarch64.apk" + macos-build: + runs-on: macos-10.15 + steps: + - uses: actions/checkout@v2.3.1 + with: + fetch-depth: 0 + + - name: Install packages + shell: bash + run: | + brew install qt5 sdl2 + + - name: Clone mac externals + shell: bash + run: | + git clone https://github.com/stenzek/duckstation-ext-mac.git dep/mac + + - name: Compile build + shell: bash + run: | + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SDL_FRONTEND=OFF -DBUILD_QT_FRONTEND=ON -DUSE_SDL2=ON -DQt5_DIR=/usr/local/opt/qt/lib/cmake/Qt5 .. + cmake --build . --parallel 2 + + - name: Zip macOS .app + shell: bash + run: | + cd build/bin + zip -r duckstation-mac-release.zip DuckStation.app/ + + - name: Upload macOS .app + uses: actions/upload-artifact@v1 + with: + name: "macos-x64" + path: "build/bin/duckstation-mac-release.zip" + create-release: - needs: [windows-build, windows-libretro-build, linux-build, linux-libretro-build, android-build] + needs: [windows-build, windows-libretro-build, linux-build, linux-libretro-build, android-build, macos-build] runs-on: "ubuntu-latest" if: github.ref == 'refs/heads/master' steps: @@ -279,6 +316,11 @@ jobs: with: name: "android" + - name: Download Mac App + uses: actions/download-artifact@v1 + with: + name: "macos-x64" + - name: Create release uses: "marvinpinto/action-automatic-releases@latest" with: @@ -296,4 +338,5 @@ jobs: linux-libretro/duckstation_libretro_linux_aarch64.so.zip linux-libretro/duckstation_libretro_android_aarch64.so.zip android/duckstation-android-aarch64.apk + macos-x64/duckstation-mac-release.zip diff --git a/.gitignore b/.gitignore index e39a3bd3d..e49baff2e 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ CMakeLists.txt.user # python bytecode __pycache__ + +# other repos +/dep/mac diff --git a/CMakeModules/DolphinPostprocessBundle.cmake b/CMakeModules/DolphinPostprocessBundle.cmake new file mode 100644 index 000000000..ba412ba48 --- /dev/null +++ b/CMakeModules/DolphinPostprocessBundle.cmake @@ -0,0 +1,46 @@ +# This module can be used in two different ways. +# +# When invoked as `cmake -P DolphinPostprocessBundle.cmake`, it fixes up an +# application folder to be standalone. It bundles all required libraries from +# the system and fixes up library IDs. Any additional shared libraries, like +# plugins, that are found under Contents/MacOS/ will be made standalone as well. +# +# When called with `include(DolphinPostprocessBundle)`, it defines a helper +# function `dolphin_postprocess_bundle` that sets up the command form of the +# module as a post-build step. + +if(CMAKE_GENERATOR) + # Being called as include(DolphinPostprocessBundle), so define a helper function. + set(_DOLPHIN_POSTPROCESS_BUNDLE_MODULE_LOCATION "${CMAKE_CURRENT_LIST_FILE}") + function(dolphin_postprocess_bundle target) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -DDOLPHIN_BUNDLE_PATH="$/../.." + -P "${_DOLPHIN_POSTPROCESS_BUNDLE_MODULE_LOCATION}" + ) + endfunction() + return() +endif() + +get_filename_component(DOLPHIN_BUNDLE_PATH "${DOLPHIN_BUNDLE_PATH}" ABSOLUTE) +message(STATUS "Fixing up application bundle: ${DOLPHIN_BUNDLE_PATH}") + +# Make sure to fix up any additional shared libraries (like plugins) that are +# needed. +file(GLOB_RECURSE extra_libs "${DOLPHIN_BUNDLE_PATH}/Contents/MacOS/*.dylib") + +# BundleUtilities doesn't support DYLD_FALLBACK_LIBRARY_PATH behavior, which +# makes it sometimes break on libraries that do weird things with @rpath. Specify +# equivalent search directories until https://gitlab.kitware.com/cmake/cmake/issues/16625 +# is fixed and in our minimum CMake version. +set(extra_dirs "/usr/local/lib" "/lib" "/usr/lib") + +# BundleUtilities is overly verbose, so disable most of its messages +function(message) + if(NOT ARGV MATCHES "^STATUS;") + _message(${ARGV}) + endif() +endfunction() + +include(BundleUtilities) +set(BU_CHMOD_BUNDLE_ITEMS ON) +fixup_bundle("${DOLPHIN_BUNDLE_PATH}" "${extra_libs}" "${extra_dirs}") diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b7fb79e4f..9948ecc51 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -29,6 +29,8 @@ The following people have contributed to the project in some way, and are credit - @heckez-sys - @Damaniel - @RaydenX93 + - @gp2man + - @Richard-L ## Special Thanks The following people did not directly contribute to the emulator, but it would not be in the state if not for them. diff --git a/README.md b/README.md index 394ad69f8..db538a896 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c ## Latest News +- 2020/09/01: Many additional user settings available, including memory cards and enhancements. Now you can set these per-game. +- 2020/08/25: Automated builds for macOS now available. - 2020/08/22: XInput controller backend added. - 2020/08/20: Per-game setting overrides added. Mostly for compatibility, but some options are customizable. - 2020/08/19: CPU PGXP mode added. It is very slow and incompatible with the recompiler, only use for games which need it. @@ -96,6 +98,20 @@ To download: - Run `chmod a+x` on the downloaded AppImage -- following this step, the AppImage can be run like a typical executable. - Optionally use a program such as [appimaged](https://github.com/AppImage/appimaged) or [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) for desktop integration. [AppImageUpdate](https://github.com/AppImage/AppImageUpdate) can be used alongside appimaged to easily update your DuckStation AppImage. +### macOS + +To download: + - Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download the Mac build. This is a zip archive containing the prebuilt binary. + - Alternatively, direct download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-mac-release.zip + - Extract the zip archive. If you're using Safari, apparently this happens automatically. This will give you DuckStation.app. + - Right click DuckStation.app, and click Open. As the package is not signed (Mac certificates are expensive), you must do this the first time you open it. Subsequent runs can be done by double-clicking. + +macOS support is considered experimental and not actively supported by the developer; the builds are provided here as a courtesy. Please feel free to submit issues, but it may be some time before +they are investigated. + +**macOS builds do not support automatic updates yet.** If there is sufficient demand, this may be something I will consider. + + ### Android A prebuilt APK is now available for Android. However, please keep in mind that the Android version is not yet feature complete, it is more of a preview of things to come. You will need a device running a 64-bit AArch64 userland (anything made in the last few years). @@ -170,12 +186,11 @@ Requirements: - Qt 5 (`brew install qt5`) 1. Clone the repository. Submodules aren't necessary, there is only one and it is only used for Windows. +2. Clone the mac externals repository (for MoltenVK): `git clone https://github.com/stenzek/duckstation-ext-mac.git dep/mac`. 2. Create a build directory, either in-tree or elsewhere, e.g. `mkdir build-release`, `cd build-release`. 3. Run cmake to configure the build system: `cmake -DCMAKE_BUILD_TYPE=Release -DQt5_DIR=/usr/local/opt/qt/lib/cmake/Qt5 ..`. You may need to tweak `Qt5_DIR` depending on your system. 4. Compile the source code: `make`. Use `make -jN` where `N` is the number of CPU cores in your system for a faster build. -5. Run the binary, located in the build directory under `bin/duckstation-sdl`, or `bin/duckstation-qt`. - -Application bundles/.apps are currently not created, so you can't launch it via Finder yet. This is planned for the future. +5. Run the binary, located in the build directory under `bin/duckstation-sdl`, or `bin/DuckStation.app` for Qt. ### Android **NOTE:** The Android frontend is still incomplete, not all functionality is available yet. User directory is hardcoded to `/sdcard/duckstation` for now. diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index 37a750962..7bfef59df 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/android/app/build.gradle b/android/app/build.gradle index 782b0d479..d0b279d7c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -24,6 +24,10 @@ android { version "3.10.2" } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { externalNativeBuild { cmake { diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index d13d6c205..8c5e18936 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -5,10 +5,10 @@ #include "common/string.h" #include "common/timestamp.h" #include "core/controller.h" -#include "core/game_list.h" #include "core/gpu.h" #include "core/host_display.h" #include "core/system.h" +#include "frontend-common/game_list.h" #include "frontend-common/imgui_styles.h" #include "frontend-common/opengl_host_display.h" #include "frontend-common/vulkan_host_display.h" @@ -20,7 +20,14 @@ Log_SetChannel(AndroidHostInterface); static JavaVM* s_jvm; static jclass s_AndroidHostInterface_class; static jmethodID s_AndroidHostInterface_constructor; -static jfieldID s_AndroidHostInterface_field_nativePointer; +static jfieldID s_AndroidHostInterface_field_mNativePointer; +static jmethodID s_AndroidHostInterface_method_reportError; +static jmethodID s_AndroidHostInterface_method_reportMessage; +static jmethodID s_EmulationActivity_method_reportError; +static jmethodID s_EmulationActivity_method_reportMessage; +static jmethodID s_EmulationActivity_method_onEmulationStarted; +static jmethodID s_EmulationActivity_method_onEmulationStopped; +static jmethodID s_EmulationActivity_method_onGameTitleChanged; namespace AndroidHelpers { // helper for retrieving the current per-thread jni environment @@ -36,7 +43,7 @@ JNIEnv* GetJNIEnv() AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj) { return reinterpret_cast( - static_cast(env->GetLongField(obj, s_AndroidHostInterface_field_nativePointer))); + static_cast(env->GetLongField(obj, s_AndroidHostInterface_field_mNativePointer))); } std::string JStringToString(JNIEnv* env, jstring str) @@ -95,12 +102,26 @@ void AndroidHostInterface::RequestExit() void AndroidHostInterface::ReportError(const char* message) { - HostInterface::ReportError(message); + CommonHostInterface::ReportError(message); + + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + jstring message_jstr = env->NewStringUTF(message); + if (m_emulation_activity_object) + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_reportError, message_jstr); + else + env->CallVoidMethod(m_java_object, s_AndroidHostInterface_method_reportError, message_jstr); } void AndroidHostInterface::ReportMessage(const char* message) { - HostInterface::ReportMessage(message); + CommonHostInterface::ReportMessage(message); + + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + jstring message_jstr = env->NewStringUTF(message); + if (m_emulation_activity_object) + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_reportMessage, message_jstr); + else + env->CallVoidMethod(m_java_object, s_AndroidHostInterface_method_reportMessage, message_jstr); } std::string AndroidHostInterface::GetStringSettingValue(const char* section, const char* key, const char* default_value) @@ -141,22 +162,17 @@ void AndroidHostInterface::UpdateInputMap() CommonHostInterface::UpdateInputMap(m_settings_interface); } -bool AndroidHostInterface::StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params) +bool AndroidHostInterface::StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface, + SystemBootParameters boot_params, bool resume_state) { Assert(!IsEmulationThreadRunning()); + emulation_activity = AndroidHelpers::GetJNIEnv()->NewGlobalRef(emulation_activity); + Log_DevPrintf("Starting emulation thread..."); m_emulation_thread_stop_request.store(false); - m_emulation_thread = - std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, initial_surface, std::move(boot_params)); - m_emulation_thread_started.Wait(); - if (!m_emulation_thread_start_result.load()) - { - m_emulation_thread.join(); - Log_ErrorPrint("Failed to start emulation in thread"); - return false; - } - + m_emulation_thread = std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, emulation_activity, + initial_surface, std::move(boot_params), resume_state); return true; } @@ -196,36 +212,48 @@ void AndroidHostInterface::RunOnEmulationThread(std::function function, m_callback_mutex.unlock(); } -void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params) +void AndroidHostInterface::EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface, + SystemBootParameters boot_params, bool resume_state) { JNIEnv* thread_env; if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK) { - Log_ErrorPrintf("Failed to attach JNI to thread"); - m_emulation_thread_start_result.store(false); - m_emulation_thread_started.Signal(); + ReportError("Failed to attach JNI to thread"); return; } CreateImGuiContext(); m_surface = initial_surface; - ApplySettings(); + m_emulation_activity_object = emulation_activity; + ApplySettings(true); // Boot system. - if (!BootSystem(boot_params)) + bool boot_result = false; + if (resume_state) { - Log_ErrorPrintf("Failed to boot system on emulation thread (file:%s).", boot_params.filename.c_str()); + if (boot_params.filename.empty()) + boot_result = ResumeSystemFromMostRecentState(); + else + boot_result = ResumeSystemFromState(boot_params.filename.c_str(), true); + } + else + { + boot_result = BootSystem(boot_params); + } + + if (!boot_result) + { + ReportFormattedError("Failed to boot system on emulation thread (file:%s).", boot_params.filename.c_str()); DestroyImGuiContext(); - m_emulation_thread_start_result.store(false); - m_emulation_thread_started.Signal(); + thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); + thread_env->DeleteGlobalRef(m_emulation_activity_object); + m_emulation_activity_object = {}; s_jvm->DetachCurrentThread(); return; } // System is ready to go. - m_emulation_thread_start_result.store(true); - m_emulation_thread_started.Signal(); - + thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted); while (!m_emulation_thread_stop_request.load()) { // run any events @@ -264,8 +292,11 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf } } - DestroySystem(); + thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); + PowerOffSystem(); DestroyImGuiContext(); + thread_env->DeleteGlobalRef(m_emulation_activity_object); + m_emulation_activity_object = {}; s_jvm->DetachCurrentThread(); } @@ -308,9 +339,27 @@ void AndroidHostInterface::ReleaseHostDisplay() m_display.reset(); } +void AndroidHostInterface::OnSystemDestroyed() +{ + CommonHostInterface::OnSystemDestroyed(); + ClearOSDMessages(); +} + +void AndroidHostInterface::OnRunningGameChanged() +{ + CommonHostInterface::OnRunningGameChanged(); + + if (m_emulation_activity_object) + { + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + jstring title_string = env->NewStringUTF(System::GetRunningTitle().c_str()); + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onGameTitleChanged, title_string); + } +} + void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, int width, int height) { - Log_InfoPrintf("SurfaceChanged %p %d %d %d", surface, format, width, height); + ReportFormattedMessage("SurfaceChanged %p %d %d %d", surface, format, width, height); if (m_surface == surface) { if (m_display) @@ -412,10 +461,11 @@ void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidat m_game_list->Refresh(invalidate_cache, invalidate_database); } -void AndroidHostInterface::ApplySettings() +void AndroidHostInterface::ApplySettings(bool display_osd_messages) { Settings old_settings = std::move(g_settings); CommonHostInterface::LoadSettings(m_settings_interface); + CommonHostInterface::FixIncompatibleSettings(display_osd_messages); CheckForSettingsChanges(old_settings); } @@ -439,10 +489,26 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) return -1; } - if ((s_AndroidHostInterface_constructor = env->GetMethodID(s_AndroidHostInterface_class, "", "()V")) == - nullptr || - (s_AndroidHostInterface_field_nativePointer = - env->GetFieldID(s_AndroidHostInterface_class, "nativePointer", "J")) == nullptr) + jclass emulation_activity_class; + if ((s_AndroidHostInterface_constructor = + env->GetMethodID(s_AndroidHostInterface_class, "", "(Landroid/content/Context;)V")) == nullptr || + (s_AndroidHostInterface_field_mNativePointer = + env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr || + (s_AndroidHostInterface_method_reportError = + env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || + (s_AndroidHostInterface_method_reportMessage = + env->GetMethodID(s_AndroidHostInterface_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr || + (emulation_activity_class = env->FindClass("com/github/stenzek/duckstation/EmulationActivity")) == nullptr || + (s_EmulationActivity_method_reportError = + env->GetMethodID(emulation_activity_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || + (s_EmulationActivity_method_reportMessage = + env->GetMethodID(emulation_activity_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr || + (s_EmulationActivity_method_onEmulationStarted = + env->GetMethodID(emulation_activity_class, "onEmulationStarted", "()V")) == nullptr || + (s_EmulationActivity_method_onEmulationStopped = + env->GetMethodID(emulation_activity_class, "onEmulationStopped", "()V")) == nullptr || + (s_EmulationActivity_method_onGameTitleChanged = + env->GetMethodID(emulation_activity_class, "onGameTitleChanged", "(Ljava/lang/String;)V")) == nullptr) { Log_ErrorPrint("AndroidHostInterface lookups failed"); return -1; @@ -457,12 +523,13 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) #define DEFINE_JNI_ARGS_METHOD(return_type, name, ...) \ extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env, __VA_ARGS__) -DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object, jstring user_directory) +DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object, + jstring user_directory) { Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG); // initialize the java side - jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor); + jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor, context_object); if (!java_obj) { Log_ErrorPrint("Failed to create Java AndroidHostInterface"); @@ -483,7 +550,7 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, job return nullptr; } - env->SetLongField(java_obj, s_AndroidHostInterface_field_nativePointer, + env->SetLongField(java_obj, s_AndroidHostInterface_field_mNativePointer, static_cast(reinterpret_cast(cpp_obj))); return java_obj; @@ -494,8 +561,8 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isEmulationThreadRunning, return AndroidHelpers::GetNativeClass(env, obj)->IsEmulationThreadRunning(); } -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject surface, - jstring filename, jstring state_filename) +DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject emulationActivity, + jobject surface, jstring filename, jboolean resume_state, jstring state_filename) { ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface); if (!native_surface) @@ -509,7 +576,8 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobj SystemBootParameters boot_params; boot_params.filename = AndroidHelpers::JStringToString(env, filename); - return AndroidHelpers::GetNativeClass(env, obj)->StartEmulationThread(native_surface, std::move(boot_params)); + return AndroidHelpers::GetNativeClass(env, obj)->StartEmulationThread(emulationActivity, native_surface, + std::move(boot_params), resume_state); } DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_stopEmulationThread, jobject obj) @@ -526,7 +594,8 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, j AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); hi->RunOnEmulationThread( - [hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, true); + [hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, + false); } DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerType, jobject obj, jint index, jstring controller_type) @@ -629,11 +698,11 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj) AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); if (hi->IsEmulationThreadRunning()) { - hi->RunOnEmulationThread([hi]() { hi->ApplySettings(); }); + hi->RunOnEmulationThread([hi]() { hi->ApplySettings(false); }); } else { - hi->ApplySettings(); + hi->ApplySettings(false); } } diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h index fa72ca339..5667631b4 100644 --- a/android/app/src/cpp/android_host_interface.h +++ b/android/app/src/cpp/android_host_interface.h @@ -35,7 +35,8 @@ public: float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override; bool IsEmulationThreadRunning() const { return m_emulation_thread.joinable(); } - bool StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params); + bool StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface, + SystemBootParameters boot_params, bool resume_state); void RunOnEmulationThread(std::function function, bool blocking = false); void StopEmulationThread(); @@ -46,7 +47,7 @@ public: void SetControllerAxisState(u32 index, s32 button_code, float value); void RefreshGameList(bool invalidate_cache, bool invalidate_database); - void ApplySettings(); + void ApplySettings(bool display_osd_messages); protected: void SetUserDirectory() override; @@ -56,13 +57,18 @@ protected: bool AcquireHostDisplay() override; void ReleaseHostDisplay() override; + void OnSystemDestroyed() override; + void OnRunningGameChanged() override; + private: - void EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params); + void EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface, + SystemBootParameters boot_params, bool resume_state); void CreateImGuiContext(); void DestroyImGuiContext(); jobject m_java_object = {}; + jobject m_emulation_activity_object = {}; AndroidSettingsInterface m_settings_interface; @@ -73,8 +79,6 @@ private: std::thread m_emulation_thread; std::atomic_bool m_emulation_thread_stop_request{false}; - std::atomic_bool m_emulation_thread_start_result{false}; - Common::Event m_emulation_thread_started; }; namespace AndroidHelpers { diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java index 59231f01e..87313d2a5 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java @@ -4,37 +4,57 @@ import android.content.Context; import android.os.Environment; import android.util.Log; import android.view.Surface; +import android.widget.Toast; -public class AndroidHostInterface -{ - private long nativePointer; +import com.google.android.material.snackbar.Snackbar; + +public class AndroidHostInterface { + private long mNativePointer; + private Context mContext; static public native AndroidHostInterface create(Context context, String userDirectory); - public AndroidHostInterface(long nativePointer) - { - this.nativePointer = nativePointer; + public AndroidHostInterface(Context context) { + this.mContext = context; + } + + public void reportError(String message) { + Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); + } + + public void reportMessage(String message) { + Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); } public native boolean isEmulationThreadRunning(); - public native boolean startEmulationThread(Surface surface, String filename, String state_filename); + + public native boolean startEmulationThread(EmulationActivity emulationActivity, Surface surface, String filename, boolean resumeState, String state_filename); + public native void stopEmulationThread(); public native void surfaceChanged(Surface surface, int format, int width, int height); // TODO: Find a better place for this. public native void setControllerType(int index, String typeName); + public native void setControllerButtonState(int index, int buttonCode, boolean pressed); + public native void setControllerAxisState(int index, int axisCode, float value); + public static native int getControllerButtonCode(String controllerType, String buttonName); + public static native int getControllerAxisCode(String controllerType, String axisName); public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase); + public native GameListEntry[] getGameListEntries(); public native void resetSystem(); + public native void loadState(boolean global, int slot); + public native void saveState(boolean global, int slot); + public native void applySettings(); static { @@ -42,6 +62,7 @@ public class AndroidHostInterface } static private AndroidHostInterface mInstance; + static public boolean createInstance(Context context) { // Set user path. String externalStorageDirectory = Environment.getExternalStorageDirectory().getAbsolutePath(); @@ -57,6 +78,7 @@ public class AndroidHostInterface static public boolean hasInstance() { return mInstance != null; } + static public AndroidHostInterface getInstance() { return mInstance; } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java index 8497145ec..66ba697f7 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java @@ -3,6 +3,7 @@ package com.github.stenzek.duckstation; import android.annotation.SuppressLint; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; @@ -11,14 +12,12 @@ import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.Menu; -import android.view.MotionEvent; import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.View; import android.view.MenuItem; import android.widget.FrameLayout; +import android.widget.Toast; -import androidx.core.app.NavUtils; import androidx.preference.PreferenceManager; /** @@ -30,77 +29,69 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde * Settings interfaces. */ SharedPreferences mPreferences; + private boolean getBooleanSetting(String key, boolean defaultValue) { return mPreferences.getBoolean(key, defaultValue); } + private void setBooleanSetting(String key, boolean value) { SharedPreferences.Editor editor = mPreferences.edit(); editor.putBoolean(key, value); editor.apply(); } + private String getStringSetting(String key, String defaultValue) { return mPreferences.getString(key, defaultValue); } - /** - * Touchscreen controller overlay - */ - TouchscreenControllerView mTouchscreenController; - private boolean mTouchscreenControllerVisible = true; + public void reportError(String message) { + Log.e("EmulationActivity", message); - /** - * Whether or not the system UI should be auto-hidden after - * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds. - */ - private static final boolean AUTO_HIDE = true; + Object lock = new Object(); + runOnUiThread(() -> { + // Toast.makeText(this, message, Toast.LENGTH_LONG); + new AlertDialog.Builder(this) + .setTitle("Error") + .setMessage(message) + .setPositiveButton("OK", (dialog, button) -> { + dialog.dismiss(); + synchronized (lock) { + lock.notify(); + } + }) + .create() + .show(); + }); - /** - * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after - * user interaction before hiding the system UI. - */ - private static final int AUTO_HIDE_DELAY_MILLIS = 3000; - - /** - * Some older devices needs a small delay between UI widget updates - * and a change of the status and navigation bar. - */ - private static final int UI_ANIMATION_DELAY = 300; - private final Handler mHideHandler = new Handler(); - private EmulationSurfaceView mContentView; - private final Runnable mHidePart2Runnable = new Runnable() { - @SuppressLint("InlinedApi") - @Override - public void run() { - // Delayed removal of status and navigation bar - - // Note that some of these constants are new as of API 16 (Jelly Bean) - // and API 19 (KitKat). It is safe to use them, as they are inlined - // at compile-time and do nothing on earlier devices. - mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - } - }; - private final Runnable mShowPart2Runnable = new Runnable() { - @Override - public void run() { - // Delayed display of UI elements - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.show(); + synchronized (lock) { + try { + lock.wait(); + } catch (InterruptedException e) { } } - }; - private boolean mVisible; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - hide(); - } - }; + } + + public void reportMessage(String message) { + Log.i("EmulationActivity", message); + runOnUiThread(() -> { + Toast.makeText(this, message, Toast.LENGTH_SHORT); + }); + } + + public void onEmulationStarted() { + } + + public void onEmulationStopped() { + runOnUiThread(() -> { + finish(); + }); + } + + public void onGameTitleChanged(String title) { + runOnUiThread(() -> { + setTitle(title); + }); + } @Override public void surfaceCreated(SurfaceHolder holder) { @@ -114,16 +105,11 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde return; } - String bootPath = getIntent().getStringExtra("bootPath"); - String bootSaveStatePath = getIntent().getStringExtra("bootSaveStatePath"); - boolean resumeState = getIntent().getBooleanExtra("resumeState", false); + final String bootPath = getIntent().getStringExtra("bootPath"); + final boolean resumeState = getIntent().getBooleanExtra("resumeState", false); + final String bootSaveStatePath = getIntent().getStringExtra("saveStatePath"); - if (!AndroidHostInterface.getInstance() - .startEmulationThread(holder.getSurface(), bootPath, bootSaveStatePath)) { - Log.e("EmulationActivity", "Failed to start emulation thread"); - finishActivity(0); - return; - } + AndroidHostInterface.getInstance().startEmulationThread(this, holder.getSurface(), bootPath, resumeState, bootSaveStatePath); } @Override @@ -146,14 +132,14 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde actionBar.setDisplayHomeAsUpEnabled(true); } - mVisible = true; + mSystemUIVisible = true; mContentView = findViewById(R.id.fullscreen_content); mContentView.getHolder().addCallback(this); mContentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (mVisible) - hide(); + if (mSystemUIVisible) + hideSystemUI(); } }); @@ -173,11 +159,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); + hideSystemUI(); + } - // Trigger the initial hide() shortly after the activity has been - // created, to briefly hint to the user that UI controls - // are available. - delayedHide(100); + @Override + protected void onStop() { + super.onStop(); + + if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) { + AndroidHostInterface.getInstance().stopEmulationThread(); + } } @Override @@ -228,37 +219,79 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde @Override public void onBackPressed() { - if (mVisible) { + if (mSystemUIVisible) { finish(); return; } - show(); + showSystemUI(); } - private void hide() { + /** + * Some older devices needs a small delay between UI widget updates + * and a change of the status and navigation bar. + */ + private static final int UI_ANIMATION_DELAY = 300; + private final Handler mSystemUIHideHandler = new Handler(); + private EmulationSurfaceView mContentView; + private final Runnable mHidePart2Runnable = new Runnable() { + @SuppressLint("InlinedApi") + @Override + public void run() { + // Delayed removal of status and navigation bar + + // Note that some of these constants are new as of API 16 (Jelly Bean) + // and API 19 (KitKat). It is safe to use them, as they are inlined + // at compile-time and do nothing on earlier devices. + mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + } + }; + private final Runnable mShowPart2Runnable = new Runnable() { + @Override + public void run() { + // Delayed display of UI elements + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + } + }; + private boolean mSystemUIVisible; + private final Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hideSystemUI(); + } + }; + + private void hideSystemUI() { // Hide UI first ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.hide(); } - mVisible = false; + mSystemUIVisible = false; // Schedule a runnable to remove the status and navigation bar after a delay - mHideHandler.removeCallbacks(mShowPart2Runnable); - mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); + mSystemUIHideHandler.removeCallbacks(mShowPart2Runnable); + mSystemUIHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); } @SuppressLint("InlinedApi") - private void show() { + private void showSystemUI() { // Show the system bar mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - mVisible = true; + mSystemUIVisible = true; // Schedule a runnable to display UI elements after a delay - mHideHandler.removeCallbacks(mHidePart2Runnable); - mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); + mSystemUIHideHandler.removeCallbacks(mHidePart2Runnable); + mSystemUIHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); } /** @@ -266,10 +299,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde * previously scheduled calls. */ private void delayedHide(int delayMillis) { - mHideHandler.removeCallbacks(mHideRunnable); - mHideHandler.postDelayed(mHideRunnable, delayMillis); + mSystemUIHideHandler.removeCallbacks(mHideRunnable); + mSystemUIHideHandler.postDelayed(mHideRunnable, delayMillis); } + /** + * Touchscreen controller overlay + */ + TouchscreenControllerView mTouchscreenController; + private boolean mTouchscreenControllerVisible = true; + private void setTouchscreenControllerVisibility(boolean visible) { mTouchscreenControllerVisible = visible; mTouchscreenController.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java b/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java index 98825fd55..5f1a098c6 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java @@ -17,13 +17,13 @@ import java.lang.reflect.Array; import java.lang.reflect.Method; public final class FileUtil { - static String TAG="TAG"; + static String TAG = "TAG"; private static final String PRIMARY_VOLUME_NAME = "primary"; @Nullable public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) { if (treeUri == null) return null; - String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),con); + String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con); if (volumePath == null) return File.separator; if (volumePath.endsWith(File.separator)) volumePath = volumePath.substring(0, volumePath.length() - 1); @@ -37,8 +37,7 @@ public final class FileUtil { return volumePath + documentPath; else return volumePath + File.separator + documentPath; - } - else return volumePath; + } else return volumePath; } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameList.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameList.java index 75d15487b..ec23b9c56 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameList.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/GameList.java @@ -12,6 +12,8 @@ import android.widget.TextView; import androidx.preference.PreferenceManager; +import java.util.Arrays; +import java.util.Comparator; import java.util.Set; public class GameList { @@ -25,10 +27,19 @@ public class GameList { mEntries = new GameListEntry[0]; } + private class GameListEntryComparator implements Comparator { + @Override + public int compare(GameListEntry left, GameListEntry right) { + return left.getTitle().compareTo(right.getTitle()); + } + } + + public void refresh(boolean invalidateCache, boolean invalidateDatabase) { // Search and get entries from native code AndroidHostInterface.getInstance().refreshGameList(invalidateCache, invalidateDatabase); mEntries = AndroidHostInterface.getInstance().getGameListEntries(); + Arrays.sort(mEntries, new GameListEntryComparator()); mAdapter.notifyDataSetChanged(); } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java index eb3fd8d63..e7d01a300 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java @@ -7,14 +7,12 @@ import android.widget.TextView; import androidx.core.content.ContextCompat; public class GameListEntry { - public enum EntryType - { + public enum EntryType { Disc, PSExe } - public enum CompatibilityRating - { + public enum CompatibilityRating { Unknown, DoesntBoot, CrashesInIntro, @@ -72,15 +70,21 @@ public class GameListEntry { return mTitle; } - public String getModifiedTime() { return mModifiedTime; } + public String getModifiedTime() { + return mModifiedTime; + } public DiscRegion getRegion() { return mRegion; } - public EntryType getType() { return mType; } + public EntryType getType() { + return mType; + } - public CompatibilityRating getCompatibilityRating() { return mCompatibilityRating; } + public CompatibilityRating getCompatibilityRating() { + return mCompatibilityRating; + } public void fillView(View view) { ((TextView) view.findViewById(R.id.game_list_view_entry_title)).setText(mTitle); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java index 95d2fe8cd..a2f9523e3 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java @@ -28,27 +28,26 @@ import android.view.MenuItem; import android.widget.AdapterView; import android.widget.ListView; import android.widget.PopupMenu; +import android.widget.Toast; import java.util.HashSet; import java.util.Set; import java.util.prefs.Preferences; +import static com.google.android.material.snackbar.Snackbar.make; + public class MainActivity extends AppCompatActivity { private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1; private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2; private GameList mGameList; private ListView mGameListView; + private boolean mHasExternalStoragePermissions = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (!AndroidHostInterface.hasInstance() && !AndroidHostInterface.createInstance(this)) { - Log.i("MainActivity", "Failed to create host interface"); - throw new RuntimeException("Failed to create host interface"); - } - setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); @@ -86,6 +85,14 @@ public class MainActivity extends AppCompatActivity { menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.game_list_entry_menu_start_game) { + startEmulation(mGameList.getEntry(position).getPath(), false); + return true; + } else if (id == R.id.game_list_entry_menu_resume_game) { + startEmulation(mGameList.getEntry(position).getPath(), true); + return true; + } return false; } }); @@ -93,6 +100,18 @@ public class MainActivity extends AppCompatActivity { return true; } }); + + mHasExternalStoragePermissions = checkForExternalStoragePermissions(); + if (mHasExternalStoragePermissions) + completeStartup(); + } + + private void completeStartup() { + if (!AndroidHostInterface.hasInstance() && !AndroidHostInterface.createInstance(this)) { + Log.i("MainActivity", "Failed to create host interface"); + throw new RuntimeException("Failed to create host interface"); + } + mGameList.refresh(false, false); } @@ -122,12 +141,17 @@ public class MainActivity extends AppCompatActivity { int id = item.getItemId(); //noinspection SimplifiableIfStatement - if (id == R.id.action_add_game_directory) { + if (id == R.id.action_resume) { + startEmulation(null, true); + } else if (id == R.id.action_start_bios) { + startEmulation(null, false); + } else if (id == R.id.action_add_game_directory) { startAddGameDirectory(); } else if (id == R.id.action_scan_for_new_games) { mGameList.refresh(false, false); - } if (id == R.id.action_rescan_all_games) { - mGameList.refresh(true, false); + } + if (id == R.id.action_rescan_all_games) { + mGameList.refresh(true, true); } if (id == R.id.action_settings) { Intent intent = new Intent(this, SettingsActivity.class); @@ -190,19 +214,21 @@ public class MainActivity extends AppCompatActivity { int[] grantResults) { // check that all were successful for (int i = 0; i < grantResults.length; i++) { - if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { - Snackbar.make(mGameListView, - "External storage permissions are required to start emulation.", - Snackbar.LENGTH_LONG); + if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { + if (!mHasExternalStoragePermissions) { + mHasExternalStoragePermissions = true; + completeStartup(); + } + } else { + Toast.makeText(this, + "External storage permissions are required to use DuckStation.", + Toast.LENGTH_LONG); + finish(); } } } private boolean startEmulation(String bootPath, boolean resumeState) { - if (!checkForExternalStoragePermissions()) { - return false; - } - Intent intent = new Intent(this, EmulationActivity.class); intent.putExtra("bootPath", bootPath); intent.putExtra("resumeState", resumeState); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java index 2ace2d51b..b5986e3e0 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java @@ -19,25 +19,24 @@ public class TouchscreenControllerButtonView extends View { private String mButtonName = ""; private ButtonStateChangedListener mListener; - public interface ButtonStateChangedListener - { + public interface ButtonStateChangedListener { void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed); } public TouchscreenControllerButtonView(Context context) { super(context); - init(context,null, 0); + init(context, null, 0); } public TouchscreenControllerButtonView(Context context, AttributeSet attrs) { super(context, attrs); - init(context,attrs, 0); + init(context, attrs, 0); } public TouchscreenControllerButtonView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - init(context,attrs, defStyle); + init(context, attrs, defStyle); } private void init(Context context, AttributeSet attrs, int defStyle) { @@ -80,15 +79,12 @@ public class TouchscreenControllerButtonView extends View { } @Override - public boolean onTouchEvent(MotionEvent event) - { + public boolean onTouchEvent(MotionEvent event) { final boolean oldState = mPressed; - switch (event.getAction()) - { + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - { + case MotionEvent.ACTION_POINTER_DOWN: { mPressed = true; invalidate(); @@ -99,8 +95,7 @@ public class TouchscreenControllerButtonView extends View { } case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - { + case MotionEvent.ACTION_POINTER_UP: { mPressed = false; invalidate(); @@ -114,8 +109,7 @@ public class TouchscreenControllerButtonView extends View { return super.onTouchEvent(event); } - public boolean isPressed() - { + public boolean isPressed() { return mPressed; } @@ -127,13 +121,11 @@ public class TouchscreenControllerButtonView extends View { mButtonName = buttonName; } - public int getButtonCode() - { + public int getButtonCode() { return mButtonCode; } - public void setButtonCode(int code) - { + public void setButtonCode(int code) { mButtonCode = code; } @@ -153,8 +145,7 @@ public class TouchscreenControllerButtonView extends View { mUnpressedDrawable = unpressedDrawable; } - public void setButtonStateChangedListener(ButtonStateChangedListener listener) - { + public void setButtonStateChangedListener(ButtonStateChangedListener listener) { mListener = listener; } } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java index 0c43b379e..1b7f475c4 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java @@ -50,9 +50,8 @@ public class TouchscreenControllerView extends FrameLayout implements Touchscree linkButton(view, R.id.controller_button_r2, "R2"); } - private void linkButton(View view, int id, String buttonName) - { - TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView)view.findViewById(id); + private void linkButton(View view, int id, String buttonName) { + TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); buttonView.setButtonName(buttonName); buttonView.setButtonStateChangedListener(this); diff --git a/android/app/src/main/res/drawable/flag_eu.xml b/android/app/src/main/res/drawable/flag_eu.xml index a3bf3c7f0..e545a72a2 100644 --- a/android/app/src/main/res/drawable/flag_eu.xml +++ b/android/app/src/main/res/drawable/flag_eu.xml @@ -1,36 +1,70 @@ - - - - + + + - - - + + + - + - - - + + + - + android:strokeColor="#00000000" + android:strokeWidth="1"> - - - + + + diff --git a/android/app/src/main/res/drawable/flag_jp.xml b/android/app/src/main/res/drawable/flag_jp.xml index 8ff9d256f..f7b9fe6bd 100644 --- a/android/app/src/main/res/drawable/flag_jp.xml +++ b/android/app/src/main/res/drawable/flag_jp.xml @@ -1,26 +1,49 @@ - - - - + + + - - - + + + - + android:strokeColor="#00000000" + android:strokeWidth="1"> - - - + + + diff --git a/android/app/src/main/res/drawable/flag_us.xml b/android/app/src/main/res/drawable/flag_us.xml index 08c798d33..d83fcb9e2 100644 --- a/android/app/src/main/res/drawable/flag_us.xml +++ b/android/app/src/main/res/drawable/flag_us.xml @@ -1,5 +1,4 @@ - - + - + - + - + - + - + - + @@ -81,32 +104,56 @@ - + - + - + - + - + - + @@ -115,32 +162,56 @@ - + - + - + - + - + - + @@ -149,32 +220,56 @@ - + - + - + - + - + - + @@ -183,32 +278,56 @@ - + - + - + - + - + - + @@ -217,112 +336,200 @@ - - + + - + - + - + - + - - + + - + - + - + - + - - + + - + - + - + - + - - + + - + - + - + - + diff --git a/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml b/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml index 13c137a92..41f4a52ed 100644 --- a/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml +++ b/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml @@ -4,7 +4,7 @@ android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_circle_button.xml b/android/app/src/main/res/drawable/ic_controller_circle_button.xml index c078d372c..d9fa460c6 100644 --- a/android/app/src/main/res/drawable/ic_controller_circle_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_circle_button.xml @@ -3,17 +3,17 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml index a68b83f98..47953d0c1 100644 --- a/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml @@ -3,17 +3,17 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_cross_button.xml b/android/app/src/main/res/drawable/ic_controller_cross_button.xml index c0b10a80a..b133bd616 100644 --- a/android/app/src/main/res/drawable/ic_controller_cross_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_cross_button.xml @@ -3,25 +3,25 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml index 9f1766ec1..b00988473 100644 --- a/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml @@ -3,25 +3,25 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_down_button.xml b/android/app/src/main/res/drawable/ic_controller_down_button.xml index b397fd762..898f2c54f 100644 --- a/android/app/src/main/res/drawable/ic_controller_down_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_down_button.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml index ecc4d7ae7..4e7d5a35e 100644 --- a/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_l1_button.xml b/android/app/src/main/res/drawable/ic_controller_l1_button.xml index 0bc81724b..9f3ab7240 100644 --- a/android/app/src/main/res/drawable/ic_controller_l1_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_l1_button.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml index 8c06f6467..06d625fe5 100644 --- a/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_l2_button.xml b/android/app/src/main/res/drawable/ic_controller_l2_button.xml index 3ca495405..3853d103f 100644 --- a/android/app/src/main/res/drawable/ic_controller_l2_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_l2_button.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml index 05fd394f7..eeaefb18b 100644 --- a/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_left_button.xml b/android/app/src/main/res/drawable/ic_controller_left_button.xml index 0dd3138b1..ccd46c3cc 100644 --- a/android/app/src/main/res/drawable/ic_controller_left_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_left_button.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml index 11907981a..39c1de9e1 100644 --- a/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_r1_button.xml b/android/app/src/main/res/drawable/ic_controller_r1_button.xml index a95517436..3130def38 100644 --- a/android/app/src/main/res/drawable/ic_controller_r1_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_r1_button.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml index 05fe52148..352ddbc6a 100644 --- a/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_r2_button.xml b/android/app/src/main/res/drawable/ic_controller_r2_button.xml index 251b747f9..195fbe85d 100644 --- a/android/app/src/main/res/drawable/ic_controller_r2_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_r2_button.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml index c5fe48ef6..640c36863 100644 --- a/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_right_button.xml b/android/app/src/main/res/drawable/ic_controller_right_button.xml index 586a11b79..8545a61ce 100644 --- a/android/app/src/main/res/drawable/ic_controller_right_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_right_button.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml index fabb570ae..f0cff05ba 100644 --- a/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_select_button.xml b/android/app/src/main/res/drawable/ic_controller_select_button.xml index ec850fc8a..bea56389c 100644 --- a/android/app/src/main/res/drawable/ic_controller_select_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_select_button.xml @@ -3,12 +3,12 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml index 1f5bd9ca2..a34a925d4 100644 --- a/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml @@ -3,12 +3,12 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_square_button.xml b/android/app/src/main/res/drawable/ic_controller_square_button.xml index 9ce49e148..0da658c01 100644 --- a/android/app/src/main/res/drawable/ic_controller_square_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_square_button.xml @@ -3,18 +3,18 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml index 731d2eed9..248a1d400 100644 --- a/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml @@ -3,18 +3,18 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_start_button.xml b/android/app/src/main/res/drawable/ic_controller_start_button.xml index e7e5d8ba7..7247bbcd7 100644 --- a/android/app/src/main/res/drawable/ic_controller_start_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_start_button.xml @@ -3,12 +3,12 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml index 85f176935..5a8255577 100644 --- a/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml @@ -3,12 +3,12 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_triangle_button.xml b/android/app/src/main/res/drawable/ic_controller_triangle_button.xml index 7645a89e7..9a1392988 100644 --- a/android/app/src/main/res/drawable/ic_controller_triangle_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_triangle_button.xml @@ -3,17 +3,17 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml index 0bab1c10a..be10c7a85 100644 --- a/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml @@ -3,17 +3,17 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_up_button.xml b/android/app/src/main/res/drawable/ic_controller_up_button.xml index ad440f992..e8b5a4064 100644 --- a/android/app/src/main/res/drawable/ic_controller_up_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_up_button.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml index 416454f5a..d17013321 100644 --- a/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_emblem_system.xml b/android/app/src/main/res/drawable/ic_emblem_system.xml index 32b3d2397..4be483cfc 100644 --- a/android/app/src/main/res/drawable/ic_emblem_system.xml +++ b/android/app/src/main/res/drawable/ic_emblem_system.xml @@ -4,44 +4,42 @@ android:height="48dp" android:viewportWidth="48" android:viewportHeight="48"> - - - - - - + + + + diff --git a/android/app/src/main/res/drawable/ic_media_cdrom.xml b/android/app/src/main/res/drawable/ic_media_cdrom.xml index 74afde8b9..9269ea16f 100644 --- a/android/app/src/main/res/drawable/ic_media_cdrom.xml +++ b/android/app/src/main/res/drawable/ic_media_cdrom.xml @@ -4,107 +4,94 @@ android:height="48dp" android:viewportWidth="48" android:viewportHeight="48"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 963330c6e..a034d7c7e 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ - - + android:layout_height="match_parent" + android:orientation="horizontal"> + + + + + + - + + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file diff --git a/android/app/src/main/res/menu/menu_emulation.xml b/android/app/src/main/res/menu/menu_emulation.xml index 439e736a5..c57b01b38 100644 --- a/android/app/src/main/res/menu/menu_emulation.xml +++ b/android/app/src/main/res/menu/menu_emulation.xml @@ -3,11 +3,14 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> - - - diff --git a/android/app/src/main/res/menu/menu_game_list_entry.xml b/android/app/src/main/res/menu/menu_game_list_entry.xml index 45bbbb8c6..37ab82ceb 100644 --- a/android/app/src/main/res/menu/menu_game_list_entry.xml +++ b/android/app/src/main/res/menu/menu_game_list_entry.xml @@ -1,22 +1,10 @@ - + android:id="@+id/game_list_entry_menu_start_game" + android:title="Start Game" /> - - - - - - - + android:id="@+id/game_list_entry_menu_resume_game" + android:title="Resume Game" /> \ No newline at end of file diff --git a/android/app/src/main/res/menu/menu_main.xml b/android/app/src/main/res/menu/menu_main.xml index dc942bbfd..9ad26feca 100644 --- a/android/app/src/main/res/menu/menu_main.xml +++ b/android/app/src/main/res/menu/menu_main.xml @@ -1,16 +1,28 @@ + tools:context="com.github.stenzek.duckstation.MainActivity"> + + + + - - - - diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 036d09bc5..c9ad5f98f 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 036d09bc5..c9ad5f98f 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml index 66fbe92dc..c35f0db98 100644 --- a/android/app/src/main/res/values/arrays.xml +++ b/android/app/src/main/res/values/arrays.xml @@ -32,22 +32,22 @@ Software - 1x (1024x512 VRAM) - 2x (2048x1024 VRAM) - 3x (3072x1536 VRAM) - 4x (4096x2048 VRAM) - 5x (5120x2560 VRAM) - 6x (6144x3072 VRAM) - 7x (7168x3584 VRAM) - 8x (8192x4096 VRAM) - 9x (9216x4608 VRAM) - 10x (10240x5120 VRAM) - 11x (11264x5632 VRAM) - 12x (12288x6144 VRAM) - 13x (13312x6656 VRAM) - 14x (14336x7168 VRAM) - 15x (15360x7680 VRAM) - 16x (16384x8192 VRAM) + 1x + 2x + 3x (for 720p) + 4x + 5x (for 1080p) + 6x (for 1440p) + 7x + 8x + 9x (for 4K) + 10x + 11x + 12x + 13x + 14x + 15x + 16x 1 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 4e41aacef..b849520af 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -30,11 +30,11 @@ Show VPS - Execution Mode + CPU Execution Mode Interpreter - Renderer + GPU Renderer Display Linear Filtering Resolution Scale True 24-Bit Color (Disables Dithering) diff --git a/android/app/src/main/res/xml/root_preferences.xml b/android/app/src/main/res/xml/root_preferences.xml index 6589670a5..c4fcb9e17 100644 --- a/android/app/src/main/res/xml/root_preferences.xml +++ b/android/app/src/main/res/xml/root_preferences.xml @@ -17,18 +17,67 @@ - - - - + + + + + + + + + + + + + + + + @@ -41,79 +90,15 @@ app:defaultValue="@string/settings_console_region_default" app:useSimpleSummaryProvider="true" /> - - + app:summary="Skips the BIOS shell/intro, booting directly into the game. Usually safe to enable, but some games break." /> - - - - - - - - - - - - - - - - - - - - + - - - - - - + app:summary="Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per channel. This produces nicer looking gradients at the cost of making some colours look slightly different. Disabling the option also enables dithering, which makes the transition between colours less sharp by applying a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't and will have broken effects with it enabled. Only applies to the hardware renderers." /> + app:key="GPU/ScaledDithering" + app:title="Scaled Dithering (scale dither pattern to resolution)" + app:defaultValue="true" + app:summary="Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. Usually safe to enable, and only supported by the hardware renderers." /> - - + app:key="GPU/DisableInterlacing" + app:title="Disable Interlacing (force progressive render/scan)" + app:defaultValue="true" + app:summary="Forces the rendering and display of frames to progressive mode. This removes the "combing" effect seen in 480i games by rendering them in 480p. Usually safe to enable." /> + + + + + + + app:defaultValue="false" + app:summary="Reduces "wobbly" polygons and "warping" textures that are common in PS1 games. >Only works with the hardware renderers. May not be compatible with all games." /> - + app:defaultValue="true" + app:summary="Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction enabled." /> + - - + app:summary="Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. Requires geometry correction enabled." /> - + - - - - - - - + + - - + app:defaultValue="false" + app:summary="Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games." /> + @@ -224,7 +209,7 @@ app:title="Display Touchscreen Controller" app:defaultValue="true" /> - + + + + + + + + diff --git a/appveyor.yml b/appveyor.yml index e88522645..85045f701 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,27 +6,11 @@ skip_tags: true image: - Visual Studio 2019 -- Ubuntu1804 -- macOS install: - cmd: >- git submodule update --init --depth 1 -- sh: >- - if [ "$APPVEYOR_BUILD_WORKER_IMAGE" == "Ubuntu1804" ]; then - - sudo apt-get update - - sudo apt-get install -y cmake ninja-build ccache libsdl2-dev libgtk2.0-dev qtbase5-dev qtbase5-dev-tools qtbase5-private-dev qt5-default - - elif [ "$APPVEYOR_BUILD_WORKER_IMAGE" == "macOS" ]; then - - brew install qt5 sdl2 - - fi - - build_script: - cmd: >- call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 @@ -44,46 +28,4 @@ build_script: appveyor PushArtifact duckstation-win64-release.7z -- sh: >- - if [ "$APPVEYOR_BUILD_WORKER_IMAGE" == "Ubuntu1804" ]; then - - mkdir -p build-release - - cd build-release - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SDL_FRONTEND=ON -DBUILD_QT_FRONTEND=ON -DUSE_SDL2=ON -G Ninja .. - - ninja - - ../appimage/generate-appimages.sh $(pwd) - - if [ $? -eq 0 ]; then - - mv duckstation-qt-x64.AppImage duckstation-qt-x64-release.AppImage - - mv duckstation-sdl-x64.AppImage duckstation-sdl-x64-release.AppImage - - 7za a -r duckstation-linux-x64-release.7z duckstation-*.AppImage - - appveyor PushArtifact duckstation-linux-x64-release.7z - - else - - echo "Failed to create AppImages, no AppImage artifact will be pushed" - - fi - - elif [ "$APPVEYOR_BUILD_WORKER_IMAGE" == "macOS" ]; then - - mkdir build-release - - cd build-release - - cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SDL_FRONTEND=YES -DBUILD_QT_FRONTEND=YES -DQt5_DIR=/usr/local/opt/qt/lib/cmake/Qt5 .. - - make - - fi - - test: off diff --git a/data/database/compatibility.xml b/data/database/compatibility.xml index 110bba0e5..0f3c059fb 100644 --- a/data/database/compatibility.xml +++ b/data/database/compatibility.xml @@ -215,6 +215,10 @@ No Issues 0.1-1391-g5f9481dd + + No Issues + 0.1-1558-gf852be74 + No Issues 0.1-1323-ga6acd33 @@ -259,6 +263,10 @@ No Issues 0.1-1336-gd711baa + + No Issues + 0.1-1580-g136a9d60 + No Issues 0.1-1304-gc8b6712 @@ -474,6 +482,10 @@ No Issues 0.1-1304-gc8b6712 + + No Issues + 0.1-1558-gf852be74 + No Issues 0.1-986-gfc911de1 @@ -717,10 +729,9 @@ No Issues 0.1-1490-g76978986 - - Crashes In-Game - 0.1-1409-ge198e315 - Graphical errors in ingame menu. If you try to check the status, the game freezes in black screen (Issues #533 and #503). + + No Issues + 0.1-1600-g032127a7 No Issues @@ -734,6 +745,10 @@ No Issues 0.1-1337-gcaf9943 + + No Issues + 0.1-1558-gf852be74 + No Issues 0.1-1400-gb527118c @@ -839,6 +854,11 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1425-g05f0ce6d + + Speed Issues + 0.1-1558-gf852be74 + Speed issues ingame (Issue #695). + No Issues 0.1-1308-g622e50fa @@ -910,6 +930,14 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-908-g9f22684 + + No Issues + 0.1-1558-gf852be74 + + + No Issues + 0.1-1558-gf852be74 + No Issues 0.1-1333-g5a955a4 @@ -922,6 +950,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1333-g5a955a4 + + No Issues + 0.1-1558-gf852be74 + No Issues 0.1-1333-g5a955a4 @@ -955,6 +987,10 @@ Tetris with Card Captor Sakura (Japan) 0.1-908-g9f22684 Broken when upscaling + + No Issues + 0.1-1580-g136a9d60 + No Issues 0.1-1336-gd711baa @@ -1020,6 +1056,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-896-gc8a00c5 + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-896-gc8a00c5 @@ -1054,6 +1094,10 @@ Tetris with Card Captor Sakura (Japan) The playable character gain "yellow horns" (actually, 2 broken polygons in head) if the upscaling is used (Issue 427). 0.1-1425-g05f0ce6d + + No Issues + 0.1-1608-g79aaf908 + No Issues If use fast forward some controllers buttons may not function properly. @@ -1063,11 +1107,27 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1336-gd711baa + + No Issues + 0.1-1608-g79aaf908 + + + No Issues + 0.1-1608-g79aaf908 + + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-986-gfc911de1 Rendering is broken with any upscaling. + + No Issues + 0.1-1580-g136a9d60 + No Issues 0.1-1308-g622e50fa @@ -1140,8 +1200,21 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-986-gfc911de1 - - Crashes In Intro + + No Issues + 0.1-1608-g79aaf908 + + + Graphical/Audio Issues + 0.1-1580-g136a9d60 + Some graphics issues in random races + + + Crashes In-Game + + + No Issues + 0.1-1614-g914f3ad4 Blackscreen after the first Loading screen (Issue #54). @@ -1167,10 +1240,18 @@ Tetris with Card Captor Sakura (Japan) No Issues + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-1425-g05f0ce6d + + Doesn't Boot + 0.1-1580-g136a9d60 + No Issues @@ -1194,6 +1275,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1308-g622e50fa + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-1304-gc8b6712 @@ -1250,6 +1335,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-986-gfc911de1 + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-1308-g622e50fa @@ -1262,6 +1351,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1425-g05f0ce6d + + No Issues + 0.1-1608-g79aaf908 + No Issues @@ -1322,6 +1415,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-887-g1eecd50 + + No Issues + 0.1-1580-g136a9d60 + No Issues 0.1-986-gfc911de1 @@ -1685,6 +1782,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1308-g622e50fa + + No Issues + 0.1-1614-g914f3ad4 + No Issues 0.1-1308-g622e50fa @@ -1859,6 +1960,10 @@ Tetris with Card Captor Sakura (Japan) No Issues + + No Issues + 0.1-1614-g914f3ad4 + No Issues Issue 419 @@ -1918,6 +2023,14 @@ Tetris with Card Captor Sakura (Japan) 0.1-1443-g7ab521f7 After the initial FMV, the game crashes (Issue #717). + + No Issues + 0.1-1608-g79aaf908 + + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-884-g096ed21 @@ -2075,6 +2188,24 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1530-g6d75f42e + + Graphical/Audio Issues + 0.1-1539-gf704cc64 + In Battle Arena Toshinden demo, game runs at a strange fast speed than it should be (Issue #695). + + + No Issues + 0.1-1539-gf704cc64 + Can hang if speed up in Star Wars Rebel Assaut 2 demo. + + + No Issues + 0.1-1539-gf704cc64 + + + No Issues + 0.1-1539-gf704cc64 + No Issues 0.1-884-g096ed21 @@ -2104,6 +2235,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-774-g5a1b008 + + No Issues + 0.1-1539-gf704cc64 + No Issues 0.1-1529-ga895c027 @@ -2306,6 +2441,10 @@ Tetris with Card Captor Sakura (Japan) 0.1-1334-g10f2366 The menu music is messing completely (Issue #662). + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-1333-g5a955a4 @@ -2834,6 +2973,11 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1265-gdd9a419 + + Graphical/Audio Issues + 0.1-1558-gf852be74 + Game needs forced 60hz timing to run at correct speed, but rendered cutscenes desync as a consequence. + No Issues 0.1-986-gfc911de1 @@ -2850,6 +2994,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-986-gfc911de1 + + No Issues + 0.1-1580-g136a9d60 + No Issues 0.1-986-gfc911de1 @@ -3054,6 +3202,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1333-g5a955a4 + + No Issues + 0.1-1608-g79aaf908 + No Issues @@ -3062,6 +3214,10 @@ Tetris with Card Captor Sakura (Japan) Small grafics errors when the cursor is over the text (don't know if this bug occurs in the real hardware). 0.1-1304-gc8b6712 + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-986-gfc911de1 @@ -3074,6 +3230,10 @@ Tetris with Card Captor Sakura (Japan) No Issues Sprite glitches + + No Issues + 0.1-1608-g79aaf908 + No Issues 0.1-1448-g472f1c1c diff --git a/data/database/gamesettings.ini b/data/database/gamesettings.ini index 22b2b8ac1..33fc1f299 100644 --- a/data/database/gamesettings.ini +++ b/data/database/gamesettings.ini @@ -3,69 +3,69 @@ # Croc - Legend of the Gobbos (USA) (SLUS-00530) [SLUS-00530] -EnablePGXPCPUMode = true +ForcePGXPCPUMode = true # Croc 2 (USA) (SLUS-00634) [SLUS-00634] -EnablePGXPCPUMode = true +ForcePGXPCPUMode = true # Doom (USA) (Rev 1) (SLUS-00077) [SLUS-00077] DisableUpscaling = true - +ForceDigitalController = true # Pop'n Music 6 (Japan) (SLPM-87089) [SLPM-87089] -EnableInterlacing = true +ForceInterlacing = true # Mr. Driller G (Japan) (SLPS-03336) [SLPS-03336] -EnableInterlacing = true +ForceInterlacing = true # Pro Pinball - Big Race USA (USA) (SLUS-01260) [SLUS-01260] ForceSoftwareRenderer = true -EnableInterlacing = true +ForceInterlacing = true # Pro Pinball - Fantastic Journey (USA) (SLUS-01261) [SLUS-01261] ForceSoftwareRenderer = true -EnableInterlacing = true +ForceInterlacing = true # True Pinball (USA) (SLUS-00337) [SLUS-00337] -EnableInterlacing = true +ForceInterlacing = true # Dead or Alive (USA) (SLUS-00606) [SLUS-00606] -EnableInterlacing = true +ForceInterlacing = true # Shinobi no Sato no Jintori Gassen (Japan) (SLPS-03553) [SLPS-03553] -EnableInterlacing = true +ForceInterlacing = true # Time Bokan Series: Bokan desu yo (SLPS-01211) [SLPS-01211] -EnableInterlacing = true +ForceInterlacing = true # Rat Attack! (USA) (SLUS-00656) [SLUS-00656] -EnableInterlacing = true +ForceInterlacing = true # Arcade Party Pak (USA) (SLUS-00952) [SLUS-00952] -EnableInterlacing = true +ForceInterlacing = true # SLUS-01222 (Colin McRae Rally 2.0 (USA) (En,Fr,Es)) @@ -73,3 +73,125 @@ EnableInterlacing = true DisplayActiveStartOffset = 64 DisplayActiveEndOffset = 68 +# SLUS-00297 (Star Wars - Dark Forces (USA)) +[SLUS-00297] +DisableUpscaling = true +DisablePGXP = true +ForceDigitalController = true + + +# SCUS-94302 (Destruction Derby (USA)) +[SCUS-94302] +ForceDigitalController = true + + +# SCUS-94900 (Crash Bandicoot (USA)) +[SCUS-94900] +ForceDigitalController = true + + +# SCUS-94350 (Destruction Derby 2 (USA)) +[SCUS-94350] +ForceDigitalController = true + + +# PCPX-96085 (Gran Turismo (Japan) (Demo 1)) +[PCPX-96085] +ForceDigitalController = true + + +# SLUS-00106 (Grand Theft Auto (USA)) +[SLUS-00106] +ForceDigitalController = true + + +# SLUS-00590 (Need for Speed - V-Rally (USA)) +[SLUS-00590] +ForceDigitalController = true + + +# SLUS-00403 (Rage Racer (USA)) +[SLUS-00403] +ForceDigitalController = true + + +# SCUS-94300 (Ridge Racer (USA)) +[SCUS-94300] +ForceDigitalController = true + + +# SLUS-00214 (Ridge Racer Revolution (USA)) +[SLUS-00214] +ForceDigitalController = true + + +# SLUS-00204 (Road & Track Presents - The Need for Speed (USA)) +[SLUS-00204] +ForceDigitalController = true + + +# SLUS-00006 (Tekken (USA)) +[SLUS-00006] +ForceDigitalController = true + + +# SLUS-00213 (Tekken 2 (USA)) +[SLUS-00213] +ForceDigitalController = true + + +# SCES-00344 (Crash Bandicoot (Europe)) +[SCES-00344] +ForceDigitalController = true + + +# SLUS-00355 (Duke Nukem - Total Meltdown (USA)) +[SLUS-00355] +DisableUpscaling = true +ForceDigitalController = true + + +# SLUS-00331 (Final Doom (USA)) +[SLUS-00331] +DisableUpscaling = true +ForceDigitalController = true + + +# SLUS-00106 (Grand Theft Auto (USA)) +[SLUS-00106] +ForceDigitalController = true + + +# SLUS-00005 (Rayman (USA)) +[SLUS-00005] +ForceDigitalController = true + + +# SLUS-01265 (Rayman Brain Games (USA)) +[SLUS-01265] +ForceDigitalController = true + + +# SLUS-00601 (Skullmonkeys (USA)) +[SLUS-00601] +ForceDigitalController = true + + +# SLPS-00435 (Megatudo 2096 (Japan)) +[SLPS-00435] +ForceRecompilerICache = true + + +# SLUS-00388 (NBA Jam Extreme (USA)) +[SLUS-00388] +ForceRecompilerICache = true + + +# SCES-02834 (Crash Bash (Europe) (En,Fr,De,Es,It)) +[SCES-02834] +ForceRecompilerICache = true + + +# SLUS-00870 (Formula One 99 (USA) (En,Fr,Es)) +[SLUS-00870] +ForceInterpreter = true diff --git a/dep/CMakeLists.txt b/dep/CMakeLists.txt index 13581d697..5b00dbbfa 100644 --- a/dep/CMakeLists.txt +++ b/dep/CMakeLists.txt @@ -1,11 +1,8 @@ add_subdirectory(cubeb) add_subdirectory(glad) add_subdirectory(googletest) -add_subdirectory(imgui) add_subdirectory(libcue) -add_subdirectory(simpleini) add_subdirectory(stb) -add_subdirectory(tinyxml2) add_subdirectory(zlib) add_subdirectory(minizip) add_subdirectory(lzma) @@ -13,10 +10,15 @@ add_subdirectory(libFLAC) add_subdirectory(libchdr) add_subdirectory(xxhash) add_subdirectory(rapidjson) - add_subdirectory(glslang) add_subdirectory(vulkan-loader) +if(NOT BUILD_LIBRETRO_CORE) + add_subdirectory(imgui) + add_subdirectory(simpleini) + add_subdirectory(tinyxml2) +endif() + if(ENABLE_DISCORD_PRESENCE) add_subdirectory(discord-rpc) endif() diff --git a/dep/imgui/CMakeLists.txt b/dep/imgui/CMakeLists.txt index 8f5b999f2..d87184ad5 100644 --- a/dep/imgui/CMakeLists.txt +++ b/dep/imgui/CMakeLists.txt @@ -18,20 +18,3 @@ target_include_directories(imgui PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" " target_include_directories(imgui INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_compile_definitions(imgui PRIVATE "imgui_STATIC") -target_sources(imgui PRIVATE - include/imgui_impl_opengl3.h - src/imgui_impl_opengl3.cpp -) -target_link_libraries(imgui PRIVATE glad) - -target_sources(imgui PRIVATE - include/imgui_impl_vulkan.h - src/imgui_impl_vulkan.cpp -) -target_link_libraries(imgui PRIVATE vulkan-loader) - -if(WIN32) - target_sources(imgui PRIVATE include/imgui_impl_dx11.h src/imgui_impl_dx11.cpp) -endif() - - diff --git a/dep/imgui/imgui.vcxproj b/dep/imgui/imgui.vcxproj index 245fbbb3d..b88cad554 100644 --- a/dep/imgui/imgui.vcxproj +++ b/dep/imgui/imgui.vcxproj @@ -37,9 +37,6 @@ - - - @@ -49,20 +46,9 @@ - - - - - - {43540154-9e1e-409c-834f-b84be5621388} - - - {9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035} - - {BB08260F-6FBC-46AF-8924-090EE71360C6} Win32Proj @@ -211,7 +197,7 @@ Disabled imgui_STATIC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) ProgramDatabase - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) + $(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) true stdcpp17 true @@ -231,7 +217,7 @@ Disabled imgui_STATIC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) ProgramDatabase - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) + $(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) true stdcpp17 true @@ -251,7 +237,7 @@ Disabled imgui_STATIC;WIN32;_ITERATOR_DEBUG_LEVEL=1;_DEBUG;_LIB;%(PreprocessorDefinitions) ProgramDatabase - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) + $(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) true stdcpp17 false @@ -273,7 +259,7 @@ Disabled imgui_STATIC;WIN32;_ITERATOR_DEBUG_LEVEL=1;_DEBUG;_LIB;%(PreprocessorDefinitions) ProgramDatabase - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) + $(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) true stdcpp17 false @@ -295,7 +281,7 @@ MaxSpeed true imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) + $(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) true /Zo /utf-8 %(AdditionalOptions) false @@ -318,7 +304,7 @@ MaxSpeed true imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) + $(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) true /Zo /utf-8 %(AdditionalOptions) true @@ -342,7 +328,7 @@ MaxSpeed true imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) + $(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) true /Zo /utf-8 %(AdditionalOptions) false @@ -365,7 +351,7 @@ MaxSpeed true imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) + $(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories) true /Zo /utf-8 %(AdditionalOptions) true diff --git a/dep/imgui/imgui.vcxproj.filters b/dep/imgui/imgui.vcxproj.filters index 1d6e671ff..0257cb6dd 100644 --- a/dep/imgui/imgui.vcxproj.filters +++ b/dep/imgui/imgui.vcxproj.filters @@ -1,8 +1,6 @@  - - @@ -10,15 +8,11 @@ - - - - \ No newline at end of file diff --git a/dep/vulkan-loader/src/vulkan_loader.cpp b/dep/vulkan-loader/src/vulkan_loader.cpp index c1be6ae57..0d580c219 100644 --- a/dep/vulkan-loader/src/vulkan_loader.cpp +++ b/dep/vulkan-loader/src/vulkan_loader.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "vulkan_loader.h" @@ -14,6 +16,10 @@ #include #endif +#ifdef __APPLE__ +#include +#endif + #define VULKAN_MODULE_ENTRY_POINT(name, required) PFN_##name name; #define VULKAN_INSTANCE_ENTRY_POINT(name, required) PFN_##name name; #define VULKAN_DEVICE_ENTRY_POINT(name, required) PFN_##name name; @@ -111,6 +117,25 @@ bool LoadVulkanLibrary() char* libvulkan_env = getenv("LIBVULKAN_PATH"); if (libvulkan_env) vulkan_module = dlopen(libvulkan_env, RTLD_NOW); + if (!vulkan_module) + { + unsigned path_size = 0; + _NSGetExecutablePath(nullptr, &path_size); + std::string path; + path.resize(path_size); + if (_NSGetExecutablePath(path.data(), &path_size) == 0) + { + path[path_size] = 0; + + size_t pos = path.rfind('/'); + if (pos != std::string::npos) + { + path.erase(pos); + path += "/../Frameworks/libvulkan.dylib"; + vulkan_module = dlopen(path.c_str(), RTLD_NOW); + } + } + } if (!vulkan_module) vulkan_module = dlopen("libvulkan.dylib", RTLD_NOW); #else diff --git a/src/common/d3d11/staging_texture.cpp b/src/common/d3d11/staging_texture.cpp index 148251308..a90a3f365 100644 --- a/src/common/d3d11/staging_texture.cpp +++ b/src/common/d3d11/staging_texture.cpp @@ -84,8 +84,8 @@ bool AutoStagingTexture::EnsureSize(ID3D11DeviceContext* context, u32 width, u32 if (m_texture && m_width >= width && m_height >= height && m_format == format) return true; - ID3D11Device* device; - context->GetDevice(&device); + ComPtr device; + context->GetDevice(device.GetAddressOf()); CD3D11_TEXTURE2D_DESC new_desc(format, width, height, 1, 1, 0, for_uploading ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_STAGING, diff --git a/src/common/types.h b/src/common/types.h index 6b94fd87f..839b585e7 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -17,7 +17,7 @@ // Force inline in non-debug helper #ifdef _DEBUG -#define ALWAYS_INLINE_RELEASE +#define ALWAYS_INLINE_RELEASE inline #else #define ALWAYS_INLINE_RELEASE ALWAYS_INLINE #endif diff --git a/src/common/vulkan/staging_texture.cpp b/src/common/vulkan/staging_texture.cpp index 0403a1164..e9b62d598 100644 --- a/src/common/vulkan/staging_texture.cpp +++ b/src/common/vulkan/staging_texture.cpp @@ -209,10 +209,8 @@ void StagingTexture::Flush() void StagingTexture::ReadTexels(u32 src_x, u32 src_y, u32 width, u32 height, void* out_ptr, u32 out_stride) { Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Upload); - if (!PrepareForAccess()) - return; - Assert((src_x + width) <= m_width && (src_y + height) <= m_height); + PrepareForAccess(); // Offset pointer to point to start of region being copied out. const char* current_ptr = m_staging_buffer.GetMapPointer(); @@ -239,10 +237,9 @@ void StagingTexture::ReadTexels(u32 src_x, u32 src_y, u32 width, u32 height, voi void StagingTexture::ReadTexel(u32 x, u32 y, void* out_ptr) { Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Upload); - if (!PrepareForAccess()) - return; - Assert(x < m_width && y < m_height); + PrepareForAccess(); + const char* src_ptr = GetMappedPointer() + y * GetMappedStride() + x * m_texel_size; std::memcpy(out_ptr, src_ptr, m_texel_size); } @@ -250,10 +247,8 @@ void StagingTexture::ReadTexel(u32 x, u32 y, void* out_ptr) void StagingTexture::WriteTexels(u32 dst_x, u32 dst_y, u32 width, u32 height, const void* in_ptr, u32 in_stride) { Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Readback); - if (!PrepareForAccess()) - return; - Assert((dst_x + width) <= m_width && (dst_y + height) <= m_height); + PrepareForAccess(); // Offset pointer to point to start of region being copied to. char* current_ptr = GetMappedPointer(); @@ -279,23 +274,18 @@ void StagingTexture::WriteTexels(u32 dst_x, u32 dst_y, u32 width, u32 height, co void StagingTexture::WriteTexel(u32 x, u32 y, const void* in_ptr) { - if (!PrepareForAccess()) - return; - Assert(x < m_width && y < m_height); + PrepareForAccess(); + char* dest_ptr = GetMappedPointer() + y * m_map_stride + x * m_texel_size; std::memcpy(dest_ptr, in_ptr, m_texel_size); } -bool StagingTexture::PrepareForAccess() +void StagingTexture::PrepareForAccess() { + Assert(IsMapped()); if (m_needs_flush) - { - if (IsMapped()) - Unmap(); Flush(); - } - return IsMapped() || Map(); } } // namespace Vulkan \ No newline at end of file diff --git a/src/common/vulkan/staging_texture.h b/src/common/vulkan/staging_texture.h index 7f03e04dd..f8d24836f 100644 --- a/src/common/vulkan/staging_texture.h +++ b/src/common/vulkan/staging_texture.h @@ -73,7 +73,7 @@ public: void WriteTexel(u32 x, u32 y, const void* in_ptr); private: - bool PrepareForAccess(); + void PrepareForAccess(); StagingBuffer m_staging_buffer; u64 m_flush_fence_counter = 0; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4d2cbad5f..c8e2f074a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -24,10 +24,6 @@ add_library(core digital_controller.h dma.cpp dma.h - game_list.cpp - game_list.h - game_settings.cpp - game_settings.h gpu.cpp gpu.h gpu_commands.cpp @@ -98,7 +94,7 @@ set(RECOMPILER_SRCS target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") -target_link_libraries(core PUBLIC Threads::Threads common imgui tinyxml2 zlib vulkan-loader simpleini) +target_link_libraries(core PUBLIC Threads::Threads common zlib vulkan-loader) target_link_libraries(core PRIVATE glad stb) if(WIN32) @@ -126,3 +122,8 @@ elseif(${CPU_ARCH} STREQUAL "aarch64") else() message("Not building recompiler") endif() + +if(NOT BUILD_LIBRETRO_CORE) + target_link_libraries(core PRIVATE imgui) + target_compile_definitions(core PRIVATE "WITH_IMGUI=1") +endif() diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index b745386e3..686fe0bad 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -466,25 +466,31 @@ std::optional AnalogController::StaticGetButtonCodeByName(std::string_view Controller::AxisList AnalogController::StaticGetAxisNames() { -#define A(n) \ - { \ -#n, static_cast < s32>(Axis::n) \ - } - - return {A(LeftX), A(LeftY), A(RightX), A(RightY)}; - -#undef A + return {{TRANSLATABLE("AnalogController", "LeftX"), static_cast(Axis::LeftX), AxisType::Full}, + {TRANSLATABLE("AnalogController", "LeftY"), static_cast(Axis::LeftY), AxisType::Full}, + {TRANSLATABLE("AnalogController", "RightX"), static_cast(Axis::RightX), AxisType::Full}, + {TRANSLATABLE("AnalogController", "RightY"), static_cast(Axis::RightY), AxisType::Full}}; } Controller::ButtonList AnalogController::StaticGetButtonNames() { -#define B(n) \ - { \ -#n, static_cast < s32>(Button::n) \ - } - return {B(Up), B(Down), B(Left), B(Right), B(Select), B(Start), B(Triangle), B(Cross), B(Circle), - B(Square), B(L1), B(L2), B(R1), B(R2), B(L3), B(R3), B(Analog)}; -#undef B + return {{TRANSLATABLE("AnalogController", "Up"), static_cast(Button::Up)}, + {TRANSLATABLE("AnalogController", "Down"), static_cast(Button::Down)}, + {TRANSLATABLE("AnalogController", "Left"), static_cast(Button::Left)}, + {TRANSLATABLE("AnalogController", "Right"), static_cast(Button::Right)}, + {TRANSLATABLE("AnalogController", "Select"), static_cast(Button::Select)}, + {TRANSLATABLE("AnalogController", "Start"), static_cast(Button::Start)}, + {TRANSLATABLE("AnalogController", "Triangle"), static_cast(Button::Triangle)}, + {TRANSLATABLE("AnalogController", "Cross"), static_cast(Button::Cross)}, + {TRANSLATABLE("AnalogController", "Circle"), static_cast(Button::Circle)}, + {TRANSLATABLE("AnalogController", "Square"), static_cast(Button::Square)}, + {TRANSLATABLE("AnalogController", "L1"), static_cast(Button::L1)}, + {TRANSLATABLE("AnalogController", "L2"), static_cast(Button::L2)}, + {TRANSLATABLE("AnalogController", "R1"), static_cast(Button::R1)}, + {TRANSLATABLE("AnalogController", "R2"), static_cast(Button::R2)}, + {TRANSLATABLE("AnalogController", "L3"), static_cast(Button::L3)}, + {TRANSLATABLE("AnalogController", "R3"), static_cast(Button::R3)}, + {TRANSLATABLE("AnalogController", "Analog"), static_cast(Button::Analog)}}; } u32 AnalogController::StaticGetVibrationMotorCount() @@ -495,11 +501,14 @@ u32 AnalogController::StaticGetVibrationMotorCount() Controller::SettingList AnalogController::StaticGetSettings() { static constexpr std::array settings = { - {{SettingInfo::Type::Boolean, "AutoEnableAnalog", "Enable Analog Mode on Reset", - "Automatically enables analog mode when the console is reset/powered on.", "false"}, - {SettingInfo::Type::Float, "AxisScale", "Analog Axis Scale", - "Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent " - "controllers, e.g. DualShock 4, Xbox One Controller.", + {{SettingInfo::Type::Boolean, "AutoEnableAnalog", TRANSLATABLE("AnalogController", "Enable Analog Mode on Reset"), + TRANSLATABLE("AnalogController", "Automatically enables analog mode when the console is reset/powered on."), + "false"}, + {SettingInfo::Type::Float, "AxisScale", TRANSLATABLE("AnalogController", "Analog Axis Scale"), + TRANSLATABLE( + "AnalogController", + "Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent " + "controllers, e.g. DualShock 4, Xbox One Controller."), "1.00f", "0.01f", "1.50f", "0.01f"}}}; return SettingList(settings.begin(), settings.end()); diff --git a/src/core/bus.cpp b/src/core/bus.cpp index e6fa98296..7b8a70c0a 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -742,10 +742,153 @@ ALWAYS_INLINE static TickCount DoDMAAccess(u32 offset, u32& value) namespace CPU { +template +ALWAYS_INLINE_RELEASE void DoInstructionRead(PhysicalMemoryAddress address, void* data) +{ + using namespace Bus; + + address &= PHYSICAL_MEMORY_ADDRESS_MASK; + + if (address < RAM_MIRROR_END) + { + std::memcpy(data, &g_ram[address & RAM_MASK], sizeof(u32) * word_count); + if constexpr (add_ticks) + g_state.pending_ticks += (icache_read ? 1 : 4) * word_count; + } + else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE)) + { + std::memcpy(data, &g_bios[(address - BIOS_BASE) & BIOS_MASK], sizeof(u32)); + if constexpr (add_ticks) + g_state.pending_ticks += m_bios_access_time[static_cast(MemoryAccessSize::Word)] * word_count; + } + else + { + CPU::RaiseException(address, Cop0Registers::CAUSE::MakeValueForException(Exception::IBE, false, false, 0)); + std::memset(data, 0, sizeof(u32) * word_count); + } +} + +TickCount GetInstructionReadTicks(VirtualMemoryAddress address) +{ + using namespace Bus; + + address &= PHYSICAL_MEMORY_ADDRESS_MASK; + + if (address < RAM_MIRROR_END) + { + return 4; + } + else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE)) + { + return m_bios_access_time[static_cast(MemoryAccessSize::Word)]; + } + else + { + return 0; + } +} + +TickCount GetICacheFillTicks(VirtualMemoryAddress address) +{ + using namespace Bus; + + address &= PHYSICAL_MEMORY_ADDRESS_MASK; + + if (address < RAM_MIRROR_END) + { + return 1 * (ICACHE_LINE_SIZE / sizeof(u32)); + } + else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE)) + { + return m_bios_access_time[static_cast(MemoryAccessSize::Word)] * (ICACHE_LINE_SIZE / sizeof(u32)); + } + else + { + return 0; + } +} + +void CheckAndUpdateICacheTags(u32 line_count, TickCount uncached_ticks) +{ + VirtualMemoryAddress current_pc = g_state.regs.pc & ICACHE_TAG_ADDRESS_MASK; + if (IsCachedAddress(current_pc)) + { + TickCount ticks = 0; + TickCount cached_ticks_per_line = GetICacheFillTicks(current_pc); + for (u32 i = 0; i < line_count; i++, current_pc += ICACHE_LINE_SIZE) + { + const u32 line = GetICacheLine(current_pc); + if (g_state.icache_tags[line] != current_pc) + { + g_state.icache_tags[line] = current_pc; + ticks += cached_ticks_per_line; + } + } + + g_state.pending_ticks += ticks; + } + else + { + g_state.pending_ticks += uncached_ticks; + } +} + +u32 FillICache(VirtualMemoryAddress address) +{ + const u32 line = GetICacheLine(address); + g_state.icache_tags[line] = GetICacheTagForAddress(address); + u8* line_data = &g_state.icache_data[line * ICACHE_LINE_SIZE]; + DoInstructionRead(address & ~(ICACHE_LINE_SIZE - 1u), line_data); + + const u32 offset = GetICacheLineOffset(address); + u32 result; + std::memcpy(&result, &line_data[offset], sizeof(result)); + return result; +} + +void ClearICache() +{ + std::memset(g_state.icache_data.data(), 0, ICACHE_SIZE); + g_state.icache_tags.fill(ICACHE_INVALD_BIT | ICACHE_DISABLED_BIT); +} + +ALWAYS_INLINE_RELEASE static u32 ReadICache(VirtualMemoryAddress address) +{ + const u32 line = GetICacheLine(address); + const u8* line_data = &g_state.icache_data[line * ICACHE_LINE_SIZE]; + const u32 offset = GetICacheLineOffset(address); + u32 result; + std::memcpy(&result, &line_data[offset], sizeof(result)); + return result; +} + +ALWAYS_INLINE_RELEASE static void WriteICache(VirtualMemoryAddress address, u32 value) +{ + const u32 line = GetICacheLine(address); + const u32 offset = GetICacheLineOffset(address); + g_state.icache_tags[line] = GetICacheTagForAddress(address) | ICACHE_INVALD_BIT; + std::memcpy(&g_state.icache_data[line * ICACHE_LINE_SIZE + offset], &value, sizeof(value)); +} + static void WriteCacheControl(u32 value) { Log_WarningPrintf("Cache control <- 0x%08X", value); - g_state.cache_control = value; + + CacheControl changed_bits{g_state.cache_control.bits ^ value}; + g_state.cache_control.bits = value; + if (changed_bits.icache_enable) + { + if (g_state.cache_control.icache_enable) + { + for (u32 i = 0; i < ICACHE_LINES; i++) + g_state.icache_tags[i] &= ~ICACHE_DISABLED_BIT; + } + else + { + for (u32 i = 0; i < ICACHE_LINES; i++) + g_state.icache_tags[i] |= ICACHE_DISABLED_BIT; + } + } } template @@ -797,7 +940,10 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32& if constexpr (type == MemoryAccessType::Write) { if (g_state.cop0_regs.sr.Isc) + { + WriteICache(address, value); return 0; + } } address &= PHYSICAL_MEMORY_ADDRESS_MASK; @@ -829,7 +975,7 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32& if (address == 0xFFFE0130) { if constexpr (type == MemoryAccessType::Read) - value = g_state.cache_control; + value = g_state.cache_control.bits; else WriteCacheControl(value); @@ -849,6 +995,10 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32& { return DoRAMAccess(address, value); } + else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE)) + { + return DoBIOSAccess(static_cast(address - BIOS_BASE), value); + } else if (address < EXP1_BASE) { return DoInvalidAccess(type, size, address, value); @@ -921,14 +1071,6 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32& { return DoEXP2Access(address & EXP2_MASK, value); } - else if (address < BIOS_BASE) - { - return DoInvalidAccess(type, size, address, value); - } - else if (address < (BIOS_BASE + BIOS_SIZE)) - { - return DoBIOSAccess(static_cast(address - BIOS_BASE), value); - } else { return DoInvalidAccess(type, size, address, value); @@ -961,12 +1103,45 @@ static bool DoAlignmentCheck(VirtualMemoryAddress address) bool FetchInstruction() { DebugAssert(Common::IsAlignedPow2(g_state.regs.npc, 4)); - if (DoMemoryAccess(g_state.regs.npc, g_state.next_instruction.bits) < - 0) + + using namespace Bus; + + PhysicalMemoryAddress address = g_state.regs.npc; + switch (address >> 29) { - // Bus errors don't set BadVaddr. - RaiseException(g_state.regs.npc, Cop0Registers::CAUSE::MakeValueForException(Exception::IBE, false, false, 0)); - return false; + case 0x00: // KUSEG 0M-512M + case 0x04: // KSEG0 - physical memory cached + { +#if 0 + // TODO: icache + TickCount cycles; + DoInstructionRead(address, cycles, g_state.next_instruction.bits); +#else + if (CompareICacheTag(address)) + g_state.next_instruction.bits = ReadICache(address); + else + g_state.next_instruction.bits = FillICache(address); + +#endif + } + break; + + case 0x05: // KSEG1 - physical memory uncached + { + DoInstructionRead(address, &g_state.next_instruction.bits); + } + break; + + case 0x01: // KUSEG 512M-1024M + case 0x02: // KUSEG 1024M-1536M + case 0x03: // KUSEG 1536M-2048M + case 0x06: // KSEG2 + case 0x07: // KSEG2 + default: + { + CPU::RaiseException(address, Cop0Registers::CAUSE::MakeValueForException(Exception::IBE, false, false, 0)); + return false; + } } g_state.regs.pc = g_state.regs.npc; @@ -974,6 +1149,30 @@ bool FetchInstruction() return true; } +bool SafeReadInstruction(VirtualMemoryAddress addr, u32* value) +{ + switch (addr >> 29) + { + case 0x00: // KUSEG 0M-512M + case 0x04: // KSEG0 - physical memory cached + case 0x05: // KSEG1 - physical memory uncached + { + DoInstructionRead(addr, value); + return true; + } + + case 0x01: // KUSEG 512M-1024M + case 0x02: // KUSEG 1024M-1536M + case 0x03: // KUSEG 1536M-2048M + case 0x06: // KSEG2 + case 0x07: // KSEG2 + default: + { + return false; + } + } +} + bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value) { u32 temp = 0; diff --git a/src/core/bus.h b/src/core/bus.h index a18ba1274..10c44f90e 100644 --- a/src/core/bus.h +++ b/src/core/bus.h @@ -78,41 +78,6 @@ extern std::bitset m_ram_code_bits; extern u8 g_ram[RAM_SIZE]; // 2MB RAM extern u8 g_bios[BIOS_SIZE]; // 512K BIOS ROM -/// Returns the address which should be used for code caching (i.e. removes mirrors). -ALWAYS_INLINE PhysicalMemoryAddress UnmirrorAddress(PhysicalMemoryAddress address) -{ - // RAM - if (address < 0x800000) - return address & UINT32_C(0x1FFFFF); - else - return address; -} - -/// Returns true if the address specified is cacheable (RAM or BIOS). -ALWAYS_INLINE bool IsCacheableAddress(PhysicalMemoryAddress address) -{ - return (address < RAM_MIRROR_END) || (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE)); -} - -/// Reads a cachable address (RAM or BIOS). -ALWAYS_INLINE u32 ReadCacheableAddress(PhysicalMemoryAddress address) -{ - u32 value; - if (address < RAM_MIRROR_END) - { - std::memcpy(&value, &g_ram[address & RAM_MASK], sizeof(value)); - return value; - } - else - { - std::memcpy(&value, &g_bios[address & BIOS_MASK], sizeof(value)); - return value; - } -} - -/// Returns true if the address specified is writable (RAM). -ALWAYS_INLINE bool IsRAMAddress(PhysicalMemoryAddress address) { return address < RAM_MIRROR_END; } - /// Flags a RAM region as code, so we know when to invalidate blocks. ALWAYS_INLINE void SetRAMCodePage(u32 index) { m_ram_code_bits[index] = true; } diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 42bb87b9a..033d5d9b5 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -3,12 +3,13 @@ #include "common/log.h" #include "common/state_wrapper.h" #include "dma.h" -#include "game_list.h" -#include "imgui.h" #include "interrupt_controller.h" #include "settings.h" #include "spu.h" #include "system.h" +#ifdef WITH_IMGUI +#include "imgui.h" +#endif Log_SetChannel(CDROM); struct CommandInfo @@ -444,7 +445,7 @@ void CDROM::InsertMedia(std::unique_ptr media) RemoveMedia(); // set the region from the system area of the disc - m_disc_region = GameList::GetRegionForImage(media.get()); + m_disc_region = System::GetRegionForImage(media.get()); Log_InfoPrintf("Inserting new media, disc region: %s, console region: %s", Settings::GetDiscRegionName(m_disc_region), Settings::GetConsoleRegionName(System::GetRegion())); @@ -1596,6 +1597,7 @@ void CDROM::BeginPlaying(u8 track_bcd, TickCount ticks_late /* = 0 */, bool afte m_secondary_status.ClearActiveBits(); m_secondary_status.motor_on = true; + m_secondary_status.playing_cdda = true; ClearSectorBuffers(); ResetAudioDecoder(); @@ -2241,42 +2243,38 @@ void CDROM::ProcessCDDASector(const u8* raw_sector, const CDImage::SubChannelQ& // For CDDA sectors, the whole sector contains the audio data. Log_DevPrintf("Read sector %u as CDDA", m_current_lba); - // These bits/reporting doesn't happen if we're reading with the CDDA mode bit set. - if (m_drive_state == DriveState::Playing) + // The reporting doesn't happen if we're reading with the CDDA mode bit set. + if (m_drive_state == DriveState::Playing && m_mode.report_audio) { - m_secondary_status.playing_cdda = true; - if (m_mode.report_audio) + const u8 frame_nibble = subq.absolute_frame_bcd >> 4; + if (m_last_cdda_report_frame_nibble != frame_nibble) { - const u8 frame_nibble = subq.absolute_frame_bcd >> 4; - if (m_last_cdda_report_frame_nibble != frame_nibble) + m_last_cdda_report_frame_nibble = frame_nibble; + + Log_DebugPrintf("CDDA report at track[%02x] index[%02x] rel[%02x:%02x:%02x]", subq.track_number_bcd, + subq.index_number_bcd, subq.relative_minute_bcd, subq.relative_second_bcd, + subq.relative_frame_bcd); + + ClearAsyncInterrupt(); + m_async_response_fifo.Push(m_secondary_status.bits); + m_async_response_fifo.Push(subq.track_number_bcd); + m_async_response_fifo.Push(subq.index_number_bcd); + if (subq.absolute_frame_bcd & 0x10) { - m_last_cdda_report_frame_nibble = frame_nibble; - - Log_DebugPrintf("CDDA report at track[%02x] index[%02x] rel[%02x:%02x:%02x]", subq.track_number_bcd, - subq.index_number_bcd, subq.relative_minute_bcd, subq.relative_second_bcd, - subq.relative_frame_bcd); - - ClearAsyncInterrupt(); - m_async_response_fifo.Push(m_secondary_status.bits); - m_async_response_fifo.Push(subq.track_number_bcd); - m_async_response_fifo.Push(subq.index_number_bcd); - if (subq.absolute_frame_bcd & 0x10) - { - m_async_response_fifo.Push(subq.relative_minute_bcd); - m_async_response_fifo.Push(0x80 | subq.relative_second_bcd); - m_async_response_fifo.Push(subq.relative_frame_bcd); - } - else - { - m_async_response_fifo.Push(subq.absolute_minute_bcd); - m_async_response_fifo.Push(subq.absolute_second_bcd); - m_async_response_fifo.Push(subq.absolute_frame_bcd); - } - - m_async_response_fifo.Push(0); // peak low - m_async_response_fifo.Push(0); // peak high - SetAsyncInterrupt(Interrupt::DataReady); + m_async_response_fifo.Push(subq.relative_minute_bcd); + m_async_response_fifo.Push(0x80 | subq.relative_second_bcd); + m_async_response_fifo.Push(subq.relative_frame_bcd); } + else + { + m_async_response_fifo.Push(subq.absolute_minute_bcd); + m_async_response_fifo.Push(subq.absolute_second_bcd); + m_async_response_fifo.Push(subq.absolute_frame_bcd); + } + + m_async_response_fifo.Push(0); // peak low + m_async_response_fifo.Push(0); // peak high + SetAsyncInterrupt(Interrupt::DataReady); } } @@ -2344,6 +2342,7 @@ void CDROM::ClearSectorBuffers() void CDROM::DrawDebugWindow() { +#ifdef WITH_IMGUI static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f}; const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; @@ -2524,4 +2523,5 @@ void CDROM::DrawDebugWindow() } ImGui::End(); +#endif } diff --git a/src/core/controller.h b/src/core/controller.h index 3c63c3900..2f2a83eb4 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -14,8 +14,14 @@ class HostInterface; class Controller { public: + enum class AxisType : u8 + { + Full, + Half + }; + using ButtonList = std::vector>; - using AxisList = std::vector>; + using AxisList = std::vector>; using SettingList = std::vector; Controller(); diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index bc5aeadf1..cd02f72f2 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -59,8 +59,6 @@ - - @@ -107,8 +105,6 @@ - - @@ -147,14 +143,14 @@ {bb08260f-6fbc-46af-8924-090ee71360c6} - - {3773f4cc-614e-4028-8595-22e08ca649e3} - {ed601289-ac1a-46b8-a8ed-17db9eb73423} - - {933118a9-68c5-47b4-b151-b03c93961623} + + {9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035} + + + {7ff9fdb9-d504-47db-a16a-b08071999620} {ee054e08-3799-4a59-a422-18259c105ffd} @@ -304,10 +300,10 @@ Level4 Disabled - WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -330,10 +326,10 @@ Level4 Disabled - WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -356,10 +352,10 @@ Level4 Disabled - WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -385,10 +381,10 @@ Level4 Disabled - WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -415,8 +411,8 @@ MaxSpeed true - WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -442,8 +438,8 @@ MaxSpeed true - WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -470,8 +466,8 @@ MaxSpeed true - WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -497,8 +493,8 @@ MaxSpeed true - WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 0d86f8170..96b2c050b 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -31,7 +31,6 @@ - @@ -47,7 +46,6 @@ - @@ -82,7 +80,6 @@ - @@ -98,6 +95,5 @@ - \ No newline at end of file diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp index b4fc78ebb..727f449d8 100644 --- a/src/core/cpu_code_cache.cpp +++ b/src/core/cpu_code_cache.cpp @@ -139,8 +139,7 @@ static void ExecuteImpl() { if (HasPendingInterrupt()) { - // TODO: Fill in m_next_instruction... - SafeReadMemoryWord(g_state.regs.pc, &g_state.next_instruction.bits); + SafeReadInstruction(g_state.regs.pc, &g_state.next_instruction.bits); DispatchInterrupt(); next_block_key = GetNextBlockKey(); } @@ -165,6 +164,9 @@ static void ExecuteImpl() LogCurrentState(); #endif + if (g_settings.cpu_recompiler_icache) + CheckAndUpdateICacheTags(block->icache_line_count, block->uncached_fetch_ticks); + InterpretCachedBlock(*block); if (g_state.pending_ticks >= g_state.downcount) @@ -247,7 +249,7 @@ void ExecuteRecompiler() { if (HasPendingInterrupt()) { - SafeReadMemoryWord(g_state.regs.pc, &g_state.next_instruction.bits); + SafeReadInstruction(g_state.regs.pc, &g_state.next_instruction.bits); DispatchInterrupt(); } @@ -351,7 +353,8 @@ bool RevalidateBlock(CodeBlock* block) { for (const CodeBlockInstruction& cbi : block->instructions) { - u32 new_code = Bus::ReadCacheableAddress(cbi.pc & PHYSICAL_MEMORY_ADDRESS_MASK); + u32 new_code = 0; + SafeReadInstruction(cbi.pc, &new_code); if (cbi.instruction.bits != new_code) { Log_DebugPrintf("Block 0x%08X changed at PC 0x%08X - %08X to %08X - recompiling.", block->GetPC(), cbi.pc, @@ -395,16 +398,12 @@ bool CompileBlock(CodeBlock* block) __debugbreak(); #endif + u32 last_cache_line = ICACHE_LINES; + for (;;) { CodeBlockInstruction cbi = {}; - - const PhysicalMemoryAddress phys_addr = pc & PHYSICAL_MEMORY_ADDRESS_MASK; - if (!Bus::IsCacheableAddress(phys_addr)) - break; - - cbi.instruction.bits = Bus::ReadCacheableAddress(phys_addr); - if (!IsInvalidInstruction(cbi.instruction)) + if (!SafeReadInstruction(pc, &cbi.instruction.bits) || !IsInvalidInstruction(cbi.instruction)) break; cbi.pc = pc; @@ -416,6 +415,18 @@ bool CompileBlock(CodeBlock* block) cbi.has_load_delay = InstructionHasLoadDelay(cbi.instruction); cbi.can_trap = CanInstructionTrap(cbi.instruction, InUserMode()); + if (g_settings.cpu_recompiler_icache) + { + const u32 icache_line = GetICacheLine(pc); + if (icache_line != last_cache_line) + { + block->icache_line_count++; + block->icache_line_count = GetICacheFillTicks(pc); + last_cache_line = icache_line; + } + block->uncached_fetch_ticks += GetInstructionReadTicks(pc); + } + // instruction is decoded now block->instructions.push_back(cbi); pc += sizeof(cbi.instruction.bits); diff --git a/src/core/cpu_code_cache.h b/src/core/cpu_code_cache.h index eec01ac3b..068e6706e 100644 --- a/src/core/cpu_code_cache.h +++ b/src/core/cpu_code_cache.h @@ -61,6 +61,8 @@ struct CodeBlock std::vector link_predecessors; std::vector link_successors; + TickCount uncached_fetch_ticks = 0; + u32 icache_line_count = 0; bool invalidated = false; const u32 GetPC() const { return key.GetPC(); } diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index ea90a1fb5..cb92a8799 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -61,6 +61,7 @@ void Initialize() void Shutdown() { // GTE::Shutdown(); + PGXP::Shutdown(); } void Reset() @@ -80,6 +81,8 @@ void Reset() g_state.cop0_regs.sr.bits = 0; g_state.cop0_regs.cause.bits = 0; + ClearICache(); + GTE::Reset(); SetPC(RESET_VECTOR); @@ -117,14 +120,18 @@ bool DoState(StateWrapper& sw) sw.Do(&g_state.load_delay_value); sw.Do(&g_state.next_load_delay_reg); sw.Do(&g_state.next_load_delay_value); - sw.Do(&g_state.cache_control); + sw.Do(&g_state.cache_control.bits); sw.DoBytes(g_state.dcache.data(), g_state.dcache.size()); if (!GTE::DoState(sw)) return false; if (sw.IsReading()) - PGXP::Initialize(); + { + ClearICache(); + if (g_settings.gpu_pgxp_enable) + PGXP::Initialize(); + } return !sw.HasError(); } @@ -1416,7 +1423,6 @@ void InterpretCachedBlock(const CodeBlock& block) { // set up the state so we've already fetched the instruction DebugAssert(g_state.regs.pc == block.GetPC()); - g_state.regs.npc = block.GetPC() + 4; for (const CodeBlockInstruction& cbi : block.instructions) diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index 660596353..43c14c99a 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -19,7 +19,32 @@ enum : PhysicalMemoryAddress DCACHE_LOCATION = UINT32_C(0x1F800000), DCACHE_LOCATION_MASK = UINT32_C(0xFFFFFC00), DCACHE_OFFSET_MASK = UINT32_C(0x000003FF), - DCACHE_SIZE = UINT32_C(0x00000400) + DCACHE_SIZE = UINT32_C(0x00000400), + ICACHE_SIZE = UINT32_C(0x00001000), + ICACHE_SLOTS = ICACHE_SIZE / sizeof(u32), + ICACHE_LINE_SIZE = 16, + ICACHE_LINES = ICACHE_SIZE / ICACHE_LINE_SIZE, + ICACHE_SLOTS_PER_LINE = ICACHE_SLOTS / ICACHE_LINES, + ICACHE_TAG_ADDRESS_MASK = 0xFFFFFFF0u +}; + +enum : u32 +{ + ICACHE_DISABLED_BIT = 0x01, + ICACHE_INVALD_BIT = 0x02, +}; + +union CacheControl +{ + u32 bits; + + BitField lock_mode; + BitField invalidate_mode; + BitField tag_test_mode; + BitField dcache_scratchpad; + BitField dcache_enable; + BitField icache_fill_size; // actually dcache? icache always fills to 16 bytes + BitField icache_enable; }; struct State @@ -49,13 +74,15 @@ struct State Reg next_load_delay_reg = Reg::count; u32 next_load_delay_value = 0; - u32 cache_control = 0; + CacheControl cache_control{ 0 }; // GTE registers are stored here so we can access them on ARM with a single instruction GTE::Regs gte_regs = {}; // data cache (used as scratchpad) std::array dcache = {}; + std::array icache_tags = {}; + std::array icache_data = {}; }; extern State g_state; @@ -64,6 +91,7 @@ void Initialize(); void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); +void ClearICache(); /// Executes interpreter loop. void Execute(); diff --git a/src/core/cpu_core_private.h b/src/core/cpu_core_private.h index 41ad24ec5..9f74fd7f0 100644 --- a/src/core/cpu_core_private.h +++ b/src/core/cpu_core_private.h @@ -34,8 +34,38 @@ ALWAYS_INLINE static void DispatchInterrupt() g_state.regs.pc); } +// icache stuff +ALWAYS_INLINE bool IsCachedAddress(VirtualMemoryAddress address) +{ + // KUSEG, KSEG0 + return (address >> 29) <= 4; +} +ALWAYS_INLINE u32 GetICacheLine(VirtualMemoryAddress address) +{ + return ((address >> 4) & 0xFFu); +} +ALWAYS_INLINE u32 GetICacheLineOffset(VirtualMemoryAddress address) +{ + return (address & (ICACHE_LINE_SIZE - 1)); +} +ALWAYS_INLINE u32 GetICacheTagForAddress(VirtualMemoryAddress address) +{ + return (address & ICACHE_TAG_ADDRESS_MASK); +} +ALWAYS_INLINE bool CompareICacheTag(VirtualMemoryAddress address) +{ + const u32 line = GetICacheLine(address); + return (g_state.icache_tags[line] == GetICacheTagForAddress(address)); +} + +TickCount GetInstructionReadTicks(VirtualMemoryAddress address); +TickCount GetICacheFillTicks(VirtualMemoryAddress address); +u32 FillICache(VirtualMemoryAddress address); +void CheckAndUpdateICacheTags(u32 line_count, TickCount uncached_ticks); + // defined in cpu_memory.cpp - memory access functions which return false if an exception was thrown. bool FetchInstruction(); +bool SafeReadInstruction(VirtualMemoryAddress addr, u32* value); bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value); bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value); bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value); diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp index 1c7ae0db4..f350844a3 100644 --- a/src/core/cpu_recompiler_code_generator.cpp +++ b/src/core/cpu_recompiler_code_generator.cpp @@ -34,7 +34,7 @@ bool CodeGenerator::CompileBlock(const CodeBlock* block, CodeBlock::HostCodePoin const CodeBlockInstruction* cbi = m_block_start; while (cbi != m_block_end) { -#ifndef Y_BUILD_CONFIG_RELEASE +#ifdef _DEBUG SmallString disasm; DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits, nullptr); Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray()); @@ -840,6 +840,9 @@ void CodeGenerator::BlockPrologue() { EmitStoreCPUStructField(offsetof(State, exception_raised), Value::FromConstantU8(0)); + if (m_block->uncached_fetch_ticks > 0) + EmitICacheCheckAndUpdate(); + // we don't know the state of the last block, so assume load delays might be in progress // TODO: Pull load delay into register cache m_current_instruction_in_branch_delay_slot_dirty = true; diff --git a/src/core/cpu_recompiler_code_generator.h b/src/core/cpu_recompiler_code_generator.h index b59992330..438786bd3 100644 --- a/src/core/cpu_recompiler_code_generator.h +++ b/src/core/cpu_recompiler_code_generator.h @@ -61,6 +61,7 @@ public: void EmitFlushInterpreterLoadDelay(); void EmitMoveNextInterpreterLoadDelay(); void EmitCancelInterpreterLoadDelayForReg(Reg reg); + void EmitICacheCheckAndUpdate(); void EmitLoadCPUStructField(HostReg host_reg, RegSize size, u32 offset); void EmitStoreCPUStructField(u32 offset, const Value& value); void EmitAddCPUStructField(u32 offset, const Value& value); diff --git a/src/core/cpu_recompiler_code_generator_aarch64.cpp b/src/core/cpu_recompiler_code_generator_aarch64.cpp index de2f8ec3c..4a62184ab 100644 --- a/src/core/cpu_recompiler_code_generator_aarch64.cpp +++ b/src/core/cpu_recompiler_code_generator_aarch64.cpp @@ -18,6 +18,7 @@ constexpr HostReg RARG1 = 0; constexpr HostReg RARG2 = 1; constexpr HostReg RARG3 = 2; constexpr HostReg RARG4 = 3; +constexpr HostReg RSCRATCH = 8; constexpr u64 FUNCTION_CALL_STACK_ALIGNMENT = 16; constexpr u64 FUNCTION_CALL_SHADOW_SPACE = 32; constexpr u64 FUNCTION_CALLEE_SAVED_SPACE_RESERVE = 80; // 8 registers @@ -125,7 +126,7 @@ void CodeGenerator::InitHostRegs() // TODO: function calls mess up the parameter registers if we use them.. fix it // allocate nonvolatile before volatile m_register_cache.SetHostRegAllocationOrder( - {19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}); + {19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17}); m_register_cache.SetCallerSavedHostRegs({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}); m_register_cache.SetCalleeSavedHostRegs({19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30}); m_register_cache.SetCPUPtrHostReg(RCPUPTR); @@ -977,8 +978,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr) const bool use_blr = !vixl::IsInt26(displacement); if (use_blr) { - m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(RRETURN)); + m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RSCRATCH)); } else { @@ -1012,8 +1013,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co const bool use_blr = !vixl::IsInt26(displacement); if (use_blr) { - m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(RRETURN)); + m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RSCRATCH)); } else { @@ -1048,8 +1049,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co const bool use_blr = !vixl::IsInt26(displacement); if (use_blr) { - m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(RRETURN)); + m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RSCRATCH)); } else { @@ -1086,8 +1087,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co const bool use_blr = !vixl::IsInt26(displacement); if (use_blr) { - m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(RRETURN)); + m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RSCRATCH)); } else { @@ -1125,8 +1126,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co const bool use_blr = !vixl::IsInt26(displacement); if (use_blr) { - m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(RRETURN)); + m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RSCRATCH)); } else { diff --git a/src/core/cpu_recompiler_code_generator_generic.cpp b/src/core/cpu_recompiler_code_generator_generic.cpp index b652cb24b..0acd1559f 100644 --- a/src/core/cpu_recompiler_code_generator_generic.cpp +++ b/src/core/cpu_recompiler_code_generator_generic.cpp @@ -1,4 +1,5 @@ #include "cpu_core.h" +#include "cpu_core_private.h" #include "cpu_recompiler_code_generator.h" namespace CPU::Recompiler { @@ -22,4 +23,48 @@ void CodeGenerator::EmitStoreInterpreterLoadDelay(Reg reg, const Value& value) m_load_delay_dirty = true; } +#ifndef CPU_X64 + +void CodeGenerator::EmitICacheCheckAndUpdate() +{ + Value pc = CalculatePC(); + Value temp = m_register_cache.AllocateScratch(RegSize_32); + m_register_cache.InhibitAllocation(); + + EmitShr(temp.GetHostRegister(), pc.GetHostRegister(), RegSize_32, Value::FromConstantU32(29)); + LabelType is_cached; + LabelType ready_to_execute; + EmitConditionalBranch(Condition::LessEqual, false, temp.GetHostRegister(), Value::FromConstantU32(4), &is_cached); + EmitAddCPUStructField(offsetof(State, pending_ticks), + Value::FromConstantU32(static_cast(m_block->uncached_fetch_ticks))); + EmitBranch(&ready_to_execute); + EmitBindLabel(&is_cached); + + // cached path + EmitAnd(pc.GetHostRegister(), pc.GetHostRegister(), Value::FromConstantU32(ICACHE_TAG_ADDRESS_MASK)); + VirtualMemoryAddress current_address = (m_block->instructions[0].pc & ICACHE_TAG_ADDRESS_MASK); + for (u32 i = 0; i < m_block->icache_line_count; i++, current_address += ICACHE_LINE_SIZE) + { + const TickCount fill_ticks = GetICacheFillTicks(current_address); + if (fill_ticks <= 0) + continue; + + const u32 line = GetICacheLine(current_address); + const u32 offset = offsetof(State, icache_tags) + (line * sizeof(u32)); + LabelType cache_hit; + + EmitLoadCPUStructField(temp.GetHostRegister(), RegSize_32, offset); + EmitConditionalBranch(Condition::Equal, false, temp.GetHostRegister(), pc, &cache_hit); + EmitAddCPUStructField(offsetof(State, pending_ticks), Value::FromConstantU32(static_cast(fill_ticks))); + EmitStoreCPUStructField(offset, pc); + EmitBindLabel(&cache_hit); + EmitAdd(pc.GetHostRegister(), pc.GetHostRegister(), Value::FromConstantU32(ICACHE_LINE_SIZE), false); + } + + EmitBindLabel(&ready_to_execute); + m_register_cache.UnunhibitAllocation(); +} + +#endif + } // namespace CPU::Recompiler diff --git a/src/core/cpu_recompiler_code_generator_x64.cpp b/src/core/cpu_recompiler_code_generator_x64.cpp index e6f85e3e6..fd2f34035 100644 --- a/src/core/cpu_recompiler_code_generator_x64.cpp +++ b/src/core/cpu_recompiler_code_generator_x64.cpp @@ -2187,6 +2187,52 @@ void CodeGenerator::EmitCancelInterpreterLoadDelayForReg(Reg reg) m_emit->L(skip_cancel); } +void CodeGenerator::EmitICacheCheckAndUpdate() +{ + Value pc = CalculatePC(); + Value seg = m_register_cache.AllocateScratch(RegSize_32); + m_register_cache.InhibitAllocation(); + + m_emit->mov(GetHostReg32(seg), GetHostReg32(pc)); + m_emit->shr(GetHostReg32(seg), 29); + + Xbyak::Label is_cached; + m_emit->cmp(GetHostReg32(seg), 4); + m_emit->jle(is_cached); + + // uncached + Xbyak::Label done; + m_emit->add(m_emit->dword[GetCPUPtrReg() + offsetof(State, pending_ticks)], + static_cast(m_block->uncached_fetch_ticks)); + m_emit->jmp(done, Xbyak::CodeGenerator::T_NEAR); + + // cached + m_emit->L(is_cached); + m_emit->and_(GetHostReg32(pc), ICACHE_TAG_ADDRESS_MASK); + + VirtualMemoryAddress current_address = (m_block->instructions[0].pc & ICACHE_TAG_ADDRESS_MASK); + for (u32 i = 0; i < m_block->icache_line_count; i++, current_address += ICACHE_LINE_SIZE) + { + const TickCount fill_ticks = GetICacheFillTicks(current_address); + if (fill_ticks <= 0) + continue; + + const u32 line = GetICacheLine(current_address); + const u32 offset = offsetof(State, icache_tags) + (line * sizeof(u32)); + Xbyak::Label cache_hit; + + m_emit->cmp(GetHostReg32(pc), m_emit->dword[GetCPUPtrReg() + offset]); + m_emit->je(cache_hit); + m_emit->mov(m_emit->dword[GetCPUPtrReg() + offset], GetHostReg32(pc)); + m_emit->add(m_emit->dword[GetCPUPtrReg() + offsetof(State, pending_ticks)], static_cast(fill_ticks)); + m_emit->L(cache_hit); + m_emit->add(GetHostReg32(pc), ICACHE_LINE_SIZE); + } + + m_emit->L(done); + m_register_cache.UnunhibitAllocation(); +} + void CodeGenerator::EmitBranch(const void* address, bool allow_scratch) { const s64 jump_distance = diff --git a/src/core/cpu_recompiler_thunks.h b/src/core/cpu_recompiler_thunks.h index 602f522af..f698a859d 100644 --- a/src/core/cpu_recompiler_thunks.h +++ b/src/core/cpu_recompiler_thunks.h @@ -14,6 +14,7 @@ namespace Recompiler::Thunks { ////////////////////////////////////////////////////////////////////////// bool InterpretInstruction(); bool InterpretInstructionPGXP(); +void CheckAndUpdateICache(u32 pc, u32 line_count); // Memory access functions for the JIT - MSB is set on exception. u64 ReadMemoryByte(u32 address); diff --git a/src/core/digital_controller.cpp b/src/core/digital_controller.cpp index 5444356e2..8ab5d7bbc 100644 --- a/src/core/digital_controller.cpp +++ b/src/core/digital_controller.cpp @@ -1,6 +1,7 @@ #include "digital_controller.h" #include "common/assert.h" #include "common/state_wrapper.h" +#include "host_interface.h" DigitalController::DigitalController() = default; @@ -155,13 +156,20 @@ Controller::AxisList DigitalController::StaticGetAxisNames() Controller::ButtonList DigitalController::StaticGetButtonNames() { -#define B(n) \ - { \ -#n, static_cast < s32>(Button::n) \ - } - return {B(Up), B(Down), B(Left), B(Right), B(Select), B(Start), B(Triangle), - B(Cross), B(Circle), B(Square), B(L1), B(L2), B(R1), B(R2)}; -#undef B + return {{TRANSLATABLE("DigitalController", "Up"), static_cast(Button::Up)}, + {TRANSLATABLE("DigitalController", "Down"), static_cast(Button::Down)}, + {TRANSLATABLE("DigitalController", "Left"), static_cast(Button::Left)}, + {TRANSLATABLE("DigitalController", "Right"), static_cast(Button::Right)}, + {TRANSLATABLE("DigitalController", "Select"), static_cast(Button::Select)}, + {TRANSLATABLE("DigitalController", "Start"), static_cast(Button::Start)}, + {TRANSLATABLE("DigitalController", "Triangle"), static_cast(Button::Triangle)}, + {TRANSLATABLE("DigitalController", "Cross"), static_cast(Button::Cross)}, + {TRANSLATABLE("DigitalController", "Circle"), static_cast(Button::Circle)}, + {TRANSLATABLE("DigitalController", "Square"), static_cast(Button::Square)}, + {TRANSLATABLE("DigitalController", "L1"), static_cast(Button::L1)}, + {TRANSLATABLE("DigitalController", "L2"), static_cast(Button::L2)}, + {TRANSLATABLE("DigitalController", "R1"), static_cast(Button::R1)}, + {TRANSLATABLE("DigitalController", "R2"), static_cast(Button::R2)}}; } u32 DigitalController::StaticGetVibrationMotorCount() diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index c56657c45..32d36ddcf 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -10,7 +10,9 @@ #include "system.h" #include "timers.h" #include -#include +#ifdef WITH_IMGUI +#include "imgui.h" +#endif Log_SetChannel(GPU); std::unique_ptr g_gpu; @@ -1341,6 +1343,7 @@ bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride void GPU::DrawDebugStateWindow() { +#ifdef WITH_IMGUI const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; ImGui::SetNextWindowSize(ImVec2(450.0f * framebuffer_scale, 550.0f * framebuffer_scale), ImGuiCond_FirstUseEver); @@ -1451,6 +1454,7 @@ void GPU::DrawDebugStateWindow() } ImGui::End(); +#endif } void GPU::DrawRendererStats(bool is_idle_frame) {} diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 7dfe6f432..4741a9263 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -3,13 +3,15 @@ #include "common/log.h" #include "common/state_wrapper.h" #include "cpu_core.h" -#include "imgui.h" #include "pgxp.h" #include "settings.h" #include "system.h" #include #include #include +#ifdef WITH_IMGUI +#include "imgui.h" +#endif Log_SetChannel(GPU_HW); template @@ -1000,6 +1002,7 @@ void GPU_HW::DrawRendererStats(bool is_idle_frame) m_renderer_stats = {}; } +#ifdef WITH_IMGUI if (ImGui::CollapsingHeader("Renderer Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; @@ -1068,4 +1071,5 @@ void GPU_HW::DrawRendererStats(bool is_idle_frame) ImGui::Columns(1); } +#endif } diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index 118dfe51e..ee1817367 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -594,7 +594,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMod WriteHeader(ss); DefineMacro(ss, "TRANSPARENCY", transparency != GPU_HW::BatchRenderMode::TransparencyDisabled); DefineMacro(ss, "TRANSPARENCY_ONLY_OPAQUE", transparency == GPU_HW::BatchRenderMode::OnlyOpaque); - DefineMacro(ss, "TRANSPARENCY_ONLY_TRANSPARENCY", transparency == GPU_HW::BatchRenderMode::OnlyTransparent); + DefineMacro(ss, "TRANSPARENCY_ONLY_TRANSPARENT", transparency == GPU_HW::BatchRenderMode::OnlyTransparent); DefineMacro(ss, "TEXTURED", textured); DefineMacro(ss, "PALETTE", actual_texture_mode == GPU::TextureMode::Palette4Bit || @@ -889,18 +889,23 @@ void BilinearSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits, } else { - #if TRANSPARENCY_ONLY_TRANSPARENCY + #if TRANSPARENCY_ONLY_TRANSPARENT discard; #endif #if TRANSPARENCY_ONLY_OPAQUE - // We don't output the second color here because it's not used. + // We don't output the second color here because it's not used (except for filtering). o_col0 = float4(color, oalpha); - #elif USE_DUAL_SOURCE - o_col0 = float4(color, oalpha); - o_col1 = float4(0.0, 0.0, 0.0, 1.0 - ialpha); + #if USE_DUAL_SOURCE + o_col1 = float4(0.0, 0.0, 0.0, 1.0 - ialpha); + #endif #else - o_col0 = float4(color, 1.0 - ialpha); + #if USE_DUAL_SOURCE + o_col0 = float4(color, oalpha); + o_col1 = float4(0.0, 0.0, 0.0, 1.0 - ialpha); + #else + o_col0 = float4(color, 1.0 - ialpha); + #endif #endif o_depth = oalpha * v_pos.z; diff --git a/src/core/gpu_hw_vulkan.cpp b/src/core/gpu_hw_vulkan.cpp index 12f2b0ae8..5f1a31dcc 100644 --- a/src/core/gpu_hw_vulkan.cpp +++ b/src/core/gpu_hw_vulkan.cpp @@ -99,13 +99,6 @@ void GPU_HW_Vulkan::ResetGraphicsAPIState() GPU_HW::ResetGraphicsAPIState(); EndRenderPass(); - - // vram texture is probably going to be displayed now - if (!IsDisplayDisabled()) - { - m_vram_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - } } void GPU_HW_Vulkan::RestoreGraphicsAPIState() @@ -394,7 +387,7 @@ bool GPU_HW_Vulkan::CreateFramebuffer() !m_vram_readback_texture.Create(VRAM_WIDTH, VRAM_HEIGHT, 1, 1, texture_format, samples, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT) || - !m_vram_readback_staging_texture.Create(Vulkan::StagingBuffer::Type::Readback, texture_format, VRAM_WIDTH, + !m_vram_readback_staging_texture.Create(Vulkan::StagingBuffer::Type::Readback, texture_format, VRAM_WIDTH / 2, VRAM_HEIGHT)) { return false; @@ -683,7 +676,9 @@ bool GPU_HW_Vulkan::CompilePipelines() gpbuilder.SetBlendAttachment( 0, true, VK_BLEND_FACTOR_ONE, m_supports_dual_source_blend ? VK_BLEND_FACTOR_SRC1_ALPHA : VK_BLEND_FACTOR_SRC_ALPHA, - (static_cast(transparency_mode) == TransparencyMode::BackgroundMinusForeground) ? + (static_cast(transparency_mode) == TransparencyMode::BackgroundMinusForeground && + static_cast(render_mode) != BatchRenderMode::TransparencyDisabled && + static_cast(render_mode) != BatchRenderMode::OnlyOpaque) ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD); @@ -926,9 +921,12 @@ void GPU_HW_Vulkan::ClearDisplay() void GPU_HW_Vulkan::UpdateDisplay() { GPU_HW::UpdateDisplay(); + EndRenderPass(); if (g_settings.debugging.show_vram) { + m_vram_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_host_display->SetDisplayTexture(&m_vram_texture, m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), 0, 0, m_vram_texture.GetWidth(), m_vram_texture.GetHeight()); m_host_display->SetDisplayParameters(VRAM_WIDTH, VRAM_HEIGHT, 0, 0, VRAM_WIDTH, VRAM_HEIGHT, @@ -954,6 +952,8 @@ void GPU_HW_Vulkan::UpdateDisplay() (scaled_vram_offset_x + scaled_display_width) <= m_vram_texture.GetWidth() && (scaled_vram_offset_y + scaled_display_height) <= m_vram_texture.GetHeight()) { + m_vram_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_host_display->SetDisplayTexture(&m_vram_texture, m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width, scaled_display_height); @@ -1017,7 +1017,8 @@ void GPU_HW_Vulkan::ReadVRAM(u32 x, u32 y, u32 width, u32 height) // Work around Mali driver bug: set full framebuffer size for render area. The GPU crashes with a page fault if we use // the actual size we're rendering to... - BeginRenderPass(m_vram_readback_render_pass, m_vram_readback_framebuffer, 0, 0, VRAM_WIDTH, VRAM_HEIGHT); + BeginRenderPass(m_vram_readback_render_pass, m_vram_readback_framebuffer, 0, 0, m_vram_readback_texture.GetWidth(), + m_vram_readback_texture.GetHeight()); // Encode the 24-bit texture as 16-bit. const u32 uniforms[4] = {copy_rect.left, copy_rect.top, copy_rect.GetWidth(), copy_rect.GetHeight()}; diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index db8efc625..bbf1c0990 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include Log_SetChannel(HostInterface); @@ -362,6 +361,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE)); si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false); + si.SetBoolValue("CPU", "ICache", false); si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)); si.SetIntValue("GPU", "ResolutionScale", 1); @@ -452,7 +452,8 @@ void HostInterface::FixIncompatibleSettings(bool display_osd_messages) { if (display_osd_messages) { - AddOSDMessage(TranslateStdString("OSDMessage", "PGXP is incompatible with the software renderer, disabling PGXP."), 10.0f); + AddOSDMessage( + TranslateStdString("OSDMessage", "PGXP is incompatible with the software renderer, disabling PGXP."), 10.0f); } g_settings.gpu_pgxp_enable = false; } @@ -510,6 +511,8 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) AddFormattedOSDMessage(5.0f, "Switching to %s CPU execution mode.", Settings::GetCPUExecutionModeName(g_settings.cpu_execution_mode)); CPU::CodeCache::SetUseRecompiler(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler); + CPU::CodeCache::Flush(); + CPU::ClearICache(); } if (g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler && @@ -520,6 +523,15 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) CPU::CodeCache::Flush(); } + if (g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter && + g_settings.cpu_recompiler_icache != old_settings.cpu_recompiler_icache) + { + AddFormattedOSDMessage(5.0f, "CPU ICache %s, flushing all blocks.", + g_settings.cpu_recompiler_icache ? "enabled" : "disabled"); + CPU::CodeCache::Flush(); + CPU::ClearICache(); + } + m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume); if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale || @@ -549,6 +561,9 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) CPU::CodeCache::Flush(); } + if (old_settings.gpu_pgxp_enable) + PGXP::Shutdown(); + if (g_settings.gpu_pgxp_enable) PGXP::Initialize(); } diff --git a/src/core/mdec.cpp b/src/core/mdec.cpp index 1d83e3435..cb1d752eb 100644 --- a/src/core/mdec.cpp +++ b/src/core/mdec.cpp @@ -5,7 +5,9 @@ #include "dma.h" #include "interrupt_controller.h" #include "system.h" -#include +#ifdef WITH_IMGUI +#include "imgui.h" +#endif Log_SetChannel(MDEC); MDEC g_mdec; @@ -701,6 +703,7 @@ void MDEC::HandleSetScaleCommand() void MDEC::DrawDebugStateWindow() { +#ifdef WITH_IMGUI const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; ImGui::SetNextWindowSize(ImVec2(300.0f * framebuffer_scale, 350.0f * framebuffer_scale), ImGuiCond_FirstUseEver); @@ -738,4 +741,5 @@ void MDEC::DrawDebugStateWindow() } ImGui::End(); +#endif } diff --git a/src/core/namco_guncon.cpp b/src/core/namco_guncon.cpp index 8916c5282..b3bf49488 100644 --- a/src/core/namco_guncon.cpp +++ b/src/core/namco_guncon.cpp @@ -209,12 +209,9 @@ Controller::AxisList NamcoGunCon::StaticGetAxisNames() Controller::ButtonList NamcoGunCon::StaticGetButtonNames() { -#define B(n) \ - { \ -#n, static_cast < s32>(Button::n) \ - } - return {B(Trigger), B(A), B(B)}; -#undef B + return {{TRANSLATABLE("NamcoGunCon", "Trigger"), static_cast(Button::Trigger)}, + {TRANSLATABLE("NamcoGunCon", "A"), static_cast(Button::A)}, + {TRANSLATABLE("NamcoGunCon", "B"), static_cast(Button::B)}}; } u32 NamcoGunCon::StaticGetVibrationMotorCount() diff --git a/src/core/negcon.cpp b/src/core/negcon.cpp index 85d235c41..d1c8e16c5 100644 --- a/src/core/negcon.cpp +++ b/src/core/negcon.cpp @@ -219,12 +219,12 @@ std::optional NeGcon::StaticGetButtonCodeByName(std::string_view button_nam Controller::AxisList NeGcon::StaticGetAxisNames() { -#define A(n) \ +#define A(n, t) \ { \ - #n, static_cast (Axis::n) \ + #n, static_cast (Axis::n), Controller::AxisType::t \ } - return {A(Steering), A(I), A(II), A(L)}; + return {A(Steering, Full), A(I, Half), A(II, Half), A(L, Half)}; #undef A } diff --git a/src/core/pgxp.cpp b/src/core/pgxp.cpp index 04ae11219..029f6057e 100644 --- a/src/core/pgxp.cpp +++ b/src/core/pgxp.cpp @@ -20,8 +20,8 @@ #include "pgxp.h" #include "settings.h" -#include #include +#include namespace PGXP { // pgxp_types.h @@ -134,6 +134,26 @@ static void WriteMem(PGXP_value* value, u32 addr); static void WriteMem16(PGXP_value* src, u32 addr); // pgxp_gpu.h +enum : u32 +{ + VERTEX_CACHE_WIDTH = 0x800 * 2, + VERTEX_CACHE_HEIGHT = 0x800 * 2, + VERTEX_CACHE_SIZE = VERTEX_CACHE_WIDTH * VERTEX_CACHE_HEIGHT, + PGXP_MEM_SIZE = 3 * 2048 * 1024 / 4 // mirror 2MB in 32-bit words * 3 +}; + +static PGXP_value* Mem = nullptr; + +const unsigned int mode_init = 0; +const unsigned int mode_write = 1; +const unsigned int mode_read = 2; +const unsigned int mode_fail = 3; + +unsigned int baseID = 0; +unsigned int lastID = 0; +unsigned int cacheMode = 0; +static PGXP_value* vertexCache = nullptr; + void PGXP_CacheVertex(short sx, short sy, const PGXP_value* _pVertex); // pgxp_gte.h @@ -142,8 +162,8 @@ static void PGXP_InitGTE(); // pgxp_cpu.h static void PGXP_InitCPU(); static PGXP_value CPU_reg_mem[34]; -#define CPU_Hi CPU_reg[33] -#define CPU_Lo CPU_reg[34] +#define CPU_Hi CPU_reg[32] +#define CPU_Lo CPU_reg[33] static PGXP_value CP0_reg_mem[32]; static PGXP_value* CPU_reg = CPU_reg_mem; @@ -195,7 +215,6 @@ double f16Overflow(double in) // pgxp_mem.c static void PGXP_InitMem(); -static PGXP_value Mem[3 * 2048 * 1024 / 4]; // mirror 2MB in 32-bit words * 3 static const u32 UserMemOffset = 0; static const u32 ScratchOffset = 2048 * 1024 / 4; static const u32 RegisterOffset = 2 * 2048 * 1024 / 4; @@ -203,7 +222,19 @@ static const u32 InvalidAddress = 3 * 2048 * 1024 / 4; void PGXP_InitMem() { - memset(Mem, 0, sizeof(Mem)); + if (!Mem) + { + Mem = static_cast(std::calloc(PGXP_MEM_SIZE, sizeof(PGXP_value))); + if (!Mem) + { + std::fprintf(stderr, "Failed to allocate PGXP memory\n"); + std::abort(); + } + } + else + { + std::memset(Mem, 0, sizeof(PGXP_value) * PGXP_MEM_SIZE); + } } u32 PGXP_ConvertAddress(u32 addr) @@ -366,8 +397,6 @@ void WriteMem16(PGXP_value* src, u32 addr) } // pgxp_main.c -u32 static gMode = 0; - void Initialize() { PGXP_InitMem(); @@ -375,24 +404,19 @@ void Initialize() PGXP_InitGTE(); } -void PGXP_SetModes(u32 modes) +void Shutdown() { - gMode = modes; -} - -u32 PGXP_GetModes() -{ - return gMode; -} - -void PGXP_EnableModes(u32 modes) -{ - gMode |= modes; -} - -void PGXP_DisableModes(u32 modes) -{ - gMode = gMode & ~modes; + cacheMode = mode_init; + if (vertexCache) + { + std::free(vertexCache); + vertexCache = nullptr; + } + if (Mem) + { + std::free(Mem); + Mem = nullptr; + } } // pgxp_gte.c @@ -584,17 +608,6 @@ void CPU_SWC2(u32 instr, u32 rtVal, u32 addr) ///////////////////////////////// //// Blade_Arma's Vertex Cache (CatBlade?) ///////////////////////////////// -const unsigned int mode_init = 0; -const unsigned int mode_write = 1; -const unsigned int mode_read = 2; -const unsigned int mode_fail = 3; - -PGXP_value vertexCache[0x800 * 2][0x800 * 2]; - -unsigned int baseID = 0; -unsigned int lastID = 0; -unsigned int cacheMode = 0; - unsigned int IsSessionID(unsigned int vertID) { // No wrapping @@ -612,6 +625,23 @@ unsigned int IsSessionID(unsigned int vertID) return 0; } +static void InitPGXPVertexCache() +{ + if (!vertexCache) + { + vertexCache = static_cast(std::calloc(VERTEX_CACHE_SIZE, sizeof(PGXP_value))); + if (!vertexCache) + { + std::fprintf(stderr, "Failed to allocate PGXP vertex cache memory\n"); + std::abort(); + } + } + else + { + memset(vertexCache, 0x00, VERTEX_CACHE_SIZE * sizeof(PGXP_value)); + } +} + void PGXP_CacheVertex(short sx, short sy, const PGXP_value* _pVertex) { const PGXP_value* pNewVertex = (const PGXP_value*)_pVertex; @@ -623,14 +653,14 @@ void PGXP_CacheVertex(short sx, short sy, const PGXP_value* _pVertex) return; } + // Initialise cache on first use + if (!vertexCache) + InitPGXPVertexCache(); + // if (bGteAccuracy) { if (cacheMode != mode_write) { - // Initialise cache on first use - if (cacheMode == mode_init) - memset(vertexCache, 0x00, sizeof(vertexCache)); - // First vertex of write session (frame?) cacheMode = mode_write; baseID = pNewVertex->count; @@ -640,7 +670,7 @@ void PGXP_CacheVertex(short sx, short sy, const PGXP_value* _pVertex) if (sx >= -0x800 && sx <= 0x7ff && sy >= -0x800 && sy <= 0x7ff) { - pOldVertex = &vertexCache[sy + 0x800][sx + 0x800]; + pOldVertex = &vertexCache[(sy + 0x800) * VERTEX_CACHE_WIDTH + (sx + 0x800)]; // To avoid ambiguity there can only be one valid entry per-session if (0) //(IsSessionID(pOldVertex->count) && (pOldVertex->value == pNewVertex->value)) @@ -664,25 +694,25 @@ void PGXP_CacheVertex(short sx, short sy, const PGXP_value* _pVertex) PGXP_value* PGXP_GetCachedVertex(short sx, short sy) { - // if (bGteAccuracy) + if (g_settings.gpu_pgxp_vertex_cache) { if (cacheMode != mode_read) { if (cacheMode == mode_fail) return NULL; - // Initialise cache on first use - if (cacheMode == mode_init) - memset(vertexCache, 0x00, sizeof(vertexCache)); - // First vertex of read session (frame?) cacheMode = mode_read; } + // Initialise cache on first use + if (!vertexCache) + InitPGXPVertexCache(); + if (sx >= -0x800 && sx <= 0x7ff && sy >= -0x800 && sy <= 0x7ff) { // Return pointer to cache entry - return &vertexCache[sy + 0x800][sx + 0x800]; + return &vertexCache[(sy + 0x800) * VERTEX_CACHE_WIDTH + (sx + 0x800)]; } } diff --git a/src/core/pgxp.h b/src/core/pgxp.h index db3192cd4..e5c664941 100644 --- a/src/core/pgxp.h +++ b/src/core/pgxp.h @@ -24,6 +24,7 @@ namespace PGXP { void Initialize(); +void Shutdown(); // -- GTE functions // Transforms diff --git a/src/core/settings.cpp b/src/core/settings.cpp index e0254e3c8..21cdbc4b1 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -92,6 +92,7 @@ void Settings::Load(SettingsInterface& si) si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str()) .value_or(DEFAULT_CPU_EXECUTION_MODE); cpu_recompiler_memory_exceptions = si.GetBoolValue("CPU", "RecompilerMemoryExceptions", false); + cpu_recompiler_icache = si.GetBoolValue("CPU", "RecompilerICache", false); gpu_renderer = ParseRendererName(si.GetStringValue("GPU", "Renderer", GetRendererName(DEFAULT_GPU_RENDERER)).c_str()) .value_or(DEFAULT_GPU_RENDERER); @@ -206,6 +207,7 @@ void Settings::Save(SettingsInterface& si) const si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode)); si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions); + si.SetBoolValue("CPU", "RecompilerICache", cpu_recompiler_icache); si.SetStringValue("GPU", "Renderer", GetRendererName(gpu_renderer)); si.SetStringValue("GPU", "Adapter", gpu_adapter.c_str()); diff --git a/src/core/settings.h b/src/core/settings.h index 91e26d5b4..5b98bde8e 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -69,6 +69,7 @@ struct Settings CPUExecutionMode cpu_execution_mode = CPUExecutionMode::Interpreter; bool cpu_recompiler_memory_exceptions = false; + bool cpu_recompiler_icache = false; float emulation_speed = 1.0f; bool speed_limiter_enabled = true; diff --git a/src/core/spu.cpp b/src/core/spu.cpp index e0d42711f..ecb8b57a6 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -8,7 +8,9 @@ #include "host_interface.h" #include "interrupt_controller.h" #include "system.h" -#include +#ifdef WITH_IMGUI +#include "imgui.h" +#endif Log_SetChannel(SPU); SPU g_spu; @@ -1747,6 +1749,7 @@ void SPU::ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out void SPU::DrawDebugStateWindow() { +#ifdef WITH_IMGUI static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f}; const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; @@ -1924,4 +1927,5 @@ void SPU::DrawDebugStateWindow() } ImGui::End(); +#endif } diff --git a/src/core/system.cpp b/src/core/system.cpp index 50ab3d24d..afa06785e 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -4,6 +4,7 @@ #include "cdrom.h" #include "common/audio_stream.h" #include "common/file_system.h" +#include "common/iso_reader.h" #include "common/log.h" #include "common/state_wrapper.h" #include "common/string_util.h" @@ -11,7 +12,6 @@ #include "cpu_code_cache.h" #include "cpu_core.h" #include "dma.h" -#include "game_list.h" #include "gpu.h" #include "gte.h" #include "host_display.h" @@ -27,7 +27,8 @@ #include "spu.h" #include "timers.h" #include -#include +#include +#include #include Log_SetChannel(System); @@ -204,6 +205,71 @@ float GetThrottleFrequency() return s_throttle_frequency; } +bool IsExeFileName(const char* path) +{ + const char* extension = std::strrchr(path, '.'); + return (extension && + (StringUtil::Strcasecmp(extension, ".exe") == 0 || StringUtil::Strcasecmp(extension, ".psexe") == 0)); +} + +bool IsPsfFileName(const char* path) +{ + const char* extension = std::strrchr(path, '.'); + return (extension && StringUtil::Strcasecmp(extension, ".psf") == 0); +} + +bool IsM3UFileName(const char* path) +{ + const char* extension = std::strrchr(path, '.'); + return (extension && StringUtil::Strcasecmp(extension, ".m3u") == 0); +} + +std::vector ParseM3UFile(const char* path) +{ + std::ifstream ifs(path); + if (!ifs.is_open()) + { + Log_ErrorPrintf("Failed to open %s", path); + return {}; + } + + std::vector entries; + std::string line; + while (std::getline(ifs, line)) + { + u32 start_offset = 0; + while (start_offset < line.size() && std::isspace(line[start_offset])) + start_offset++; + + // skip comments + if (start_offset == line.size() || line[start_offset] == '#') + continue; + + // strip ending whitespace + u32 end_offset = static_cast(line.size()) - 1; + while (std::isspace(line[end_offset]) && end_offset > start_offset) + end_offset--; + + // anything? + if (start_offset == end_offset) + continue; + + std::string entry_path(line.begin() + start_offset, line.begin() + end_offset + 1); + if (!FileSystem::IsAbsolutePath(entry_path)) + { + SmallString absolute_path; + FileSystem::BuildPathRelativeToFile(absolute_path, path, entry_path.c_str()); + entry_path = absolute_path; + } + + Log_DevPrintf("Read path from m3u: '%s'", entry_path.c_str()); + entries.push_back(std::move(entry_path)); + } + + Log_InfoPrintf("Loaded %zu paths from m3u '%s'", entries.size(), path); + return entries; +} + ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region) { switch (region) @@ -221,6 +287,188 @@ ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region) } } +std::string_view GetTitleForPath(const char* path) +{ + const char* extension = std::strrchr(path, '.'); + if (path == extension) + return path; + + const char* path_end = path + std::strlen(path); + const char* title_end = extension ? (extension - 1) : (path_end); + const char* title_start = std::max(std::strrchr(path, '/'), std::strrchr(path, '\\')); + if (!title_start || title_start == path) + return std::string_view(path, title_end - title_start); + else + return std::string_view(title_start + 1, title_end - title_start); +} + +std::string GetGameCodeForPath(const char* image_path) +{ + std::unique_ptr cdi = CDImage::Open(image_path); + if (!cdi) + return {}; + + return GetGameCodeForImage(cdi.get()); +} + +std::string GetGameCodeForImage(CDImage* cdi) +{ + ISOReader iso; + if (!iso.Open(cdi, 1)) + return {}; + + // Read SYSTEM.CNF + std::vector system_cnf_data; + if (!iso.ReadFile("SYSTEM.CNF", &system_cnf_data)) + return {}; + + // Parse lines + std::vector> lines; + std::pair current_line; + bool reading_value = false; + for (size_t pos = 0; pos < system_cnf_data.size(); pos++) + { + const char ch = static_cast(system_cnf_data[pos]); + if (ch == '\r' || ch == '\n') + { + if (!current_line.first.empty()) + { + lines.push_back(std::move(current_line)); + current_line = {}; + reading_value = false; + } + } + else if (ch == ' ' || (ch >= 0x09 && ch <= 0x0D)) + { + continue; + } + else if (ch == '=' && !reading_value) + { + reading_value = true; + } + else + { + if (reading_value) + current_line.second.push_back(ch); + else + current_line.first.push_back(ch); + } + } + + if (!current_line.first.empty()) + lines.push_back(std::move(current_line)); + + // Find the BOOT line + auto iter = std::find_if(lines.begin(), lines.end(), + [](const auto& it) { return StringUtil::Strcasecmp(it.first.c_str(), "boot") == 0; }); + if (iter == lines.end()) + return {}; + + // cdrom:\SCES_123.45;1 + std::string code = iter->second; + std::string::size_type pos = code.rfind('\\'); + if (pos != std::string::npos) + { + code.erase(0, pos + 1); + } + else + { + // cdrom:SCES_123.45;1 + pos = code.rfind(':'); + if (pos != std::string::npos) + code.erase(0, pos + 1); + } + + pos = code.find(';'); + if (pos != std::string::npos) + code.erase(pos); + + // SCES_123.45 -> SCES-12345 + for (pos = 0; pos < code.size();) + { + if (code[pos] == '.') + { + code.erase(pos, 1); + continue; + } + + if (code[pos] == '_') + code[pos] = '-'; + else + code[pos] = static_cast(std::toupper(code[pos])); + + pos++; + } + + return code; +} + +DiscRegion GetRegionForCode(std::string_view code) +{ + std::string prefix; + for (size_t pos = 0; pos < code.length(); pos++) + { + const int ch = std::tolower(code[pos]); + if (ch < 'a' || ch > 'z') + break; + + prefix.push_back(static_cast(ch)); + } + + if (prefix == "sces" || prefix == "sced" || prefix == "sles" || prefix == "sled") + return DiscRegion::PAL; + else if (prefix == "scps" || prefix == "slps" || prefix == "slpm" || prefix == "sczs" || prefix == "papx") + return DiscRegion::NTSC_J; + else if (prefix == "scus" || prefix == "slus") + return DiscRegion::NTSC_U; + else + return DiscRegion::Other; +} + +DiscRegion GetRegionFromSystemArea(CDImage* cdi) +{ + // The license code is on sector 4 of the disc. + u8 sector[CDImage::DATA_SECTOR_SIZE]; + if (!cdi->Seek(1, 4) || cdi->Read(CDImage::ReadMode::DataOnly, 1, sector) != 1) + return DiscRegion::Other; + + static constexpr char ntsc_u_string[] = " Licensed by Sony Computer Entertainment Amer ica "; + static constexpr char ntsc_j_string[] = " Licensed by Sony Computer Entertainment Inc."; + static constexpr char pal_string[] = " Licensed by Sony Computer Entertainment Euro pe"; + + // subtract one for the terminating null + if (std::equal(ntsc_u_string, ntsc_u_string + countof(ntsc_u_string) - 1, sector)) + return DiscRegion::NTSC_U; + else if (std::equal(ntsc_j_string, ntsc_j_string + countof(ntsc_j_string) - 1, sector)) + return DiscRegion::NTSC_J; + else if (std::equal(pal_string, pal_string + countof(pal_string) - 1, sector)) + return DiscRegion::PAL; + else + return DiscRegion::Other; +} + +DiscRegion GetRegionForImage(CDImage* cdi) +{ + DiscRegion system_area_region = GetRegionFromSystemArea(cdi); + if (system_area_region != DiscRegion::Other) + return system_area_region; + + std::string code = GetGameCodeForImage(cdi); + if (code.empty()) + return DiscRegion::Other; + + return GetRegionForCode(code); +} + +std::optional GetRegionForPath(const char* image_path) +{ + std::unique_ptr cdi = CDImage::Open(image_path); + if (!cdi) + return {}; + + return GetRegionForImage(cdi.get()); +} + bool RecreateGPU(GPURenderer renderer) { g_gpu->RestoreGraphicsAPIState(); @@ -298,8 +546,8 @@ bool Boot(const SystemBootParameters& params) bool psf_boot = false; if (!params.filename.empty()) { - exe_boot = GameList::IsExeFileName(params.filename.c_str()); - psf_boot = (!exe_boot && GameList::IsPsfFileName(params.filename.c_str())); + exe_boot = IsExeFileName(params.filename.c_str()); + psf_boot = (!exe_boot && IsPsfFileName(params.filename.c_str())); if (exe_boot || psf_boot) { // TODO: Pull region from PSF @@ -312,9 +560,9 @@ bool Boot(const SystemBootParameters& params) else { u32 playlist_index; - if (GameList::IsM3UFileName(params.filename.c_str())) + if (IsM3UFileName(params.filename.c_str())) { - s_media_playlist = GameList::ParseM3UFile(params.filename.c_str()); + s_media_playlist = ParseM3UFile(params.filename.c_str()); s_media_playlist_filename = params.filename; if (s_media_playlist.empty()) { @@ -351,7 +599,7 @@ bool Boot(const SystemBootParameters& params) if (s_region == ConsoleRegion::Auto) { - const DiscRegion disc_region = GameList::GetRegionForImage(media.get()); + const DiscRegion disc_region = GetRegionForImage(media.get()); if (disc_region != DiscRegion::Other) { s_region = GetConsoleRegionForDiscRegion(disc_region); @@ -690,7 +938,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer) return false; } - playlist_entries = GameList::ParseM3UFile(playlist_filename.c_str()); + playlist_entries = ParseM3UFile(playlist_filename.c_str()); if (playlist_entries.empty()) { g_host_interface->ReportFormattedError("Failed to load save state playlist entries from '%s'", @@ -1192,7 +1440,7 @@ void UpdateMemoryCards() { if (!s_media_playlist_filename.empty() && g_settings.memory_card_use_playlist_title) { - const std::string playlist_title(GameList::GetTitleForPath(s_media_playlist_filename.c_str())); + const std::string playlist_title(GetTitleForPath(s_media_playlist_filename.c_str())); card = MemoryCard::Open(g_host_interface->GetGameMemoryCardPath(playlist_title.c_str(), i)); } else if (s_running_game_title.empty()) diff --git a/src/core/system.h b/src/core/system.h index eee1f6973..a6fde2ac5 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -45,9 +45,29 @@ enum class State Paused }; +/// Returns true if the filename is a PlayStation executable we can inject. +bool IsExeFileName(const char* path); + +/// Returns true if the filename is a Portable Sound Format file we can uncompress/load. +bool IsPsfFileName(const char* path); + +/// Returns true if the filename is a M3U Playlist we can handle. +bool IsM3UFileName(const char* path); + +/// Parses an M3U playlist, returning the entries. +std::vector ParseM3UFile(const char* path); + /// Returns the preferred console type for a disc. ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region); +std::string GetGameCodeForImage(CDImage* cdi); +std::string GetGameCodeForPath(const char* image_path); +DiscRegion GetRegionForCode(std::string_view code); +DiscRegion GetRegionFromSystemArea(CDImage* cdi); +DiscRegion GetRegionForImage(CDImage* cdi); +std::optional GetRegionForPath(const char* image_path); +std::string_view GetTitleForPath(const char* path); + State GetState(); void SetState(State new_state); bool IsRunning(); diff --git a/src/core/timers.cpp b/src/core/timers.cpp index fc7a06396..a51b8bd7c 100644 --- a/src/core/timers.cpp +++ b/src/core/timers.cpp @@ -4,7 +4,9 @@ #include "gpu.h" #include "interrupt_controller.h" #include "system.h" -#include +#ifdef WITH_IMGUI +#include "imgui.h" +#endif Log_SetChannel(Timers); Timers g_timers; @@ -349,7 +351,7 @@ TickCount Timers::GetTicksUntilNextInterrupt() const min_ticks_for_this_timer = std::min(min_ticks_for_this_timer, static_cast(0xFFFF - cs.counter)); if (cs.external_counting_enabled) // sysclk/8 for timer 2 - min_ticks_for_this_timer = std::max(1, min_ticks_for_this_timer / 8); + min_ticks_for_this_timer = std::max(1, min_ticks_for_this_timer * 8); min_ticks = std::min(min_ticks, min_ticks_for_this_timer); } @@ -369,6 +371,7 @@ void Timers::UpdateSysClkEvent() void Timers::DrawDebugStateWindow() { +#ifdef WITH_IMGUI static constexpr u32 NUM_COLUMNS = 10; static constexpr std::array column_names = { {"#", "Value", "Target", "Sync", "Reset", "IRQ", "IRQRepeat", "IRQToggle", "Clock Source", "Reached"}}; @@ -437,4 +440,5 @@ void Timers::DrawDebugStateWindow() ImGui::Columns(1); ImGui::End(); +#endif } diff --git a/src/core/timing_event.cpp b/src/core/timing_event.cpp index 42f7edfc9..8197eff80 100644 --- a/src/core/timing_event.cpp +++ b/src/core/timing_event.cpp @@ -8,11 +8,12 @@ Log_SetChannel(TimingEvents); namespace TimingEvents { -static std::vector s_events; +static TimingEvent* s_active_events_head; +static TimingEvent* s_active_events_tail; +static TimingEvent* s_current_event = nullptr; +static u32 s_active_event_count = 0; static u32 s_global_tick_counter = 0; static u32 s_last_event_run_time = 0; -static bool s_running_events = false; -static bool s_events_need_sorting = false; u32 GetGlobalTickCounter() { @@ -32,7 +33,7 @@ void Reset() void Shutdown() { - Assert(s_events.empty()); + Assert(s_active_event_count == 0); } std::unique_ptr CreateTimingEvent(std::string name, TickCount period, TickCount interval, @@ -49,126 +50,249 @@ std::unique_ptr CreateTimingEvent(std::string name, TickCount perio void UpdateCPUDowncount() { if (!CPU::g_state.frame_done) - CPU::g_state.downcount = s_events[0]->GetDowncount(); + CPU::g_state.downcount = s_active_events_head->GetDowncount(); } -static bool CompareEvents(const TimingEvent* lhs, const TimingEvent* rhs) +static void SortEvent(TimingEvent* event) { - return lhs->GetDowncount() > rhs->GetDowncount(); + const TickCount event_downcount = event->m_downcount; + + if (event->prev && event->prev->m_downcount > event_downcount) + { + // move backwards + TimingEvent* current = event->prev; + while (current && current->m_downcount > event_downcount) + current = current->prev; + + // unlink + if (event->prev) + event->prev->next = event->next; + else + s_active_events_head = event->next; + if (event->next) + event->next->prev = event->prev; + else + s_active_events_tail = event->prev; + + // insert after current + if (current) + { + event->next = current->next; + if (current->next) + current->next->prev = event; + else + s_active_events_tail = event; + + event->prev = current; + current->next = event; + } + else + { + // insert at front + DebugAssert(s_active_events_head); + s_active_events_head->prev = event; + event->prev = nullptr; + event->next = s_active_events_head; + s_active_events_head = event; + UpdateCPUDowncount(); + } + } + else if (event->next && event_downcount > event->next->m_downcount) + { + // move forwards + TimingEvent* current = event->next; + while (current && event_downcount > current->m_downcount) + current = current->next; + + // unlink + if (event->prev) + event->prev->next = event->next; + else + s_active_events_head = event->next; + if (event->next) + event->next->prev = event->prev; + else + s_active_events_tail = event->prev; + + // insert before current + if (current) + { + event->next = current; + event->prev = current->prev; + + if (current->prev) + current->prev->next = event; + else + s_active_events_head = event; + + current->prev = event; + } + else + { + // insert at back + DebugAssert(s_active_events_tail); + s_active_events_tail->next = event; + event->next = nullptr; + event->prev = s_active_events_tail; + s_active_events_tail = event; + } + } } static void AddActiveEvent(TimingEvent* event) { - s_events.push_back(event); - if (!s_running_events) + DebugAssert(!event->prev && !event->next); + s_active_event_count++; + + TimingEvent* current = nullptr; + TimingEvent* next = s_active_events_head; + while (next && event->m_downcount > next->m_downcount) { - std::push_heap(s_events.begin(), s_events.end(), CompareEvents); + current = next; + next = next->next; + } + + if (!next) + { + // new tail + event->prev = s_active_events_tail; + if (s_active_events_tail) + { + s_active_events_tail->next = event; + s_active_events_tail = event; + } + else + { + // first event + s_active_events_tail = event; + s_active_events_head = event; + UpdateCPUDowncount(); + } + } + else if (!current) + { + // new head + event->next = s_active_events_head; + s_active_events_head->prev = event; + s_active_events_head = event; UpdateCPUDowncount(); } else { - s_events_need_sorting = true; + // inbetween current < event > next + event->prev = current; + event->next = next; + current->next = event; + next->prev = event; } } static void RemoveActiveEvent(TimingEvent* event) { - auto iter = std::find_if(s_events.begin(), s_events.end(), [event](const auto& it) { return event == it; }); - if (iter == s_events.end()) - { - Panic("Attempt to remove inactive event"); - return; - } + DebugAssert(s_active_event_count > 0); - s_events.erase(iter); - if (!s_running_events) + if (event->next) { - std::make_heap(s_events.begin(), s_events.end(), CompareEvents); - if (!s_events.empty()) - UpdateCPUDowncount(); + event->next->prev = event->prev; } else { - s_events_need_sorting = true; + s_active_events_tail = event->prev; } -} -static TimingEvent* FindActiveEvent(const char* name) -{ - auto iter = - std::find_if(s_events.begin(), s_events.end(), [&name](auto& ev) { return ev->GetName().compare(name) == 0; }); + if (event->prev) + { + event->prev->next = event->next; + } + else + { + s_active_events_head = event->next; + if (s_active_events_head) + UpdateCPUDowncount(); + } - return (iter != s_events.end()) ? *iter : nullptr; + event->prev = nullptr; + event->next = nullptr; + + s_active_event_count--; } static void SortEvents() { - if (!s_running_events) + std::vector events; + events.reserve(s_active_event_count); + + TimingEvent* next = s_active_events_head; + while (next) { - std::make_heap(s_events.begin(), s_events.end(), CompareEvents); - UpdateCPUDowncount(); + TimingEvent* current = next; + events.push_back(current); + next = current->next; + current->prev = nullptr; + current->next = nullptr; } - else + + s_active_events_head = nullptr; + s_active_events_tail = nullptr; + s_active_event_count = 0; + + for (TimingEvent* event : events) + AddActiveEvent(event); +} + +static TimingEvent* FindActiveEvent(const char* name) +{ + for (TimingEvent* event = s_active_events_head; event; event = event->next) { - s_events_need_sorting = true; + if (event->GetName().compare(name) == 0) + return event; } + + return nullptr; } void RunEvents() { - DebugAssert(!s_running_events && !s_events.empty()); - - s_running_events = true; + DebugAssert(!s_current_event); TickCount pending_ticks = (s_global_tick_counter + CPU::GetPendingTicks()) - s_last_event_run_time; CPU::ResetPendingTicks(); while (pending_ticks > 0) { - const TickCount time = std::min(pending_ticks, s_events[0]->GetDowncount()); + const TickCount time = std::min(pending_ticks, s_active_events_head->GetDowncount()); s_global_tick_counter += static_cast(time); pending_ticks -= time; // Apply downcount to all events. // This will result in a negative downcount for those events which are late. - for (TimingEvent* evt : s_events) + for (TimingEvent* event = s_active_events_head; event; event = event->next) { - evt->m_downcount -= time; - evt->m_time_since_last_run += time; + event->m_downcount -= time; + event->m_time_since_last_run += time; } // Now we can actually run the callbacks. - while (s_events.front()->m_downcount <= 0) + while (s_active_events_head->m_downcount <= 0) { - TimingEvent* evt = s_events.front(); - std::pop_heap(s_events.begin(), s_events.end(), CompareEvents); + // move it to the end, since that'll likely be its new position + TimingEvent* event = s_active_events_head; + s_current_event = event; // Factor late time into the time for the next invocation. - const TickCount ticks_late = -evt->m_downcount; - const TickCount ticks_to_execute = evt->m_time_since_last_run; - evt->m_downcount += evt->m_interval; - evt->m_time_since_last_run = 0; + const TickCount ticks_late = -event->m_downcount; + const TickCount ticks_to_execute = event->m_time_since_last_run; + event->m_downcount += event->m_interval; + event->m_time_since_last_run = 0; // The cycles_late is only an indicator, it doesn't modify the cycles to execute. - evt->m_callback(ticks_to_execute, ticks_late); - - // Place it in the appropriate position in the queue. - if (s_events_need_sorting) - { - // Another event may have been changed by this event, or the interval/downcount changed. - std::make_heap(s_events.begin(), s_events.end(), CompareEvents); - s_events_need_sorting = false; - } - else - { - // Keep the event list in a heap. The event we just serviced will be in the last place, - // so we can use push_here instead of make_heap, which should be faster. - std::push_heap(s_events.begin(), s_events.end(), CompareEvents); - } + event->m_callback(ticks_to_execute, ticks_late); + if (event->m_active) + SortEvent(event); } } s_last_event_run_time = s_global_tick_counter; - s_running_events = false; + s_current_event = nullptr; UpdateCPUDowncount(); } @@ -216,21 +340,21 @@ bool DoState(StateWrapper& sw) } else { - u32 event_count = static_cast(s_events.size()); - sw.Do(&event_count); - for (TimingEvent* evt : s_events) + sw.Do(&s_active_event_count); + + for (TimingEvent* event = s_active_events_head; event; event = event->next) { - sw.Do(&evt->m_name); - sw.Do(&evt->m_downcount); - sw.Do(&evt->m_time_since_last_run); - sw.Do(&evt->m_period); - sw.Do(&evt->m_interval); + sw.Do(&event->m_name); + sw.Do(&event->m_downcount); + sw.Do(&event->m_time_since_last_run); + sw.Do(&event->m_period); + sw.Do(&event->m_interval); } sw.Do(&s_last_event_run_time); - Log_DevPrintf("Wrote %u events to save state.", event_count); + Log_DevPrintf("Wrote %u events to save state.", s_active_event_count); } return !sw.HasError(); @@ -276,7 +400,8 @@ void TimingEvent::Schedule(TickCount ticks) { // Event is already active, so we leave the time since last run alone, and just modify the downcount. // If this is a call from an IO handler for example, re-sort the event queue. - TimingEvents::SortEvents(); + if (TimingEvents::s_current_event != this) + TimingEvents::SortEvent(this); } } @@ -300,7 +425,8 @@ void TimingEvent::Reset() m_downcount = m_interval; m_time_since_last_run = 0; - TimingEvents::SortEvents(); + if (TimingEvents::s_current_event != this) + TimingEvents::SortEvent(this); } void TimingEvent::InvokeEarly(bool force /* = false */) @@ -318,7 +444,8 @@ void TimingEvent::InvokeEarly(bool force /* = false */) m_callback(ticks_to_execute, 0); // Since we've changed the downcount, we need to re-sort the events. - TimingEvents::SortEvents(); + DebugAssert(TimingEvents::s_current_event != this); + TimingEvents::SortEvent(this); } void TimingEvent::Activate() diff --git a/src/core/timing_event.h b/src/core/timing_event.h index 0238b3402..ca58ddbdf 100644 --- a/src/core/timing_event.h +++ b/src/core/timing_event.h @@ -56,6 +56,9 @@ public: void SetInterval(TickCount interval) { m_interval = interval; } void SetPeriod(TickCount period) { m_period = period; } + TimingEvent* prev = nullptr; + TimingEvent* next = nullptr; + TickCount m_downcount; TickCount m_time_since_last_run; TickCount m_period; diff --git a/src/duckstation-libretro/CMakeLists.txt b/src/duckstation-libretro/CMakeLists.txt index 804a7ba21..9638e35ee 100644 --- a/src/duckstation-libretro/CMakeLists.txt +++ b/src/duckstation-libretro/CMakeLists.txt @@ -21,7 +21,7 @@ if(WIN32) ) endif() -target_link_libraries(duckstation_libretro PRIVATE core common imgui glad scmversion frontend-common vulkan-loader libretro-common) +target_link_libraries(duckstation_libretro PRIVATE core common glad scmversion frontend-common vulkan-loader libretro-common) # no lib prefix set_target_properties(duckstation_libretro PROPERTIES PREFIX "") diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index a8fc3660b..09feaa9c5 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -7,7 +7,6 @@ #include "core/analog_controller.h" #include "core/bus.h" #include "core/digital_controller.h" -#include "core/game_list.h" #include "core/gpu.h" #include "core/system.h" #include "libretro_audio_stream.h" @@ -132,7 +131,7 @@ bool LibretroHostInterface::ConfirmMessage(const char* message) void LibretroHostInterface::GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) { // Just use the filename for now... we don't have the game list. Unless we can pull this from the frontend somehow? - *title = GameList::GetTitleForPath(path); + *title = System::GetTitleForPath(path); code->clear(); } @@ -370,7 +369,7 @@ void LibretroHostInterface::OnSystemDestroyed() m_using_hardware_renderer = false; } -static std::array s_option_definitions = {{ +static std::array s_option_definitions = {{ {"duckstation_Console.Region", "Console Region", "Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.", @@ -406,6 +405,12 @@ static std::array s_option_definitions = {{ "Which mode to use for CPU emulation. Recompiler provides the best performance.", {{"Interpreter", "Interpreter"}, {"CachedIntepreter", "Cached Interpreter"}, {"Recompiler", "Recompiler"}}, "Recompiler"}, + {"duckstation_CPU.RecompilerICache", + "CPU Recompiler ICache", + "Determines whether the CPU's instruction cache is simulated in the recompiler. Improves accuracy at a small cost " + "to performance. If games are running too fast, try enabling this option.", + {{"true", "Enabled"}, {"false", "Disabled"}}, + "false"}, {"duckstation_GPU.Renderer", "GPU Renderer", "Which renderer to use to emulate the GPU", @@ -1152,7 +1157,7 @@ bool LibretroHostInterface::DiskControlGetImageLabel(unsigned index, char* label if (image_path.empty()) return false; - const std::string_view title = GameList::GetTitleForPath(label); + const std::string_view title = System::GetTitleForPath(label); StringUtil::Strlcpy(label, title, len); Log_DevPrintf("DiskControlGetImagePath(%u) -> %s", index, label); return true; diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index 3efd34db4..5a5d186ce 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -105,3 +105,63 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf.win" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qt.conf" ) endif() + +if(APPLE) + include(BundleUtilities) + set(BUNDLE_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DuckStation.app) + + # Ask for an application bundle. + set_target_properties(duckstation-qt PROPERTIES + MACOSX_BUNDLE true + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in + OUTPUT_NAME DuckStation + ) + + # Copy qt.conf into the bundle + target_sources(duckstation-qt PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf") + set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/qt.conf" PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + # Copy icon into the bundle + target_sources(duckstation-qt PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/DuckStation.icns") + set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/DuckStation.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + # Copy Qt plugins into the bundle + get_target_property(qtcocoa_location Qt5::QCocoaIntegrationPlugin LOCATION) + target_sources(duckstation-qt PRIVATE "${qtcocoa_location}") + set_source_files_properties("${qtcocoa_location}" PROPERTIES MACOSX_PACKAGE_LOCATION MacOS/platforms) + + get_target_property(qtmacstyle_location Qt5::QMacStylePlugin LOCATION) + target_sources(duckstation-qt PRIVATE "${qtmacstyle_location}") + set_source_files_properties("${qtmacstyle_location}" PROPERTIES MACOSX_PACKAGE_LOCATION MacOS/styles) + + # Copy resources into the bundle + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/data") + file(GLOB_RECURSE resources RELATIVE "${CMAKE_SOURCE_DIR}/data" "${CMAKE_SOURCE_DIR}/data/*") + foreach(res ${resources}) + target_sources(duckstation-qt PRIVATE "${CMAKE_SOURCE_DIR}/data/${res}") + get_filename_component(resdir "${res}" DIRECTORY) + set_source_files_properties("${CMAKE_SOURCE_DIR}/data/${res}" PROPERTIES + MACOSX_PACKAGE_LOCATION "MacOS/${resdir}") + source_group("Resources" FILES "${CMAKE_SOURCE_DIR}/data/${res}") + endforeach() + + # Copy translations into the bundle + add_custom_command(TARGET duckstation-qt + POST_BUILD + COMMAND mkdir $/translations + COMMAND cp ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/translations/*.qm $/translations) + + # Copy MoltenVK into the bundle + target_sources(duckstation-qt PRIVATE "${CMAKE_SOURCE_DIR}/dep/mac/MoltenVK/libvulkan.dylib") + set_source_files_properties("${CMAKE_SOURCE_DIR}/dep/mac/MoltenVK/libvulkan.dylib" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks) + + # Update library references to make the bundle portable + include(DolphinPostprocessBundle) + dolphin_postprocess_bundle(duckstation-qt) + # Fix rpath + add_custom_command(TARGET duckstation-qt + POST_BUILD COMMAND + ${CMAKE_INSTALL_NAME_TOOL} -add_rpath "@executable_path/../Frameworks/" + $) +endif() + diff --git a/src/duckstation-qt/DuckStation.icns b/src/duckstation-qt/DuckStation.icns new file mode 100644 index 000000000..fe463b16e Binary files /dev/null and b/src/duckstation-qt/DuckStation.icns differ diff --git a/src/duckstation-qt/Info.plist.in b/src/duckstation-qt/Info.plist.in new file mode 100644 index 000000000..572ee0d29 --- /dev/null +++ b/src/duckstation-qt/Info.plist.in @@ -0,0 +1,45 @@ + + + + + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + bin + cue + img + chd + m3u + psexe + psf + + CFBundleTypeIconFile + DuckStation.icns + CFBundleTypeName + PlayStation File + CFBundleTypeRole + Viewer + + + CFBundleExecutable + DuckStation + CFBundleIconFile + DuckStation.icns + CFBundleIdentifier + com.github.stenzek.duckstation + CFBundleDevelopmentRegion + English + CFBundlePackageType + APPL + NSHumanReadableCopyright + Licensed under GPL version 3 + LSMinimumSystemVersion + ${CMAKE_OSX_DEPLOYMENT_TARGET} + NSHighResolutionCapable + + CSResourcesFileMapped + + + diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp index 2b4d93a32..837485555 100644 --- a/src/duckstation-qt/advancedsettingswidget.cpp +++ b/src/duckstation-qt/advancedsettingswidget.cpp @@ -27,6 +27,8 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface, SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuMaxRunAhead, "Hacks", "GPUMaxRunAhead"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU", "RecompilerMemoryExceptions", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerICache, "CPU", "RecompilerICache", + false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showDebugMenu, "Main", "ShowDebugMenu"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuUseDebugDevice, "GPU", "UseDebugDevice"); @@ -38,6 +40,10 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface, dialog->registerWidgetHelp(m_ui.gpuUseDebugDevice, tr("Use Debug Host GPU Device"), tr("Unchecked"), tr("Enables the usage of debug devices and shaders for rendering APIs which support them. " "Should only be used when debugging the emulator.")); + dialog->registerWidgetHelp( + m_ui.cpuRecompilerICache, tr("Enable Recompiler ICache"), tr("Unchecked"), + tr("Determines whether the CPU's instruction cache is simulated in the recompiler. Improves accuracy at a small " + "cost to performance. If games are running too fast, try enabling this option.")); } AdvancedSettingsWidget::~AdvancedSettingsWidget() = default; diff --git a/src/duckstation-qt/advancedsettingswidget.ui b/src/duckstation-qt/advancedsettingswidget.ui index d1ba75000..d75529513 100644 --- a/src/duckstation-qt/advancedsettingswidget.ui +++ b/src/duckstation-qt/advancedsettingswidget.ui @@ -184,6 +184,20 @@ + + + + Enable Recompiler Memory Exceptions + + + + + + + Enable Recompiler ICache + + + @@ -191,13 +205,6 @@ - - - - Enable Recompiler Memory Exceptions - - - diff --git a/src/duckstation-qt/controllersettingswidget.cpp b/src/duckstation-qt/controllersettingswidget.cpp index c7c5d7914..a66014163 100644 --- a/src/duckstation-qt/controllersettingswidget.cpp +++ b/src/duckstation-qt/controllersettingswidget.cpp @@ -174,6 +174,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin QGridLayout* layout = new QGridLayout(ui->bindings_container); const auto buttons = Controller::GetButtonNames(ctype); + const char* cname = Settings::GetControllerTypeName(ctype); InputBindingWidget* first_button = nullptr; InputBindingWidget* last_button = nullptr; @@ -196,7 +197,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1); std::string key_name = StringUtil::StdStringFromFormat("Button%s", button_name.c_str()); - QLabel* label = new QLabel(QString::fromStdString(button_name), ui->bindings_container); + QLabel* label = new QLabel(qApp->translate(cname, button_name.c_str()), ui->bindings_container); InputButtonBindingWidget* button = new InputButtonBindingWidget(m_host_interface, std::move(section_name), std::move(key_name), ui->bindings_container); layout->addWidget(label, start_row + current_row, current_column); @@ -223,7 +224,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin const int num_rows = (static_cast(axises.size()) + 1) / 2; int current_row = 0; int current_column = 0; - for (const auto& [axis_name, axis_code] : axises) + for (const auto& [axis_name, axis_code, axis_type] : axises) { if (current_row == num_rows) { @@ -233,9 +234,9 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1); std::string key_name = StringUtil::StdStringFromFormat("Axis%s", axis_name.c_str()); - QLabel* label = new QLabel(QString::fromStdString(axis_name), ui->bindings_container); - InputAxisBindingWidget* button = new InputAxisBindingWidget(m_host_interface, std::move(section_name), - std::move(key_name), ui->bindings_container); + QLabel* label = new QLabel(qApp->translate(cname, axis_name.c_str()), ui->bindings_container); + InputAxisBindingWidget* button = new InputAxisBindingWidget( + m_host_interface, std::move(section_name), std::move(key_name), axis_type, ui->bindings_container); layout->addWidget(label, start_row + current_row, current_column); layout->addWidget(button, start_row + current_row, current_column + 1); @@ -282,13 +283,13 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin { std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1); std::string key_name = si.key; - const QString setting_tooltip = si.description ? QString::fromUtf8(si.description) : ""; + const QString setting_tooltip = si.description ? qApp->translate(cname, si.description) : QString(); switch (si.type) { case SettingInfo::Type::Boolean: { - QCheckBox* cb = new QCheckBox(tr(si.visible_name), ui->bindings_container); + QCheckBox* cb = new QCheckBox(qApp->translate(cname, si.visible_name), ui->bindings_container); cb->setToolTip(setting_tooltip); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, cb, std::move(section_name), std::move(key_name), si.BooleanDefaultValue()); @@ -306,7 +307,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin sb->setSingleStep(si.IntegerStepValue()); SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, sb, std::move(section_name), std::move(key_name), si.IntegerDefaultValue()); - layout->addWidget(new QLabel(tr(si.visible_name), ui->bindings_container), start_row, 0); + layout->addWidget(new QLabel(qApp->translate(cname, si.visible_name), ui->bindings_container), start_row, 0); layout->addWidget(sb, start_row, 1, 1, 3); start_row++; } @@ -321,7 +322,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin sb->setSingleStep(si.FloatStepValue()); SettingWidgetBinder::BindWidgetToFloatSetting(m_host_interface, sb, std::move(section_name), std::move(key_name), si.FloatDefaultValue()); - layout->addWidget(new QLabel(tr(si.visible_name), ui->bindings_container), start_row, 0); + layout->addWidget(new QLabel(qApp->translate(cname, si.visible_name), ui->bindings_container), start_row, 0); layout->addWidget(sb, start_row, 1, 1, 3); start_row++; } @@ -333,7 +334,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin le->setToolTip(setting_tooltip); SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, le, std::move(section_name), std::move(key_name), si.StringDefaultValue()); - layout->addWidget(new QLabel(tr(si.visible_name), ui->bindings_container), start_row, 0); + layout->addWidget(new QLabel(qApp->translate(cname, si.visible_name), ui->bindings_container), start_row, 0); layout->addWidget(le, start_row, 1, 1, 3); start_row++; } @@ -356,7 +357,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin hbox->addWidget(le, 1); hbox->addWidget(browse_button); - layout->addWidget(new QLabel(tr(si.visible_name), ui->bindings_container), start_row, 0); + layout->addWidget(new QLabel(qApp->translate(cname, si.visible_name), ui->bindings_container), start_row, 0); layout->addLayout(hbox, start_row, 1, 1, 3); start_row++; } diff --git a/src/duckstation-qt/duckstation-qt.ico b/src/duckstation-qt/duckstation-qt.ico index 082a9d8b8..f95b67991 100644 Binary files a/src/duckstation-qt/duckstation-qt.ico and b/src/duckstation-qt/duckstation-qt.ico differ diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp index c3e422bb8..62dc163ba 100644 --- a/src/duckstation-qt/gamelistmodel.cpp +++ b/src/duckstation-qt/gamelistmodel.cpp @@ -1,5 +1,6 @@ #include "gamelistmodel.h" #include "common/string_util.h" +#include "core/system.h" static constexpr std::array s_column_names = { {"Type", "Code", "Title", "File Title", "Size", "Region", "Compatibility"}}; @@ -69,7 +70,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const case Column_FileTitle: { - const std::string_view file_title(GameList::GetTitleForPath(ge.path.c_str())); + const std::string_view file_title(System::GetTitleForPath(ge.path.c_str())); return QString::fromUtf8(file_title.data(), static_cast(file_title.length())); } @@ -96,7 +97,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const case Column_FileTitle: { - const std::string_view file_title(GameList::GetTitleForPath(ge.path.c_str())); + const std::string_view file_title(System::GetTitleForPath(ge.path.c_str())); return QString::fromUtf8(file_title.data(), static_cast(file_title.length())); } @@ -187,7 +188,8 @@ bool GameListModel::titlesLessThan(int left_row, int right_row, bool ascending) const GameListEntry& left = m_game_list->GetEntries().at(left_row); const GameListEntry& right = m_game_list->GetEntries().at(right_row); - return ascending ? (left.title < right.title) : (right.title < left.title); + return ascending ? (StringUtil::Strcasecmp(left.title.c_str(), right.title.c_str()) < 0) : + (StringUtil::Strcasecmp(right.title.c_str(), left.title.c_str()) > 0); } bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column, @@ -221,25 +223,25 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r { if (left.code == right.code) return titlesLessThan(left_row, right_row, ascending); - return ascending ? (left.code < right.code) : (right.code > left.code); + return ascending ? (StringUtil::Strcasecmp(left.code.c_str(), right.code.c_str()) < 0) : + (StringUtil::Strcasecmp(right.code.c_str(), left.code.c_str()) > 0); } case Column_Title: { - if (left.title == right.title) - return titlesLessThan(left_row, right_row, ascending); - - return ascending ? (left.title < right.title) : (right.title > left.title); + return titlesLessThan(left_row, right_row, ascending); } case Column_FileTitle: { - const std::string_view file_title_left(GameList::GetTitleForPath(left.path.c_str())); - const std::string_view file_title_right(GameList::GetTitleForPath(right.path.c_str())); + const std::string_view file_title_left(System::GetTitleForPath(left.path.c_str())); + const std::string_view file_title_right(System::GetTitleForPath(right.path.c_str())); if (file_title_left == file_title_right) return titlesLessThan(left_row, right_row, ascending); - return ascending ? (file_title_left < file_title_right) : (file_title_right > file_title_left); + const std::size_t smallest = std::min(file_title_left.size(), file_title_right.size()); + return ascending ? (StringUtil::Strncasecmp(file_title_left.data(), file_title_right.data(), smallest) < 0) : + (StringUtil::Strncasecmp(file_title_right.data(), file_title_left.data(), smallest) > 0); } case Column_Region: diff --git a/src/duckstation-qt/gamelistmodel.h b/src/duckstation-qt/gamelistmodel.h index 547555951..edf04892e 100644 --- a/src/duckstation-qt/gamelistmodel.h +++ b/src/duckstation-qt/gamelistmodel.h @@ -1,6 +1,6 @@ #pragma once -#include "core/game_list.h" #include "core/types.h" +#include "frontend-common/game_list.h" #include #include #include diff --git a/src/duckstation-qt/gamelistsettingswidget.cpp b/src/duckstation-qt/gamelistsettingswidget.cpp index c298d3527..268e2490f 100644 --- a/src/duckstation-qt/gamelistsettingswidget.cpp +++ b/src/duckstation-qt/gamelistsettingswidget.cpp @@ -2,7 +2,7 @@ #include "common/assert.h" #include "common/minizip_helpers.h" #include "common/string_util.h" -#include "core/game_list.h" +#include "frontend-common/game_list.h" #include "gamelistsearchdirectoriesmodel.h" #include "qthostinterface.h" #include "qtutils.h" diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index e06aa3f7f..2f4325dcd 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -1,7 +1,7 @@ #include "gamelistwidget.h" #include "common/string_util.h" -#include "core/game_list.h" #include "core/settings.h" +#include "frontend-common/game_list.h" #include "gamelistmodel.h" #include "qthostinterface.h" #include "qtutils.h" diff --git a/src/duckstation-qt/gamepropertiesdialog.cpp b/src/duckstation-qt/gamepropertiesdialog.cpp index f72fe4bed..b595ac76a 100644 --- a/src/duckstation-qt/gamepropertiesdialog.cpp +++ b/src/duckstation-qt/gamepropertiesdialog.cpp @@ -1,17 +1,21 @@ #include "gamepropertiesdialog.h" #include "common/cd_image.h" #include "common/cd_image_hasher.h" -#include "core/game_list.h" #include "core/settings.h" +#include "frontend-common/game_list.h" #include "qthostinterface.h" #include "qtprogresscallback.h" #include "qtutils.h" #include "scmversion/scmversion.h" #include #include +#include #include #include +static constexpr char MEMORY_CARD_IMAGE_FILTER[] = + QT_TRANSLATE_NOOP("MemoryCardSettingsWidget", "All Memory Card Types (*.mcd *.mcr *.mc)"); + GamePropertiesDialog::GamePropertiesDialog(QtHostInterface* host_interface, QWidget* parent /* = nullptr */) : QDialog(parent), m_host_interface(host_interface) { @@ -127,13 +131,15 @@ void GamePropertiesDialog::setupAdditionalUi() qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast(i)))); } + m_ui.userResolutionScale->addItem(tr("(unchanged)")); + QtUtils::FillComboBoxWithResolutionScales(m_ui.userResolutionScale); + m_ui.userControllerType1->addItem(tr("(unchanged)")); for (u32 i = 0; i < static_cast(ControllerType::Count); i++) { m_ui.userControllerType1->addItem( qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast(i)))); } - m_ui.userControllerType2->addItem(tr("(unchanged)")); for (u32 i = 0; i < static_cast(ControllerType::Count); i++) { @@ -141,6 +147,19 @@ void GamePropertiesDialog::setupAdditionalUi() qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast(i)))); } + m_ui.userMemoryCard1Type->addItem(tr("(unchanged)")); + for (u32 i = 0; i < static_cast(MemoryCardType::Count); i++) + { + m_ui.userMemoryCard1Type->addItem( + qApp->translate("MemoryCardType", Settings::GetMemoryCardTypeDisplayName(static_cast(i)))); + } + m_ui.userMemoryCard2Type->addItem(tr("(unchanged)")); + for (u32 i = 0; i < static_cast(MemoryCardType::Count); i++) + { + m_ui.userMemoryCard2Type->addItem( + qApp->translate("MemoryCardType", Settings::GetMemoryCardTypeDisplayName(static_cast(i)))); + } + QGridLayout* traits_layout = new QGridLayout(m_ui.compatibilityTraits); for (u32 i = 0; i < static_cast(GameSettings::Trait::Count); i++) { @@ -153,9 +172,9 @@ void GamePropertiesDialog::setupAdditionalUi() setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); } -void GamePropertiesDialog::showForEntry(QtHostInterface* host_interface, const GameListEntry* ge) +void GamePropertiesDialog::showForEntry(QtHostInterface* host_interface, const GameListEntry* ge, QWidget* parent) { - GamePropertiesDialog* gpd = new GamePropertiesDialog(host_interface); + GamePropertiesDialog* gpd = new GamePropertiesDialog(host_interface, parent); gpd->populate(ge); gpd->show(); gpd->onResize(); @@ -198,6 +217,26 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path) } } +void GamePropertiesDialog::populateBooleanUserSetting(QCheckBox* cb, const std::optional& value) +{ + QSignalBlocker sb(cb); + if (value.has_value()) + cb->setCheckState(value.value() ? Qt::Checked : Qt::Unchecked); + else + cb->setCheckState(Qt::PartiallyChecked); +} + +void GamePropertiesDialog::connectBooleanUserSetting(QCheckBox* cb, std::optional* value) +{ + connect(cb, &QCheckBox::stateChanged, [this, value](int state) { + if (state == Qt::PartiallyChecked) + value->reset(); + else + *value = (state == Qt::Checked); + saveGameSettings(); + }); +} + void GamePropertiesDialog::populateGameSettings() { const GameSettings::Entry& gs = m_game_settings; @@ -230,6 +269,27 @@ void GamePropertiesDialog::populateGameSettings() m_ui.userAspectRatio->setCurrentIndex(static_cast(gs.display_aspect_ratio.value()) + 1); } + populateBooleanUserSetting(m_ui.userLinearUpscaling, gs.display_linear_upscaling); + populateBooleanUserSetting(m_ui.userIntegerUpscaling, gs.display_integer_upscaling); + + if (gs.gpu_resolution_scale.has_value()) + { + QSignalBlocker sb(m_ui.userResolutionScale); + m_ui.userResolutionScale->setCurrentIndex(static_cast(gs.gpu_resolution_scale.value()) + 1); + } + else + { + QSignalBlocker sb(m_ui.userResolutionScale); + m_ui.userResolutionScale->setCurrentIndex(0); + } + + populateBooleanUserSetting(m_ui.userTrueColor, gs.gpu_true_color); + populateBooleanUserSetting(m_ui.userScaledDithering, gs.gpu_scaled_dithering); + populateBooleanUserSetting(m_ui.userBilinearTextureFiltering, gs.gpu_bilinear_texture_filtering); + populateBooleanUserSetting(m_ui.userForceNTSCTimings, gs.gpu_force_ntsc_timings); + populateBooleanUserSetting(m_ui.userWidescreenHack, gs.gpu_widescreen_hack); + populateBooleanUserSetting(m_ui.userPGXP, gs.gpu_pgxp); + if (gs.controller_1_type.has_value()) { QSignalBlocker sb(m_ui.userControllerType1); @@ -240,15 +300,26 @@ void GamePropertiesDialog::populateGameSettings() QSignalBlocker sb(m_ui.userControllerType2); m_ui.userControllerType2->setCurrentIndex(static_cast(gs.controller_2_type.value()) + 1); } - if (gs.gpu_widescreen_hack.has_value()) + + if (gs.memory_card_1_type.has_value()) { - QSignalBlocker sb(m_ui.userControllerType2); - m_ui.userWidescreenHack->setCheckState(Qt::Checked); + QSignalBlocker sb(m_ui.userMemoryCard1Type); + m_ui.userMemoryCard1Type->setCurrentIndex(static_cast(gs.memory_card_1_type.value()) + 1); } - else + if (gs.memory_card_2_type.has_value()) { - QSignalBlocker sb(m_ui.userControllerType2); - m_ui.userWidescreenHack->setCheckState(Qt::PartiallyChecked); + QSignalBlocker sb(m_ui.userMemoryCard2Type); + m_ui.userMemoryCard2Type->setCurrentIndex(static_cast(gs.memory_card_2_type.value()) + 1); + } + if (!gs.memory_card_1_shared_path.empty()) + { + QSignalBlocker sb(m_ui.userMemoryCard1SharedPath); + m_ui.userMemoryCard1SharedPath->setText(QString::fromStdString(gs.memory_card_1_shared_path)); + } + if (!gs.memory_card_2_shared_path.empty()) + { + QSignalBlocker sb(m_ui.userMemoryCard2SharedPath); + m_ui.userMemoryCard2SharedPath->setText(QString::fromStdString(gs.memory_card_2_shared_path)); } } @@ -306,6 +377,24 @@ void GamePropertiesDialog::connectUi() saveGameSettings(); }); + connectBooleanUserSetting(m_ui.userLinearUpscaling, &m_game_settings.display_linear_upscaling); + connectBooleanUserSetting(m_ui.userIntegerUpscaling, &m_game_settings.display_integer_upscaling); + + connect(m_ui.userResolutionScale, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.gpu_resolution_scale.reset(); + else + m_game_settings.gpu_resolution_scale = static_cast(index - 1); + saveGameSettings(); + }); + + connectBooleanUserSetting(m_ui.userTrueColor, &m_game_settings.gpu_true_color); + connectBooleanUserSetting(m_ui.userScaledDithering, &m_game_settings.gpu_scaled_dithering); + connectBooleanUserSetting(m_ui.userForceNTSCTimings, &m_game_settings.gpu_force_ntsc_timings); + connectBooleanUserSetting(m_ui.userBilinearTextureFiltering, &m_game_settings.gpu_bilinear_texture_filtering); + connectBooleanUserSetting(m_ui.userWidescreenHack, &m_game_settings.gpu_widescreen_hack); + connectBooleanUserSetting(m_ui.userPGXP, &m_game_settings.gpu_pgxp); + connect(m_ui.userControllerType1, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { if (index <= 0) m_game_settings.controller_1_type.reset(); @@ -322,13 +411,50 @@ void GamePropertiesDialog::connectUi() saveGameSettings(); }); - connect(m_ui.userWidescreenHack, &QCheckBox::stateChanged, [this](int state) { - if (state == Qt::PartiallyChecked) - m_game_settings.gpu_widescreen_hack.reset(); + connect(m_ui.userMemoryCard1Type, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.memory_card_1_type.reset(); else - m_game_settings.gpu_widescreen_hack = (state == Qt::Checked); + m_game_settings.memory_card_1_type = static_cast(index - 1); saveGameSettings(); }); + connect(m_ui.userMemoryCard1SharedPath, &QLineEdit::textChanged, [this](const QString& text) { + if (text.isEmpty()) + std::string().swap(m_game_settings.memory_card_1_shared_path); + else + m_game_settings.memory_card_1_shared_path = text.toStdString(); + saveGameSettings(); + }); + connect(m_ui.userMemoryCard1SharedPathBrowse, &QPushButton::clicked, [this]() { + QString path = QFileDialog::getOpenFileName(this, tr("Select path to memory card image"), QString(), + qApp->translate("MemoryCardSettingsWidget", MEMORY_CARD_IMAGE_FILTER)); + if (path.isEmpty()) + return; + + m_ui.userMemoryCard1SharedPath->setText(path); + }); + connect(m_ui.userMemoryCard2Type, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.memory_card_2_type.reset(); + else + m_game_settings.memory_card_2_type = static_cast(index - 1); + saveGameSettings(); + }); + connect(m_ui.userMemoryCard2SharedPath, &QLineEdit::textChanged, [this](const QString& text) { + if (text.isEmpty()) + std::string().swap(m_game_settings.memory_card_2_shared_path); + else + m_game_settings.memory_card_2_shared_path = text.toStdString(); + saveGameSettings(); + }); + connect(m_ui.userMemoryCard2SharedPathBrowse, &QPushButton::clicked, [this]() { + QString path = QFileDialog::getOpenFileName(this, tr("Select path to memory card image"), QString(), + qApp->translate("MemoryCardSettingsWidget", MEMORY_CARD_IMAGE_FILTER)); + if (path.isEmpty()) + return; + + m_ui.userMemoryCard2SharedPath->setText(path); + }); for (u32 i = 0; i < static_cast(GameSettings::Trait::Count); i++) { diff --git a/src/duckstation-qt/gamepropertiesdialog.h b/src/duckstation-qt/gamepropertiesdialog.h index fb2929bec..ed2e74aab 100644 --- a/src/duckstation-qt/gamepropertiesdialog.h +++ b/src/duckstation-qt/gamepropertiesdialog.h @@ -1,5 +1,5 @@ #pragma once -#include "core/game_settings.h" +#include "frontend-common/game_settings.h" #include "ui_gamepropertiesdialog.h" #include #include @@ -17,7 +17,7 @@ public: GamePropertiesDialog(QtHostInterface* host_interface, QWidget* parent = nullptr); ~GamePropertiesDialog(); - static void showForEntry(QtHostInterface* host_interface, const GameListEntry* ge); + static void showForEntry(QtHostInterface* host_interface, const GameListEntry* ge, QWidget* parent); public Q_SLOTS: void clear(); @@ -43,6 +43,8 @@ private: void populateCompatibilityInfo(const std::string& game_code); void populateTracksInfo(const std::string& image_path); void populateGameSettings(); + void populateBooleanUserSetting(QCheckBox* cb, const std::optional& value); + void connectBooleanUserSetting(QCheckBox* cb, std::optional* value); void saveGameSettings(); void fillEntryFromUi(GameListCompatibilityEntry* entry); void computeTrackHashes(); diff --git a/src/duckstation-qt/gamepropertiesdialog.ui b/src/duckstation-qt/gamepropertiesdialog.ui index e6a800ede..e6af3e8bf 100644 --- a/src/duckstation-qt/gamepropertiesdialog.ui +++ b/src/duckstation-qt/gamepropertiesdialog.ui @@ -7,7 +7,7 @@ 0 0 793 - 647 + 600 @@ -21,7 +21,7 @@ - 0 + 1 @@ -190,39 +190,132 @@ - GPU Settings + GPU Screen Display - - - Crop Mode: - - - - - - - Aspect Ratio: - + - - + + - Widescreen Hack + Crop Mode: + + + + + + + + + + Linear Upscaling true + + + + Integer Upscaling + + + true + + + + + + + + + + GPU Enhancements + + + + + + Resolution Scale: + + + + + + + + + + + + True Color Rendering (24-bit, disables dithering) + + + true + + + + + + + Scaled Dithering (scale dither pattern to resolution) + + + true + + + + + + + Widescreen Hack + + + true + + + + + + + Force NTSC Timings (60hz-on-PAL) + + + true + + + + + + + Bilinear Texture Filtering + + + true + + + + + + + PGXP Geometry Correction + + + true + + + + + @@ -256,7 +349,78 @@ - + + + Memory Card Settings + + + + + + Memory Card 1 Type: + + + + + + + + + + Memory Card 1 Shared Path: + + + + + + + + + + + + Browse... + + + + + + + + + Memory Card 2 Type: + + + + + + + + + + Memory Card 2 Shared Path: + + + + + + + + + + + + Browse... + + + + + + + + + + Qt::Vertical diff --git a/src/duckstation-qt/gpusettingswidget.cpp b/src/duckstation-qt/gpusettingswidget.cpp index 2f2f2919f..15964ad4f 100644 --- a/src/duckstation-qt/gpusettingswidget.cpp +++ b/src/duckstation-qt/gpusettingswidget.cpp @@ -1,6 +1,7 @@ #include "gpusettingswidget.h" #include "core/gpu.h" #include "core/settings.h" +#include "qtutils.h" #include "settingsdialog.h" #include "settingwidgetbinder.h" @@ -62,14 +63,15 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p dialog->registerWidgetHelp( m_ui.renderer, tr("Renderer"), Settings::GetRendererDisplayName(Settings::DEFAULT_GPU_RENDERER), - tr( - "Chooses the backend to use for rendering the console/game visuals.
Depending on your system and hardware, " - "Direct3D 11 and OpenGL hardware backends may be available.
The software renderer offers the best compatibility, " - "but is the slowest and does not offer any enhancements.")); + tr("Chooses the backend to use for rendering the console/game visuals.
Depending on your system and hardware, " + "Direct3D 11 and OpenGL hardware backends may be available.
The software renderer offers the best " + "compatibility, " + "but is the slowest and does not offer any enhancements.")); dialog->registerWidgetHelp( m_ui.adapter, tr("Adapter"), tr("(Default)"), tr("If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware " - "renderers.
This option is only supported in Direct3D and Vulkan. OpenGL will always use the default device.")); + "renderers.
This option is only supported in Direct3D and Vulkan. OpenGL will always use the default " + "device.")); dialog->registerWidgetHelp( m_ui.displayAspectRatio, tr("Aspect Ratio"), QStringLiteral("4:3"), tr("Changes the aspect ratio used to display the console's output to the screen. The default " @@ -82,14 +84,16 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p "compromise between stability and hiding black borders.")); dialog->registerWidgetHelp( m_ui.disableInterlacing, tr("Disable Interlacing (force progressive render/scan)"), tr("Unchecked"), - tr("Forces the rendering and display of frames to progressive mode.
This removes the \"combing\" effect seen in " - "480i games by rendering them in 480p. Usually safe to enable.
" - "May not be compatible with all games.")); - dialog->registerWidgetHelp( - m_ui.displayLinearFiltering, tr("Linear Upscaling"), tr("Checked"), - tr("Uses bilinear texture filtering when displaying the console's framebuffer to the screen.
Disabling filtering " - "will producer a sharper, blockier/pixelated image. Enabling will smooth out the image.
The option will be less " - "noticable the higher the resolution scale.")); + tr( + "Forces the rendering and display of frames to progressive mode.
This removes the \"combing\" effect seen in " + "480i games by rendering them in 480p. Usually safe to enable.
" + "May not be compatible with all games.")); + dialog->registerWidgetHelp(m_ui.displayLinearFiltering, tr("Linear Upscaling"), tr("Checked"), + tr("Uses bilinear texture filtering when displaying the console's framebuffer to the " + "screen.
Disabling filtering " + "will producer a sharper, blockier/pixelated image. Enabling will smooth out the " + "image.
The option will be less " + "noticable the higher the resolution scale.")); dialog->registerWidgetHelp( m_ui.displayIntegerScaling, tr("Integer Upscaling"), tr("Unchecked"), tr("Adds padding to the display area to ensure that the ratio between pixels on the host to " @@ -114,12 +118,12 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p m_ui.scaledDithering, tr("Scaled Dithering (scale dither pattern to resolution)"), tr("Checked"), tr("Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less " "obvious at higher resolutions.
Usually safe to enable, and only supported by the hardware renderers.")); - dialog->registerWidgetHelp( - m_ui.forceNTSCTimings, tr("Force NTSC Timings (60hz-on-PAL)"), tr("Unchecked"), - tr( - "Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz.
For most games which " - "have a speed tied to the framerate, this will result in the game running approximately 17% faster.
For variable " - "frame rate games, it may not affect the speed.")); + dialog->registerWidgetHelp(m_ui.forceNTSCTimings, tr("Force NTSC Timings (60hz-on-PAL)"), tr("Unchecked"), + tr("Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at " + "60hz.
For most games which " + "have a speed tied to the framerate, this will result in the game running " + "approximately 17% faster.
For variable " + "frame rate games, it may not affect the speed.")); dialog->registerWidgetHelp( m_ui.linearTextureFiltering, tr("Bilinear Texture Filtering"), tr("Unchecked"), tr("Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering.
Will have a " @@ -128,7 +132,8 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"), tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially " "increasing the field of view from 4:3 to 16:9 in 3D games.
For 2D games, or games which " - "use pre-rendered backgrounds, this enhancement will not work as expected.
May not be compatible with all games.")); + "use pre-rendered backgrounds, this enhancement will not work as expected.
May not be compatible with " + "all games.")); dialog->registerWidgetHelp( m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"), tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games.
Only " @@ -178,29 +183,7 @@ void GPUSettingsWidget::setupAdditionalUi() qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast(i)))); } - std::array resolution_suffixes = {{ - QString(), // auto - QString(), // 1x - QString(), // 2x - tr(" (for 720p)"), // 3x - QString(), // 4x - tr(" (for 1080p)"), // 5x - tr(" (for 1440p)"), // 6x - QString(), // 7x - QString(), // 8x - tr(" (for 4K)"), // 9x - QString(), // 10x - QString(), // 11x - QString(), // 12x - QString(), // 13x - QString(), // 14x - QString(), // 15x - QString() // 16x - }}; - - m_ui.resolutionScale->addItem(tr("Automatic based on window size")); - for (u32 i = 1; i <= GPU::MAX_RESOLUTION_SCALE; i++) - m_ui.resolutionScale->addItem(tr("%1x%2").arg(i).arg(resolution_suffixes[i])); + QtUtils::FillComboBoxWithResolutionScales(m_ui.resolutionScale); } void GPUSettingsWidget::populateGPUAdapters() diff --git a/src/duckstation-qt/inputbindingdialog.cpp b/src/duckstation-qt/inputbindingdialog.cpp index 56b261152..0edded305 100644 --- a/src/duckstation-qt/inputbindingdialog.cpp +++ b/src/duckstation-qt/inputbindingdialog.cpp @@ -36,8 +36,32 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event) { const QEvent::Type event_type = event->type(); - if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || - event_type == QEvent::MouseButtonDblClick) + // if the key is being released, set the input + if (event_type == QEvent::KeyRelease) + { + addNewBinding(std::move(m_new_binding_value)); + stopListeningForInput(); + return true; + } + else if (event_type == QEvent::KeyPress) + { + QString binding = QtUtils::KeyEventToString(static_cast(event)); + if (!binding.isEmpty()) + m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString(); + + return true; + } + else if (event_type == QEvent::MouseButtonRelease) + { + const u32 button_mask = static_cast(static_cast(event)->button()); + const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask); + m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1); + addNewBinding(std::move(m_new_binding_value)); + stopListeningForInput(); + return true; + } + + if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick) { return true; } @@ -103,6 +127,27 @@ void InputBindingDialog::addNewBinding(std::string new_binding) saveListToSettings(); } +void InputBindingDialog::bindToControllerAxis(int controller_index, int axis_index, std::optional positive) +{ + const char* sign_char = ""; + if (positive) + { + sign_char = *positive ? "+" : "-"; + } + + std::string binding = + StringUtil::StdStringFromFormat("Controller%d/%sAxis%d", controller_index, sign_char, axis_index); + addNewBinding(std::move(binding)); + stopListeningForInput(); +} + +void InputBindingDialog::bindToControllerButton(int controller_index, int button_index) +{ + std::string binding = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index); + addNewBinding(std::move(binding)); + stopListeningForInput(); +} + void InputBindingDialog::onAddBindingButtonClicked() { if (isListeningForInput()) @@ -159,38 +204,6 @@ InputButtonBindingDialog::~InputButtonBindingDialog() InputButtonBindingDialog::stopListeningForInput(); } -bool InputButtonBindingDialog::eventFilter(QObject* watched, QEvent* event) -{ - const QEvent::Type event_type = event->type(); - - // if the key is being released, set the input - if (event_type == QEvent::KeyRelease) - { - addNewBinding(std::move(m_new_binding_value)); - stopListeningForInput(); - return true; - } - else if (event_type == QEvent::KeyPress) - { - QString binding = QtUtils::KeyEventToString(static_cast(event)); - if (!binding.isEmpty()) - m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString(); - - return true; - } - else if (event_type == QEvent::MouseButtonRelease) - { - const u32 button_mask = static_cast(static_cast(event)->button()); - const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask); - m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1); - addNewBinding(std::move(m_new_binding_value)); - stopListeningForInput(); - return true; - } - - return InputBindingDialog::eventFilter(watched, event); -} - void InputButtonBindingDialog::hookControllerInput() { ControllerInterface* controller_interface = m_host_interface->getControllerInterface(); @@ -206,7 +219,7 @@ void InputButtonBindingDialog::hookControllerInput() // TODO: this probably should consider the "last value" QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), - Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, ei.value > 0)); + Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional, ei.value > 0)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f) @@ -229,21 +242,6 @@ void InputButtonBindingDialog::unhookControllerInput() controller_interface->ClearHook(); } -void InputButtonBindingDialog::bindToControllerAxis(int controller_index, int axis_index, bool positive) -{ - std::string binding = - StringUtil::StdStringFromFormat("Controller%d/%cAxis%d", controller_index, positive ? '+' : '-', axis_index); - addNewBinding(std::move(binding)); - stopListeningForInput(); -} - -void InputButtonBindingDialog::bindToControllerButton(int controller_index, int button_index) -{ - std::string binding = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index); - addNewBinding(std::move(binding)); - stopListeningForInput(); -} - void InputButtonBindingDialog::startListeningForInput(u32 timeout_in_seconds) { InputBindingDialog::startListeningForInput(timeout_in_seconds); @@ -257,8 +255,10 @@ void InputButtonBindingDialog::stopListeningForInput() } InputAxisBindingDialog::InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, - std::string key_name, std::vector bindings, QWidget* parent) - : InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent) + std::string key_name, std::vector bindings, + Controller::AxisType axis_type, QWidget* parent) + : InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent), + m_axis_type(axis_type) { } @@ -282,6 +282,13 @@ void InputAxisBindingDialog::hookControllerInput() return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), + Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional, std::nullopt)); + return ControllerInterface::Hook::CallbackResult::StopMonitoring; + } + else if (ei.type == ControllerInterface::Hook::Type::Button && m_axis_type == Controller::AxisType::Half && + ei.value > 0.0f) + { + QMetaObject::invokeMethod(this, "bindToControllerButton", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } @@ -299,11 +306,19 @@ void InputAxisBindingDialog::unhookControllerInput() controller_interface->ClearHook(); } -void InputAxisBindingDialog::bindToControllerAxis(int controller_index, int axis_index) +bool InputAxisBindingDialog::eventFilter(QObject* watched, QEvent* event) { - std::string binding = StringUtil::StdStringFromFormat("Controller%d/Axis%d", controller_index, axis_index); - addNewBinding(std::move(binding)); - stopListeningForInput(); + if (m_axis_type != Controller::AxisType::Half) + { + const QEvent::Type event_type = event->type(); + + if (event_type == QEvent::KeyRelease || event_type == QEvent::KeyPress || event_type == QEvent::MouseButtonRelease) + { + return true; + } + } + + return InputBindingDialog::eventFilter(watched, event); } void InputAxisBindingDialog::startListeningForInput(u32 timeout_in_seconds) diff --git a/src/duckstation-qt/inputbindingdialog.h b/src/duckstation-qt/inputbindingdialog.h index 84ee7efe0..ff26b3776 100644 --- a/src/duckstation-qt/inputbindingdialog.h +++ b/src/duckstation-qt/inputbindingdialog.h @@ -1,7 +1,9 @@ #pragma once #include "common/types.h" +#include "core/controller.h" #include "ui_inputbindingdialog.h" #include +#include #include #include @@ -17,6 +19,8 @@ public: ~InputBindingDialog(); protected Q_SLOTS: + void bindToControllerAxis(int controller_index, int axis_index, std::optional positive); + void bindToControllerButton(int controller_index, int button_index); void onAddBindingButtonClicked(); void onRemoveBindingButtonClicked(); void onClearBindingsButtonClicked(); @@ -52,7 +56,7 @@ protected: u32 m_input_listen_remaining_seconds = 0; }; -class InputButtonBindingDialog : public InputBindingDialog +class InputButtonBindingDialog final : public InputBindingDialog { Q_OBJECT @@ -61,13 +65,6 @@ public: std::vector bindings, QWidget* parent); ~InputButtonBindingDialog(); -protected: - bool eventFilter(QObject* watched, QEvent* event) override; - -private Q_SLOTS: - void bindToControllerAxis(int controller_index, int axis_index, bool positive); - void bindToControllerButton(int controller_index, int button_index); - protected: void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; @@ -75,21 +72,22 @@ protected: void unhookControllerInput(); }; -class InputAxisBindingDialog : public InputBindingDialog +class InputAxisBindingDialog final : public InputBindingDialog { Q_OBJECT public: InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name, - std::vector bindings, QWidget* parent); + std::vector bindings, Controller::AxisType axis_type, QWidget* parent); ~InputAxisBindingDialog(); -private Q_SLOTS: - void bindToControllerAxis(int controller_index, int axis_index); - protected: + bool eventFilter(QObject* watched, QEvent* event) override; void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; void hookControllerInput(); void unhookControllerInput(); + +private: + Controller::AxisType m_axis_type; }; diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index ec97936dc..9d55dc16e 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -40,6 +40,27 @@ void InputBindingWidget::updateText() setText(QString::fromStdString(m_bindings[0])); } +void InputBindingWidget::bindToControllerAxis(int controller_index, int axis_index, std::optional positive) +{ + const char* sign_char = ""; + if (positive) + { + sign_char = *positive ? "+" : "-"; + } + + m_new_binding_value = + StringUtil::StdStringFromFormat("Controller%d/%sAxis%d", controller_index, sign_char, axis_index); + setNewBinding(); + stopListeningForInput(); +} + +void InputBindingWidget::bindToControllerButton(int controller_index, int button_index) +{ + m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index); + setNewBinding(); + stopListeningForInput(); +} + void InputBindingWidget::beginRebindAll() { m_is_binding_all = true; @@ -53,8 +74,32 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event) { const QEvent::Type event_type = event->type(); - if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || - event_type == QEvent::MouseButtonDblClick) + // if the key is being released, set the input + if (event_type == QEvent::KeyRelease) + { + setNewBinding(); + stopListeningForInput(); + return true; + } + else if (event_type == QEvent::KeyPress) + { + QString binding = QtUtils::KeyEventToString(static_cast(event)); + if (!binding.isEmpty()) + m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString(); + + return true; + } + else if (event_type == QEvent::MouseButtonRelease) + { + const u32 button_mask = static_cast(static_cast(event)->button()); + const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask); + m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1); + setNewBinding(); + stopListeningForInput(); + return true; + } + + if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick) { return true; } @@ -185,38 +230,6 @@ InputButtonBindingWidget::~InputButtonBindingWidget() InputButtonBindingWidget::stopListeningForInput(); } -bool InputButtonBindingWidget::eventFilter(QObject* watched, QEvent* event) -{ - const QEvent::Type event_type = event->type(); - - // if the key is being released, set the input - if (event_type == QEvent::KeyRelease) - { - setNewBinding(); - stopListeningForInput(); - return true; - } - else if (event_type == QEvent::KeyPress) - { - QString binding = QtUtils::KeyEventToString(static_cast(event)); - if (!binding.isEmpty()) - m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString(); - - return true; - } - else if (event_type == QEvent::MouseButtonRelease) - { - const u32 button_mask = static_cast(static_cast(event)->button()); - const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask); - m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1); - setNewBinding(); - stopListeningForInput(); - return true; - } - - return InputBindingWidget::eventFilter(watched, event); -} - void InputButtonBindingWidget::hookControllerInput() { ControllerInterface* controller_interface = m_host_interface->getControllerInterface(); @@ -232,7 +245,7 @@ void InputButtonBindingWidget::hookControllerInput() // TODO: this probably should consider the "last value" QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), - Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, ei.value > 0)); + Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional, ei.value > 0)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f) @@ -255,21 +268,6 @@ void InputButtonBindingWidget::unhookControllerInput() controller_interface->ClearHook(); } -void InputButtonBindingWidget::bindToControllerAxis(int controller_index, int axis_index, bool positive) -{ - m_new_binding_value = - StringUtil::StdStringFromFormat("Controller%d/%cAxis%d", controller_index, positive ? '+' : '-', axis_index); - setNewBinding(); - stopListeningForInput(); -} - -void InputButtonBindingWidget::bindToControllerButton(int controller_index, int button_index) -{ - m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index); - setNewBinding(); - stopListeningForInput(); -} - void InputButtonBindingWidget::startListeningForInput(u32 timeout_in_seconds) { InputBindingWidget::startListeningForInput(timeout_in_seconds); @@ -291,8 +289,8 @@ void InputButtonBindingWidget::openDialog() } InputAxisBindingWidget::InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, - std::string key_name, QWidget* parent) - : InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent) + std::string key_name, Controller::AxisType axis_type, QWidget* parent) + : InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent), m_axis_type(axis_type) { } @@ -316,6 +314,13 @@ void InputAxisBindingWidget::hookControllerInput() return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), + Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional, std::nullopt)); + return ControllerInterface::Hook::CallbackResult::StopMonitoring; + } + else if (ei.type == ControllerInterface::Hook::Type::Button && m_axis_type == Controller::AxisType::Half && + ei.value > 0.0f) + { + QMetaObject::invokeMethod(this, "bindToControllerButton", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } @@ -333,11 +338,19 @@ void InputAxisBindingWidget::unhookControllerInput() controller_interface->ClearHook(); } -void InputAxisBindingWidget::bindToControllerAxis(int controller_index, int axis_index) +bool InputAxisBindingWidget::eventFilter(QObject* watched, QEvent* event) { - m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Axis%d", controller_index, axis_index); - setNewBinding(); - stopListeningForInput(); + if (m_axis_type != Controller::AxisType::Half) + { + const QEvent::Type event_type = event->type(); + + if (event_type == QEvent::KeyRelease || event_type == QEvent::KeyPress || event_type == QEvent::MouseButtonRelease) + { + return true; + } + } + + return InputBindingWidget::eventFilter(watched, event); } void InputAxisBindingWidget::startListeningForInput(u32 timeout_in_seconds) @@ -354,7 +367,7 @@ void InputAxisBindingWidget::stopListeningForInput() void InputAxisBindingWidget::openDialog() { - InputAxisBindingDialog binding_dialog(m_host_interface, m_section_name, m_key_name, m_bindings, + InputAxisBindingDialog binding_dialog(m_host_interface, m_section_name, m_key_name, m_bindings, m_axis_type, QtUtils::GetRootWidget(this)); binding_dialog.exec(); reloadBinding(); diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h index 9c3bc338d..e34f44d8d 100644 --- a/src/duckstation-qt/inputbindingwidgets.h +++ b/src/duckstation-qt/inputbindingwidgets.h @@ -1,6 +1,8 @@ #pragma once +#include "core/controller.h" #include "core/types.h" #include +#include class QTimer; @@ -18,6 +20,8 @@ public: ALWAYS_INLINE void setNextWidget(InputBindingWidget* widget) { m_next_widget = widget; } public Q_SLOTS: + void bindToControllerAxis(int controller_index, int axis_index, std::optional positive); + void bindToControllerButton(int controller_index, int button_index); void beginRebindAll(); void clearBinding(); void reloadBinding(); @@ -66,13 +70,6 @@ public: QWidget* parent); ~InputButtonBindingWidget(); -protected: - bool eventFilter(QObject* watched, QEvent* event) override; - -private Q_SLOTS: - void bindToControllerAxis(int controller_index, int axis_index, bool positive); - void bindToControllerButton(int controller_index, int button_index); - protected: void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; @@ -87,18 +84,19 @@ class InputAxisBindingWidget : public InputBindingWidget public: InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name, - QWidget* parent); + Controller::AxisType axis_type, QWidget* parent); ~InputAxisBindingWidget(); -private Q_SLOTS: - void bindToControllerAxis(int controller_index, int axis_index); - protected: + bool eventFilter(QObject* watched, QEvent* event) override; void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; void openDialog() override; void hookControllerInput(); void unhookControllerInput(); + +private: + Controller::AxisType m_axis_type; }; class InputRumbleBindingWidget : public InputBindingWidget diff --git a/src/duckstation-qt/main.cpp b/src/duckstation-qt/main.cpp index 8eb740aaf..cfad70bea 100644 --- a/src/duckstation-qt/main.cpp +++ b/src/duckstation-qt/main.cpp @@ -1,6 +1,7 @@ #include "common/log.h" #include "mainwindow.h" #include "qthostinterface.h" +#include "qtutils.h" #include #include #include @@ -8,6 +9,9 @@ int main(int argc, char* argv[]) { + // Register any standard types we need elsewhere + qRegisterMetaType>(); + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 97e67ef50..756951b0f 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2,10 +2,10 @@ #include "aboutdialog.h" #include "autoupdaterdialog.h" #include "common/assert.h" -#include "core/game_list.h" #include "core/host_display.h" #include "core/settings.h" #include "core/system.h" +#include "frontend-common/game_list.h" #include "gamelistsettingswidget.h" #include "gamelistwidget.h" #include "gamepropertiesdialog.h" @@ -27,10 +27,11 @@ #include #include -static constexpr char DISC_IMAGE_FILTER[] = +static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP( + "MainWindow", "All File Types (*.bin *.img *.cue *.chd *.exe *.psexe *.psf);;Single-Track Raw Images (*.bin *.img);;Cue Sheets " "(*.cue);;MAME CHD Images (*.chd);;PlayStation Executables (*.exe *.psexe);;Portable Sound Format Files " - "(*.psf);;Playlists (*.m3u)"; + "(*.psf);;Playlists (*.m3u)"); ALWAYS_INLINE static QString getWindowTitle() { @@ -405,7 +406,7 @@ void MainWindow::onGameListContextMenuRequested(const QPoint& point, const GameL if (entry) { connect(menu.addAction(tr("Properties...")), &QAction::triggered, - [this, entry]() { GamePropertiesDialog::showForEntry(m_host_interface, entry); }); + [this, entry]() { GamePropertiesDialog::showForEntry(m_host_interface, entry, this); }); connect(menu.addAction(tr("Open Containing Directory...")), &QAction::triggered, [this, entry]() { const QFileInfo fi(QString::fromStdString(entry->path)); diff --git a/src/duckstation-qt/memorycardsettingswidget.cpp b/src/duckstation-qt/memorycardsettingswidget.cpp index 63ee1eaae..6df775b40 100644 --- a/src/duckstation-qt/memorycardsettingswidget.cpp +++ b/src/duckstation-qt/memorycardsettingswidget.cpp @@ -11,7 +11,7 @@ #include #include -static constexpr char MEMORY_CARD_IMAGE_FILTER[] = "All Memory Card Types (*.mcd *.mcr *.mc)"; +static constexpr char MEMORY_CARD_IMAGE_FILTER[] = QT_TRANSLATE_NOOP("MemoryCardSettingsWidget", "All Memory Card Types (*.mcd *.mcr *.mc)"); MemoryCardSettingsWidget::MemoryCardSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog) diff --git a/src/duckstation-qt/qt.conf b/src/duckstation-qt/qt.conf new file mode 100644 index 000000000..e69de29bb diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index df1f4b6aa..ddacf57e3 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -6,9 +6,9 @@ #include "common/log.h" #include "common/string_util.h" #include "core/controller.h" -#include "core/game_list.h" #include "core/gpu.h" #include "core/system.h" +#include "frontend-common/game_list.h" #include "frontend-common/imgui_styles.h" #include "frontend-common/ini_settings_interface.h" #include "frontend-common/opengl_host_display.h" @@ -641,6 +641,7 @@ void QtHostInterface::OnSystemDestroyed() { CommonHostInterface::OnSystemDestroyed(); + ClearOSDMessages(); startBackgroundControllerPollTimer(); emit emulationStopped(); } diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index 9df725acb..1122cdf00 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -1,8 +1,10 @@ #include "qtutils.h" #include "common/byte_stream.h" +#include #include #include #include +#include #include #include #include @@ -646,4 +648,25 @@ void OpenURL(QWidget* parent, const char* url) return OpenURL(parent, QUrl::fromEncoded(QByteArray(url, static_cast(std::strlen(url))))); } +void FillComboBoxWithResolutionScales(QComboBox* cb) +{ + cb->addItem(qApp->translate("GPUSettingsWidget", "Automatic based on window size")); + cb->addItem(qApp->translate("GPUSettingsWidget", "1x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "2x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "3x (for 720p)")); + cb->addItem(qApp->translate("GPUSettingsWidget", "4x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "5x (for 1080p)")); + cb->addItem(qApp->translate("GPUSettingsWidget", "6x (for 1440p)")); + cb->addItem(qApp->translate("GPUSettingsWidget", "7x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "8x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "9x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "10x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "11x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "12x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "13x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "14x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "15x")); + cb->addItem(qApp->translate("GPUSettingsWidget", "16x")); +} + } // namespace QtUtils \ No newline at end of file diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index ef9975616..b0df67fe9 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -1,11 +1,15 @@ #pragma once #include +#include #include #include #include +Q_DECLARE_METATYPE(std::optional); + class ByteStream; +class QComboBox; class QFrame; class QKeyEvent; class QTableView; @@ -51,4 +55,7 @@ void OpenURL(QWidget* parent, const QUrl& qurl); /// Opens a URL string with the default handler. void OpenURL(QWidget* parent, const char* url); +/// Fills a combo box with resolution scale options. +void FillComboBoxWithResolutionScales(QComboBox* cb); + } // namespace QtUtils \ No newline at end of file diff --git a/src/duckstation-qt/translations/duckstation-qt_pt-br.ts b/src/duckstation-qt/translations/duckstation-qt_pt-br.ts index 451a3f85a..0cb7e6515 100644 --- a/src/duckstation-qt/translations/duckstation-qt_pt-br.ts +++ b/src/duckstation-qt/translations/duckstation-qt_pt-br.ts @@ -112,41 +112,225 @@ GPU Max Run-Ahead: - + + + Enable Recompiler ICache + Ativar Recompilador ICache + + + Reset To Default Redefinir para o Padrão - + Enable Recompiler Memory Exceptions Habilitar Exceções de Memória - + System Settings Configurações do Sistema - + Show Debug Menu Mostrar Menu de Depuração - - + + Use Debug Host GPU Device Usar GPU para depuração - + + Unchecked Desmarcado - + Enables the usage of debug devices and shaders for rendering APIs which support them. Should only be used when debugging the emulator. Permite o uso de dispositivos de depuração e shaders para renderizar APIs que os suportam. Só deve ser usado ao depurar o emulador. + + + Determines whether the CPU's instruction cache is simulated in the recompiler. Improves accuracy at a small cost to performance. If games are running too fast, try enabling this option. + Determina se a instrução enviada ao CPU emulado fica armazenada no recompilador. Melhor a precisão ao pequeno custo de performance. Se os jogos estão rodando muito rápido, tente ativar esta opção. + + + + AnalogController + + + + Controller %u switched to analog mode. + Controle %u mudado para modo analogico. + + + + + Controller %u switched to digital mode. + Controle %u mudado para modo digital. + + + + Controller %u is locked to analog mode by the game. + Controle %u está travado em modo analogico pelo jogo. + + + + Controller %u is locked to digital mode by the game. + Controle %u está travado no modo digital pelo jogo. + + + + LeftX + Esquerda Eixo X + + + + LeftY + Esquerda Eixo Y + + + + RightX + Direita Eixo X + + + + RightY + Direita Eixo Y + + + + Up + 🠉 + + + + Down + 🠋 + + + + Left + 🠰 + 🠈 + + + + Right + ➡️ + + + + + Select + Select + + + + Start + Start + + + + Triangle + ⃤ 🛆⟁ + + + + + Cross + + + + + + Circle + + + + + Square + + + + + L1 + L1 + + + + L2 + L2 + + + + R1 + R1 + + + + R2 + R2 + + + + L3 + L3 + + + + R3 + R3 + + + + Analog + Analogico + + + + Enable Analog Mode on Reset + Ativar modo Analogico ao Reiniciar + + + + Automatically enables analog mode when the console is reset/powered on. + Ativa o modo analogico automaticamente quando o console é reiniciado / desligado. + + + + Analog Axis Scale + Escala de Eixo do Analogico + + + + Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. + Seta a escala do eixo dos controles. Um valor entre 1.30 e 1.40 é recomendável quando estiver usando controles mais recentes, ex: Dualshock 4 e Controles de X-Box One. + + + + AudioBackend + + + Null (No Output) + Nulo (Sem som) + + + + Cubeb + Cubed + + + + SDL + + AudioSettingsWidget @@ -177,11 +361,13 @@ + Sync To Output Sincronizar + Start Dumping On Boot Despejar Audio ao Iniciar @@ -197,7 +383,7 @@ - + Mute Mudo @@ -207,68 +393,72 @@ 100% - + Audio Backend Opção de Audio - + The audio backend determines how frames produced by the emulator are submitted to the host. Cubeb provides the lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio output. As opções disponiveis determinam como o jogo irá reproduzir os sons; Cubed, fornece menor latência (atraso), se tiver problemas tente usar a opção SDL. A opção Nulo desativa o som do jogo completamente no emulador. - + Buffer Size Tamanho do Buffer - + The buffer size determines the size of the chunks of audio which will be pulled by the host. Smaller values reduce the output latency, but may cause hitches if the emulation speed is inconsistent. Note that the Cubeb backend uses smaller chunks regardless of this value, so using a low value here may not significantly change latency. O Tamanho do Buffer determina o quão preciso será o som no emulador.Valores menores reduzem a latência de saída, mas podem causar problemas se a velocidade da emulação for inconsistente.Usar a opção Cubed implica em valores menores independente da latência o que não fará muita diferença final. - + Checked Marcado - Throttles the emulation speed based on the audio backend pulling audio frames. Sync will automatically be disabled if not running at 100% speed. + A sincronização será desativada automaticamente se não estiver funcionando a 100% da velocidade. + + + + Throttles the emulation speed based on the audio backend pulling audio frames. This helps to remove noises or crackling if emulation is too fast. Sync will automatically be disabled if not running at 100% speed. A sincronização será desativada automaticamente se não estiver funcionando a 100% da velocidade. - - + + Unchecked Desmarcado - + Start dumping audio to file as soon as the emulator is started. Mainly useful as a debug option. Inicia o despejo do audio para um arquivo assim que o emulador é iniciado. Útil só em caso de depuração. - + Volume Volume - + Controls the volume of the audio played on the host. Values are in percentage. - Controla o volume do aúdio. Valores são mostrados em porcentagem. + Controla o volume do áudio. Valores são mostrados em porcentagem. - + Prevents the emulator from producing any audible sound. Previne o emulador de produzir qualquer som. - + Maximum latency: %1 frames (%2ms) Latência Máxima:%1 frames (%2ms) - + %1% %1% @@ -353,6 +543,60 @@ Cancelar + + CPUExecutionMode + + + Intepreter (Slowest) + Interpretador (Mais Lento) + + + + Cached Interpreter (Faster) + Int. Armazenado (Rápido) + + + + Recompiler (Fastest) + Recompilador (Mais Rápido) + + + + CommonHostInterface + + + Are you sure you want to stop emulation? + Quer mesmo parar a Emulacao? + + + + The current state will be saved. + O estado atual será salvo. + + + + ConsoleRegion + + + Auto-Detect + Auto Detectar + + + + NTSC-J (Japan) + NTSC-J (Japão) + + + + NTSC-U (US) + NTSC-U (US) + + + + PAL (Europe, Australia) + PAL (Europeu, Australia) + + ConsoleSettingsWidget @@ -377,7 +621,7 @@ - + Fast Boot Inicio Rápido @@ -422,147 +666,347 @@ Carregar jogo para RAM - - + + Unchecked Desmarcado - + Patches the BIOS to skip the console's boot animation. Does not work with all games, but usually safe to enabled. Pula a animação de inicio do console. Não funciona com todos os jogos, mas é seguro deixar marcado. - + Preload Image to RAM Pré-carregar Jogo para RAM - - Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. + + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some cases also eliminates stutter when games initiate audio track playback. Carrega o jogo na memória RAM. Útil para evitar certas instabilidades durante o jogo. - + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. + Carrega o jogo na memória RAM. Útil para evitar certas instabilidades durante o jogo. + + + Select BIOS Image Escolha o Arquivo de BIOS + + ControllerInterface + + + None + Nenhum + + + + SDL + SDL + + + + XInput + X-Input + + ControllerSettingsWidget - + Controller Type: Tipo de Controle: - + Load Profile Carregar Perfil - + Save Profile Salvar Perfil - + Clear All Limpar Tudo - + Clear Bindings Limpar Atribuições - + Are you sure you want to clear all bound controls? This can not be reversed. Tem certeza que quer limpar todos os vinculos, isto não poderá ser desfeito. - - + + Rebind All Reatribuir Tudo - + Are you sure you want to rebind all controls? All currently-bound controls will be irreversibly cleared. Rebinding will begin after confirmation. Tem certeza que quer reatribuir todos os controles? Todas as mudanças feitas nos controles serão perdidas. A reatribuição se dará após a confirmação. - + Port %1 Port %1 - + Button Bindings: Atribuir Botões: - + Axis Bindings: Atribuir Analogicos: - + Rumble Vibração - - - + + + Browse... Procurar... - + Select File Escolha o Arquivo - - + + Select path to input profile ini Escolha o caminho para inserir o perfil do jogo - + New... Novo... - - + + Enter Input Profile Name Escolha um nome para o Perfil - - + + Error Erro - + No name entered, input profile was not saved. Nome não atribuido, configuração de controle não foi salva. - + No path selected, input profile was not saved. Caminho não atribuido, configuração de controle não foi salva. + + ControllerType + + + None + Nenhum + + + + Digital Controller + Controle Digital + + + + Analog Controller (DualShock) + Controle Analogico (Dualshock) + + + + Namco GunCon + Namco GunCon + + + + PlayStation Mouse + Playstation Mouse + + + + NeGcon + NeGcon + + + + DigitalController + + + Up + 🠉 + 🠉 + + + + Down + ↓ ⭳ ⯆ ⮟ 🡇 🠋 + 🠋 + + + + Left + 🠰 + 🠈 + + + + Right + 🠊 🢧➜ ➡️ + + + + + Select + Select + + + + Start + Start + + + + Triangle + + 🛆 + + + + Cross + ╳Xx + + + + + Circle + + + + + + Square + ⃞ ⬛ ⬜ + + + + + L1 + L1 + + + + L2 + L2 + + + + R1 + R1 + + + + R2 + R2 + + + + DiscRegion + + + NTSC-J (Japan) + NTSC-J (Japão) + + + + NTSC-U (US) + NTSC-U (US) + + + + PAL (Europe, Australia) + PAL (Europeu, Australia) + + + + Other + Outros + + + + DisplayCropMode + + + None + Nenhum + + + + Only Overscan Area + Somente Área Renderizada + + + + All Borders + Todas as Bordas + + + + GPURenderer + + + Hardware (D3D11) + Placa de Video (D3D11) + + + + Hardware (Vulkan) + Placa de Video (Vulkan) + + + + Hardware (OpenGL) + Placa de Video (OpenGL) + + + + Software + Software + + GPUSettingsWidget Form - + Form @@ -600,19 +1044,19 @@ - + Linear Upscaling Escalonamento Linear - + Integer Upscaling Escalonamento Integro - + VSync Sincronização Vertical (V-Sync) @@ -628,37 +1072,37 @@ - + True Color Rendering (24-bit, disables dithering) Renderização em (24 Cores, desativa o efeito dithering) - + Scaled Dithering (scale dither pattern to resolution) Dithering Escalonado, (Escalona o padrão do dithering para a resolução) - + Disable Interlacing (force progressive render/scan) Desativa o entrelaçamento (Força Rederização Progressiva) - + Force NTSC Timings (60hz-on-PAL) Força o temporizador rodar em NTSC (60hz em jogos EU) - + Bilinear Texture Filtering Filtragem de Textura Bilinear - + Widescreen Hack Hack para Telas Widescreen @@ -669,64 +1113,62 @@ - + Geometry Correction Correção Geométrica - + Culling Correction Correção de Curvas - + Texture Correction Correção de Textura - + Vertex Cache Vertice Armazenado - + CPU Mode Modo CPU - + Renderer Rederizador - Chooses the backend to use for rendering tasks for the the console GPU. Depending on your system and hardware, Direct3D 11 and OpenGL hardware backends may be available. The software renderer offers the best compatibility, but is the slowest and does not offer any enhancements. - Escolhe a opção a ser usada para emular a GPU. Dependendo do seu sistema e hardware, As opções DX11 e OpenGL podem aparecer.O renderizador de software oferece a melhor compatibilidade, mas é o mais lento e não oferece nenhum aprimoramento. + Escolhe a opção a ser usada para emular a GPU. Dependendo do seu sistema e hardware, As opções DX11 e OpenGL podem aparecer.O renderizador de software oferece a melhor compatibilidade, mas é o mais lento e não oferece nenhum aprimoramento. - + Adapter Adaptador - If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware renderers. This option is only supported in Direct3D and Vulkan, OpenGL will always use the default device. - Se você tem várias GPUs ,você poderá selecionar qual delas deseja usar para os renderizadores de hardware. Esta opção é suportada apenas no Direct3D e no Vulkan, OpenGL sempre usará o dispositivo padrão. + Se você tem várias GPUs ,você poderá selecionar qual delas deseja usar para os renderizadores de hardware. Esta opção é suportada apenas no Direct3D e no Vulkan, OpenGL sempre usará o dispositivo padrão. - - - - - - - - - + + + + + + + + + Unchecked Desmarcado @@ -735,87 +1177,79 @@ Permite o uso de dispositivos de depuração e shaders para renderizar APIs que os suportam. Só deve ser usado ao depurar o emulador. - + Aspect Ratio Razão de Aspecto - + Changes the aspect ratio used to display the console's output to the screen. The default is 4:3 which matches a typical TV of the era. Altera a proporção usada para exibir o jogo na tela. O padrão é 4:3, que corresponde a uma TV típica da época.CRT mais conhecida como Tubão. - + Crop Mode Modo de Corte - + Only Overscan Area Somente Área Renderizada - Determines how much of the area typically not visible on a consumer TV set to crop/hide. Some games display content in the overscan area, or use it for screen effects and may not display correctly with the All Borders setting. Only Overscan offers a good compromise between stability and hiding black borders. - Determina quanto da area normalmente não visivel em uma TV o usuário pode ver ou não.Alguns jogos mostram conteúdo fora desta area pré-determinada.Somente esta opção "overscan" (fora da área visivel) pode oferecer um boa estabilidade na hora de ocultar as tarjas (bordas)pretas quando ocorrem. + Determina quanto da area normalmente não visivel em uma TV o usuário pode ver ou não.Alguns jogos mostram conteúdo fora desta area pré-determinada.Somente esta opção "overscan" (fora da área visivel) pode oferecer um boa estabilidade na hora de ocultar as tarjas (bordas)pretas quando ocorrem. - Forces the rendering and display of frames to progressive mode. This removes the "combing" effect seen in 480i games by rendering them in 480p. Not all games are compatible with this option, some require interlaced rendering or render interlaced internally. Usually safe to enable. - Força a renderização e a exibição de quadros para o modo progressivo. Isso remove efeitos de "trepidação" Visto nos jogos 480i renderizando-os em 480p.Nem todos os jogos são compatíveis com esta opção, alguns requerem renderização entrelaçada internamente. Normalmente é seguro ativar. + Força a renderização e a exibição de quadros para o modo progressivo. Isso remove efeitos de "trepidação" Visto nos jogos 480i renderizando-os em 480p.Nem todos os jogos são compatíveis com esta opção, alguns requerem renderização entrelaçada internamente. Normalmente é seguro ativar. - Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. For variable frame rate games, it may not affect the speed. - Quando o console está no modo PAL - Geralmente jogos Europeus rodam a 50hz. força estes jogos a rodar em até 60hz sendo assim, resulta em um jogo mais rápido até 15%.Em jogos com taxas de quadro (fps) variável pode isto não afetará a velocidade na hora da jogatina. + Quando o console está no modo PAL - Geralmente jogos Europeus rodam a 50hz. força estes jogos a rodar em até 60hz sendo assim, resulta em um jogo mais rápido até 15%.Em jogos com taxas de quadro (fps) variável pode isto não afetará a velocidade na hora da jogatina. Forces the display of frames to progressive mode. This only affects the displayed image, the console will be unaware of the setting. If the game is internally producing interlaced frames, this option may not have any effect. Usually safe to enable. Força o modo de quadros por segundo em modo progressivo. Se o jogo já tem essa opção nativamente ele não irá ter nenhum beneficio podendo assim deixar a mesma ligada. - - - - - + + + + + Checked Marcado - Uses bilinear texture filtering when displaying the console's framebuffer to the screen. Disabling filtering will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. The option will be less noticable the higher the resolution scale. - Usa textura bilinear filtrando todo buffer para a tela principal.Desabilitar esta filtragem produzirá uma imagem mais nítida porém pixelada. Ativar irá deixar a imagem mais suave. Esta opção fica menos notável em resoluções mais altas. + Usa textura bilinear filtrando todo buffer para a tela principal.Desabilitar esta filtragem produzirá uma imagem mais nítida porém pixelada. Ativar irá deixar a imagem mais suave. Esta opção fica menos notável em resoluções mais altas. - Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games. - Adiciona preenchimento na tela para garantir que a proporção entre pixels seja um número inteiro. Pode resultar em uma imagem mais nítida em alguns jogos 2D. + Adiciona preenchimento na tela para garantir que a proporção entre pixels seja um número inteiro. Pode resultar em uma imagem mais nítida em alguns jogos 2D. - Enables synchronization with the host display when possible. Enabling this option will provide better frame pacing and smoother motion with fewer duplicated frames. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). - Ativa a sincronização quando possível. A ativação dessa opção fornecerá melhor ritmo de quadros por segundo e movimento mais suave com menos quadros duplicados.<br><br>O V-Sync é desativado automaticamente quando não é possível usá-lo (por exemplo quando o jogo não estiver rodando a 100%). + Ativa a sincronização quando possível. A ativação dessa opção fornecerá melhor ritmo de quadros por segundo e movimento mais suave com menos quadros duplicados.<br><br>O V-Sync é desativado automaticamente quando não é possível usá-lo (por exemplo quando o jogo não estiver rodando a 100%). - + Resolution Scale Escala de Resolução - Enables the upscaling of 3D objects rendered to the console's framebuffer. Only applies to the hardware backends. This option is usually safe, with most games looking fine at higher resolutions. Higher resolutions require a more powerful GPU. - Permite o aumento de escala de objetos 3D renderizados, aplica-se apenas aos back-end de hardware é seguro usar essa opção na maioria dos jogos ficando melhor ainda em resoluções mais altas; Isto implica também no maior uso da sua Placa de Video. + Permite o aumento de escala de objetos 3D renderizados, aplica-se apenas aos back-end de hardware é seguro usar essa opção na maioria dos jogos ficando melhor ainda em resoluções mais altas; Isto implica também no maior uso da sua Placa de Video. - + Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per channel. This produces nicer looking gradients at the cost of making some colours look slightly different. Disabling the option also enables dithering, which makes the transition between colours less sharp by applying a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't and will have broken effects with it enabled. Only applies to the hardware renderers. Força a precisão das cores produz efeitos de gradientes mais agradável ao custo de fazer com que algumas cores pareçam um pouco diferentes. Desativar a opção também ativa alguns pontilhados, o que torna a transição entre cores menos nítida a maioria dos jogos é compatível com esta opção, os que não forem terão efeitos quebrados com a opção ativada. Aplica-se apenas aos renderizadores por hardware. - Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. Usually safe to enable, and only supported by the hardware renderers. - Escalona os 'ditherings' - pontilhados na imagem para a placa de Video.Torna a visão destes pontos muito menos visiveis em resoluções mais altas.Geralmente seguro ativar e suportado apenas pelos rederizadores por Hardware (ou seja usando sua placa de vídeo). + Escalona os 'ditherings' - pontilhados na imagem para a placa de Video.Torna a visão destes pontos muito menos visiveis em resoluções mais altas.Geralmente seguro ativar e suportado apenas pelos rederizadores por Hardware (ou seja usando sua placa de vídeo). Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. For variable frame rate games, it may not affect the framerate. @@ -834,115 +1268,281 @@ Reduz 'tremeliques' nos poligonos tentando preservar os mesmos na hora da transferência para a memória. Funciona apenas se rederizado por hardware e pode não é compatível com todos os jogos. - Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. + Suaviza texturas ampliadas em objetos 3D usando filtragem bilinear. Terá efeito maior em resoluções mais altas. Aplica-se apenas aos rederizadores por hardware. + + + Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <b><u>May not be compatible with all games.</u></b> + Escala as posições de vértices para uma proporção de aspecto esticado, aumentando o campo de visão de 4:3 para 16:9 em jogos 3D. <br>Para jogos 2D, ou jogos que usam fundos pré-rederizados, este aprimoramento não funcionará como esperado. <b><u>Pode não ser compatível com todos os jogos</u></b> + + + + Chooses the backend to use for rendering the console/game visuals. <br>Depending on your system and hardware, Direct3D 11 and OpenGL hardware backends may be available. <br>The software renderer offers the best compatibility, but is the slowest and does not offer any enhancements. + Escolhe a opção a ser usada para emular a GPU. Dependendo do seu sistema e hardware, As opções DX11 e OpenGL podem aparecer.O renderizador de software oferece a melhor compatibilidade, mas é o mais lento e não oferece nenhum aprimoramento. + + + + If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default device. + Se você tem várias GPUs ,você poderá selecionar qual delas deseja usar para os renderizadores de hardware. Esta opção é suportada apenas no Direct3D e no Vulkan, OpenGL sempre usará o dispositivo padrão. + + + + Determines how much of the area typically not visible on a consumer TV set to crop/hide. <br>Some games display content in the overscan area, or use it for screen effects. <br>May not display correctly with the "All Borders" setting. "Only Overscan" offers a good compromise between stability and hiding black borders. + Determina quanto da area normalmente não visivel em uma TV o usuário pode ver ou não.Alguns jogos mostram conteúdo fora desta area pré-determinada.Somente esta opção "overscan" (fora da área visivel) pode oferecer um boa estabilidade na hora de ocultar as tarjas (bordas)pretas quando ocorrem. + + + + Forces the rendering and display of frames to progressive mode. <br>This removes the "combing" effect seen in 480i games by rendering them in 480p. Usually safe to enable.<br> <b><u>May not be compatible with all games.</u></b> + Força a renderização e a exibição de quadros para o modo progressivo. Isso remove efeitos de "trepidação" Visto nos jogos 480i renderizando-os em 480p.Nem todos os jogos são compatíveis com esta opção, alguns requerem renderização entrelaçada internamente. Normalmente é seguro ativar..</u></b> + + + + Uses bilinear texture filtering when displaying the console's framebuffer to the screen. <br>Disabling filtering will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. <br>The option will be less noticable the higher the resolution scale. + Usa textura bilinear filtrando todo buffer para a tela principal.Desabilitar esta filtragem produzirá uma imagem mais nítida porém pixelada. Ativar irá deixar a imagem mais suave. Esta opção fica menos notável em resoluções mais altas. + + + + Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. <br>May result in a sharper image in some 2D games. + Adiciona preenchimento na tela para garantir que a proporção entre pixels seja um número inteiro. Pode resultar em uma imagem mais nítida em alguns jogos 2D. + + + + Enable this option to match DuckStation's refresh rate with your current monitor or screen. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). + Habilite esta opção para combinar a taxa de atualização do emulador com seu monitor. O V-Sync (sincronização vertical) será desativado automaticamente quando não for possivel atingir 100% da velocidade. + + + + Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies to the hardware backends. <br>This option is usually safe, with most games looking fine at higher resolutions. Higher resolutions require a more powerful GPU. + Aumentar a resolução para mais de 1x aumentará a resolução dos Poligonos e linhas em jogos 3D. Só é utilizável quando usado com placas de video dedicadas. <br> Geralmente é seguro ativar esta opção, deixando assim a maior parte dos jogos com vizual muito melhor em resoluções mais altas; Porém, utiliza mais da sua placa de Vídeo. + + + + Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. <br>Usually safe to enable, and only supported by the hardware renderers. + Escalona os 'ditherings' - pontilhados na imagem para a placa de Video.Torna a visão destes pontos muito menos visiveis em resoluções mais altas.Geralmente seguro ativar e suportado apenas pelos rederizadores por Hardware (ou seja usando sua placa de vídeo). + + + + Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. <br>For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. <br>For variable frame rate games, it may not affect the speed. + Quando o console está no modo PAL - Geralmente jogos Europeus rodam a 50hz. força estes jogos a rodar em até 60hz sendo assim, resulta em um jogo mais rápido até 15%.Em jogos com taxas de quadro (fps) variável pode isto não afetará a velocidade na hora da jogatina. + + + + Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. <br>Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. Suaviza texturas ampliadas em objetos 3D usando filtragem bilinear. Terá efeito maior em resoluções mais altas. Aplica-se apenas aos rederizadores por hardware. - - Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <b><u>May not be compatible with all games.</u></b> + + Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <br><b><u>May not be compatible with all games.</u></b> Escala as posições de vértices para uma proporção de aspecto esticado, aumentando o campo de visão de 4:3 para 16:9 em jogos 3D. <br>Para jogos 2D, ou jogos que usam fundos pré-rederizados, este aprimoramento não funcionará como esperado. <b><u>Pode não ser compatível com todos os jogos</u></b> - + Reduces "wobbly" polygons and "warping" textures that are common in PS1 games. <br>Only works with the hardware renderers. <b><u>May not be compatible with all games.</u></b> Reduz "tremeliques" nos poligonos tentando preservar os mesmos na hora da transferência para a memória. Funciona apenas se rederizado por hardware e pode não ser compatível com todos os jogos.</u></b> - + Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction enabled. Aumenta a precisão das curvas nos poligonos, reduzindo o número de buracos na geometria do mesmo. Requer a Correção Geometrica ativada. - + Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. Requires geometry correction enabled. Utiliza interpolação corretiva em perspetiva para cordenadas e das cores na textura, endireitando as que estiverem distorcidas. Requer correção de geometria ativada. - + Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility. Quando a correção de vertices falha, essa opção se encarrega de usar as coordenadas da tela para o rastreamento. Pode melhorar a compatibilidade com o PGXP. - + Tries to track vertex manipulation through the CPU. Some games require this option for PGXP to be effective. Very slow, and incompatible with the recompiler. Tenta manipular o rastreamento dos vértices (extremidades) direto para o processador. Alguns jogos exigem esta opção para que o aprimoramento - PGXP. tenha o efeito desejado. Atenção, este modo é MUITO LENTO, e imcompativel com o recompilador se ativo. - (for 720p) - >(720p) + >(720p) - (for 1080p) - >(1080p) + >(1080p) - (for 1440p) - >(1440p) + >(1440p) - (for 4K) - >(4k) + >(4k) - + Automatic based on window size Automático, baseado no tamanho da janela aberta - + + 1x + 1x > (1024x512) + + + + 2x + 2x > (2048x1024) + + + + 3x (for 720p) + 3x > (3072x1536) + + + + 4x + 4x > (4096x2048) + + + + 5x (for 1080p) + 5x > (5120x2560) + + + + 6x (for 1440p) + 6x >(6144x3072) + + + + 7x + 7x > (7168x3584) + + + + 8x + 8x > (8192x4096) + + + + 9x + 9x > (9216x4608) + + + + 10x + 10x > (10240x5120) + + + + 11x + 11x > (11264x5632) + + + + 12x + 12x > (12288x6144) + + + + 13x + 13x > (13312x6656) + + + + 14x + 14x > (14336x7168) + + + + 15x + 15x > (15360x7680) + + + + 16x + 16x > (16384x8192) + + %1x%2 - %1x%2 + %1x%2 %1x (%2x%3 VRAM) %1x (%2x%3 VRAM) - - + + (Default) Padrão + + GameListCompatibilityRating + + + Unknown + Desconhecido + + + + Doesn't Boot + Não Funciona + + + + Crashes In Intro + Quebra logo no Inicio + + + + Crashes In-Game + Quebra durante o Jogo + + + + Graphical/Audio Issues + Problemas de Áudio e Vídeo + + + + No Issues + Sem Problemas + + GameListModel - + Type Tipo - + Code Código - + Title Titulo - + File Title Titulo do Jogo (Na pasta) - + Size Tamanho - + Region Região - + Compatibility Compatibilidade @@ -1071,131 +1671,386 @@ This will download approximately 4 megabytes over your current internet connecti - + + Properties + Propriedades + + + Image Path: Caminho da Imagem: - + Game Code: Código do Jogo: - + Title: Titulo: - + Region: Região: - + Compatibility: Compatibilidade: - + Upscaling Issues: Problemas Escalonamento: - + Comments: Comentários: - + Version Tested: Versão Testada: - + Set to Current Definir para Atual - + Tracks: Faixas: - + # # - + Mode Modo - + Start Iniciar - + Length Comprimento - + Hash Valores - + + User Settings + Configurações Personalizadas + + + GPU Settings + Configurações da GPU + + + + Crop Mode: + Modo de Corte: + + + + Aspect Ratio: + Proporção e Aspecto: + + + + GPU Screen Display + Modo de Exibição GPU + + + + Linear Upscaling + Escalonamento Linear + + + + Integer Upscaling + Escalonamento Integro + + + + GPU Enhancements + Melhorias GPU + + + + Resolution Scale: + Escala de Resolução: + + + + True Color Rendering (24-bit, disables dithering) + Renderização em (24 Cores, desativa o efeito dithering) + + + + Scaled Dithering (scale dither pattern to resolution) + Dithering Escalonado, (Escalona o padrão do dithering para a resolução) + + + + Widescreen Hack + Melhoria para Telas Panorâmicas + + + + Force NTSC Timings (60hz-on-PAL) + Força o temporizador NTSC (60hz Jogos EU) + + + + Bilinear Texture Filtering + Filtragem de Textura Bilinear + + + + PGXP Geometry Correction + PGXP Correção Geometrica + + + + Controller Settings + Configurações de Controle + + + + Controller 1 Type: + Opção Controle 1: + + + + Controller 2 Type: + Opção Controle 2: + + + + Memory Card Settings + Cartões de Memória + + + + Memory Card 1 Type: + Cartão de Memória Tipo 1: + + + + Memory Card 1 Shared Path: + Cartão de Memória 1 Caminho do Compartilhamento: + + + + + Browse... + Procurar... + + + + Memory Card 2 Type: + Cartão de Memória Tipo 2: + + + + Memory Card 2 Shared Path: + Cartão de Memória 2 Caminho do Compartilhamento: + + + + Compatibility Settings + Configurações de Compatibilidade + + + + Traits + Caracteristicas Individuais + + + + Overrides + Sobreposições + + + + Display Active Offset: + Opções de Deslocamento: + + + Compute Hashes Calcular Valores - + Verify Dump Validar Jogo - + Export Compatibility Info Exportar Informação de Compatibilidade - + Close Fechar - + Game Properties - %1 Propriedades do Jogo - %1 - %1 - %1 + %1 - + + + + + + + + (unchanged) + (Inalterado) + + + <not computed> - + <não calculado> - + + + Select path to memory card image + Escolha o caminho para os Cartões de Memória + + + Not yet implemented Não Implementado Ainda - + Compatibility Info Export - + Press OK to copy to clipboard. Dê ok para copiar para área de transferência. + + GameSettingsTrait + + + Force Interpreter + Forçar Interpretador + + + + Force Software Renderer + Forçar Renderização por Software + + + Enable Interlacing + Ativar Entrelaçamento + + + + Force Interlacing + Força o Entrelaçamento + + + + Disable True Color + Desativar Cor Real (True Color) + + + + Disable Upscaling + Desativar Escalonamento + + + + Disable Scaled Dithering + Desativar Escalonamento do Dithering + + + + Disallow Forcing NTSC Timings + Desativa os temporizadores em NTSC + + + + Disable Widescreen + Desativar Func.Esticar (Widescreen) + + + + Disable PGXP + Desativar PGXP + + + + Disable PGXP Culling + Desativar Correção de Curvas + + + + Force PGXP Vertex Cache + Força o armazenamento de cache em modo PGXP + + + + Force PGXP CPU Mode + Força o PGXP em modo CPU + + + + Force Recompiler Memory Exceptions + Forçar exceções de memória do recompilador + + + + Force Recompiler ICache + Força Recompilador em modo Armazenado (ICache) + + + Enable PGXP Vertex Cache + Ativar PGXP Vértice Armazenado + + + Enable PGXP CPU Mode + Ativar PGXP - Modo CPU + + + + Force Digital Controller + Forçar Controle Digital (D-Pad) + + + Enable Recompiler Memory Exceptions + Habilitar Exceções de Memória + + GeneralSettingsWidget @@ -1209,221 +2064,366 @@ This will download approximately 4 megabytes over your current internet connecti Comportamento - - + + Pause On Start Pausar ao Iniciar - - + + Confirm Power Off Confirmar ao Fechar - - + + Save State On Exit Salvar ao Fechar - - + + Load Devices From Save States Carregar a partir do estado salvo - - + + Start Fullscreen Iniciar em Tela Cheia - - + + Render To Main Window Carregar Jogo na janela principal - - + + + Apply Per-Game Settings + Usar Configs. Separadas por Jogo + + + + Emulation Speed Velocidade da emulação - + 100% 100% - - + + Enable Speed Limiter Ativa Limitador de Velocidade - - + + Increase Timer Resolution Aumentar Resolução em Tempo Real - + On-Screen Display Mensagens na Tela - + Show Messages Mostrar Mensagens - - + + Show FPS Mostar FPS - + Show Emulation Speed Mostrar velocidade de Emulação - - + + Show VPS Mostrar VPS - + Show Resolution Mostrar Resolução - - - - - - - + + Miscellaneous + Diversos + + + + Controller Backend: + Tipo de Controle: + + + + + + + + + + Checked Marcado - + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. Determina se uma janela será mostrara para confirmar o fechamento do emulador ou jogo quando atalho é pressionado. - + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. Salva automaticamente o estado do emulador ao desligar ou sair. Você pode retomar diretamente de onde parou na próxima vez. - - - - - - + + + + + + Unchecked Desmarcado - + Automatically switches to fullscreen mode when a game is started. Muda para o modo Tela Cheia assim que um Jogo é Iniciado. - + Renders the display of the simulated console to the main window of the application, over the game list. If unchecked, the display will render in a separate window. Renderiza o jogo na janela principal do emulador sob a janela da lista de jogos. Se desmarcado, o jogo irá rodar em uma janela separada. - + Pauses the emulator when a game is started. Pausa a emulação quando um jogo é iniciado. - + When enabled, memory cards and controllers will be overwritten when save states are loaded. This can result in lost saves, and controller type mismatches. For deterministic save states, enable this option, otherwise leave disabled. Quando ativado, os cartões de memória e os controles serão substituídos assim que os saves states forem carregados. Isso pode resultar em perda de Saves e incompatibilidade nos controles. sendo assim, deixe isto desativado. - + + When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should leave this option enabled except when testing enhancements with incompatible games. + Quando ativadas, as configurações separadas por jogos serão aplicadas e os aprimoramentos incompatíveis serão desligados. Você deve deixar esta opção ativada exceto ao usar melhorias com jogos não compatíveis. + + + Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will run as fast as possible, which may not be playable. Acelera a velocidade da emulação para a velocidade escolhida acima. Se desmarcado, o emulador será executado o mais rápido possível, pode ser que não seja possivel sequer jogar. - + Increases the system timer resolution when emulation is started to provide more accurate frame pacing. May increase battery usage on laptops. Aumenta a resolução em tempo real quando emulador é iniciado dando maior precisão nos quadros emulados. Pode aumentar o consumo de bateria em Laptops. - + Sets the target emulation speed. It is not guaranteed that this speed will be reached, and if not, the emulator will run as fast as it can manage. Ajusta a velocidade da emulação. Não é garantido que a velocidade será alcançada sendo assim o emulador irá rodar o mais rápido que pode. - + Show OSD Messages Mostar mensagens em Tela - + Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc. - Mostrar as mensagens na tela quando eventos ocorrerem;Quando um SaveState é criado ou carregado, capturas de tela forem feitas etc. + Mostrar as mensagens na tela (canto superior esquerdo) quando eventos ocorrerem; Quando um SaveState é criado ou carregado, capturas de tela forem feitas etc. - + Shows the internal frame rate of the game in the top-right corner of the display. Mostra o FPS atual do jogo no topo superior direito da tela. - + Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display. Mostra o FPS no canto superior direito da tela. - + Show Speed Mostrar Velocidade - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. Mostra a velocidade de emulação atual do sistema no canto superior direito da tela registrado em porcentagem. - - + + Controller Backend + Tipo de Controle + + + + Determines the backend which is used for controller input. Windows users may prefer to use XInput over SDL2 for compatibility. + Determina qual opção de controle será usada para o controle em uso. Para quem usa Windows dê preferência ao X-Input ao invés do SDL2 para melhor compatibilidade. + + + + Enable Discord Presence Ativar Presença Rica do Discord - + Shows the game you are currently playing as part of your profile in Discord. Mostra o jogo que estiver jogando em seu perfil no Discord quando logado. - - + + Enable Automatic Update Check Verificar Por Atualizações - + Automatically checks for updates to the program on startup. Updates can be deferred until later or skipped entirely. Verifica automaticamente por atualizações assim que o emulador for iniciado. Atualizações podem ser postergadas ou ignoradas completamente. - + %1% %1% + + Hotkeys + + + Fast Forward + Avanço Rápido + + + + Toggle Fast Forward + Pulo de Quadros (Alternado) + + + + Toggle Fullscreen + Tela Cheia + + + + Toggle Pause + Pausa + + + + Power Off System + Desligar o Sistema + + + + Save Screenshot + Salvar Caputra de tela + + + + Frame Step + Pulo de quadro (Fixo) + + + + Toggle Software Rendering + Alternar para Renderizador por Software + + + + Toggle PGXP + PGXP + + + + Increase Resolution Scale + Aumentar Escala de Resolução + + + + Decrease Resolution Scale + Diminuir Escala de Resolução + + + + Load From Selected Slot + Carregar do Estado Salvo + + + + Save To Selected Slot + Salvar para compartimento Selecionado + + + + Select Previous Save Slot + Selecionar compartimento anterior + + + + Select Next Save Slot + Selecionar próximo compartimento + + + + Load Game State %u + Carregar estado de jogo %u + + + + Save Game State %u + Salvar Estado do Jogo %u + + + + Load Global State %u + Carregar Estado Global %u + + + + Save Global State %u + Salvar Estado Global %u + + + + Toggle Mute + Mudo + + + + Volume Up + Volume + + + + + Volume Down + Volume - + + InputBindingDialog @@ -1457,8 +2457,8 @@ This will download approximately 4 megabytes over your current internet connecti Atribuições para %1 %2 - - + + Push Button/Axis... [%1] Aperte Botão/Analogicos... [%1] @@ -1471,19 +2471,72 @@ This will download approximately 4 megabytes over your current internet connecti %1 atribuições - - + + Push Button/Axis... [%1] Aperte Botão/Analogicos... [%1] + + LogLevel + + + None + Nenhum + + + + Error + Erro + + + + Warning + Atenção + + + + Performance + Performance + + + + Success + Sucesso + + + + Information + Informação + + + + Developer + Desenvolvedor + + + + Profile + Perfil + + + + Debug + Depurar + + + + Trace + Rastreio + + MainWindow - - - + + + DuckStation DuckStation @@ -1494,7 +2547,7 @@ This will download approximately 4 megabytes over your current internet connecti - + Change Disc Mudar Disco @@ -1516,7 +2569,7 @@ This will download approximately 4 megabytes over your current internet connecti S&ettings - Configurações + &Configurações @@ -1531,12 +2584,12 @@ This will download approximately 4 megabytes over your current internet connecti &Help - Ajuda + &Ajuda &Debug - Depurar + &Depurar @@ -1549,251 +2602,276 @@ This will download approximately 4 megabytes over your current internet connecti Mudar Modo de emulação para CPU - + + &View + &Ver + + + toolBar - + Start &Disc... Iniciar Disco... - + Start &BIOS Iniciar BIOS - + &Scan For New Games Escanear Jogos Novos - + &Rescan All Games Scanear Todos os Jogos - + Power &Off Desligar - + &Reset Reiniciar - + &Pause Pausar - + &Load State Carregar Estado - + &Save State Salvar Estado - + E&xit Sair - + C&onsole Settings... Configuração do Console - + &Controller Settings... Configuração de Controles... - + &Hotkey Settings... Configuração de Atalhos... - + &GPU Settings... Configuração da GPU - + Fullscreen Tela Cheia - + Resolution Scale Escala de Resolução - + &GitHub Repository... Repositório no Github... - + &Issue Tracker... Problemas Abertos... - + &Discord Server... Servidor no Discord... - + Check for &Updates... Checar por Atualizações... - + &About... Sobre... - + Change Disc... Mudar Disco... - + Audio Settings... Configurações de Audio... - + Game List Settings... Configurar lista de Jogos... - + General Settings... Configurações Gerais... - + Advanced Settings... Configurações Avançadas... - + Add Game Directory... Adicionar Diretório de Jogo... - + &Settings... Configurações... - + From File... Do Arquivo... - + From Game List... Da lista de Jogos... - + Remove Disc Remover Disco - + Resume State Resumir Estado - + Global State Estado Global - + Show VRAM Mostrar VRAM - + Dump CPU to VRAM Copies Despejar cópias do CPU para a VRAM - + Dump VRAM to CPU Copies Despejar cópias da VRAM para o CPU - + Dump Audio Despejar Audio - + Dump RAM... Despejar para RAM... - + Show GPU State Mostrar Estado da GPU - + Show CDROM State Mostrar estado do CD-Rom - + Show SPU State Mostrar estado do SPU - + Show Timers State Mostrar estado do Temporizador - + Show MDEC State Mostrar estado do MDEC - + &Screenshot Captura de Tela - + &Memory Card Settings... Configurações de Memory Card... - + Resume Resumir - + Resumes the last save state created. Resumir Último Estado Salvo + + + &Toolbar + &Barra de Ferramentas + + + + &Status Bar + Barra de &Status + + + + &Game List + &Caminho dos Jogos + + + + System &Display + Sistema e &Video + Failed to get window info from widget Falha ao tentar obter informação da janela - + Failed to create host display device context. Falha ao criar uma amostra de contexto da tela. @@ -1802,84 +2880,95 @@ This will download approximately 4 megabytes over your current internet connecti Falha ao tentar obter novas informações da janela - - + + All File Types (*.bin *.img *.cue *.chd *.exe *.psexe *.psf);;Single-Track Raw Images (*.bin *.img);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;PlayStation Executables (*.exe *.psexe);;Portable Sound Format Files (*.psf);;Playlists (*.m3u) + + + + + Select Disc Image Escolha uma imagem de Disco - + Properties... Propriedades... - + Open Containing Directory... Abrir diretório... - + Default Boot Inicio Padrão - + Fast Boot Inicio Rápido - + Full Boot Inicio Completo - + Add Search Directory... Adiciona um novo diretório... - + Language changed. Please restart the application to apply. Lingua Alterada. Reinicie para Aplicar!. - + Destination File Destino do Arquivo - + Default Padrão - + DarkFusion DarkFusion - + QDarkStyle Escuro - + Updater Error Erro na Atualização - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> <p>Desculpe mas, Você está tentando atualizar uma versão não oficial do Duckstation Para evitarmos imcompatibilidade, o atualizador automático só poderá ser usado nas versões oficiais! </p><p>Para obtê-las, Siga as instruções de como e onde no link "Baixando e Rodando" conforme abaixo:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> - + Automatic updating is not supported on the current platform. Atualizções automáticas não são suportadas na plataforma atual. MemoryCardSettingsWidget + + + + All Memory Card Types (*.mcd *.mcr *.mc) + Todos os Tipos de MC (*.mcd *.mcr *.mc) + Shared Settings @@ -1937,25 +3026,216 @@ This will download approximately 4 megabytes over your current internet connecti Escolha o caminho para os Cartões de Memória + + MemoryCardType + + + No Memory Card + Sem Cartão de Memória + + + + Shared Between All Games + Compartrilhada Entre Jogos + + + + Separate Card Per Game (Game Code) + Separar Cartão Por Jogo (Cód. Jogo) + + + + Separate Card Per Game (Game Title) + Separar Cartão Por Jogo (Titulo. Jogo) + + + + NamcoGunCon + + + Trigger + Gatilho + + + + A + A + + + + B + B + + + + OSDMessage + + + System reset. + Sistema Reiniciado. + + + + Loading state from '%s'... + Carregando estado de '%s'... + + + + Loading state from '%s' failed. Resetting. + Carregamento de estado '%s'.falhou. Reiniciando. + + + + Saving state to '%s' failed. + Salvando estado para '%s' falhou. + + + + State saved to '%s'. + Estado salvo para '%s'. + + + + PGXP is incompatible with the software renderer, disabling PGXP. + PGXP é incompatível com o rederizador por software, desativando PGXP. + + + + PGXP CPU mode is incompatible with the recompiler, using Cached Interpreter instead. + PGXP em modo CPU não é compatível com o recompilador, mudando para Interpretador armazenado. + + + + Speed limiter enabled. + Limitador de Velocidade Ativado. + + + + Speed limiter disabled. + Limitador de Velocidade Desativado. + + + + Volume: Muted + Volume: Mudo + + + + + + Volume: %d%% + Volume: %d%% + + + + Loaded input profile from '%s' + Perfil de controle carregado de '%s' + + + + Failed to save screenshot to '%s' + Falha ao salvar captura para '%s' + + + + Screenshot saved to '%s'. + Captura de tela salva para '%s'. + + + + CPU interpreter forced by game settings. + Configurado o interpretador por CPU pela configuração personalizada. + + + + Software renderer forced by game settings. + Renderização por software forçada pelas configurações personalizadas. + + + + Interlacing forced by game settings. + Entrelaçamento forçado pela configuração personalizada. + + + + True color disabled by game settings. + Efeito Cor real (true color) desativada pelas configs. personalizadas. + + + + Upscaling disabled by game settings. + Escalonamento desativado pelas configurações personalizadas. + + + + Scaled dithering disabled by game settings. + Dithering escalonado desativado pelas configurações personalizadas. + + + + Widescreen disabled by game settings. + Visão Panoramica desativada pelas configurações. + + + + Forcing NTSC Timings disallowed by game settings. + Temporizadores NTSC não permitidos pela configuração personalizada. + + + + PGXP geometry correction disabled by game settings. + Correção geométrica desativada pelas configurações personalizadas. + + + + PGXP culling disabled by game settings. + Correção de curvas desativada pela configuração personalizada. + + + + PGXP vertex cache forced by game settings. + Vertice Armazenado forçado pelas configurações personalizadas. + + + + PGXP CPU mode forced by game settings. + PGXP em modo CPU forçado pelas configurações personalizadas. + + + + Controller %u changed to digital by game settings. + Controle %u mudado para modo analogico pela configuração personalizada. + + + + Recompiler memory exceptions forced by game settings. + Exeções de RAM forçada pelas configurações. + + + + Recompiler ICache forced by game settings. + Recompilador ICache forçado pelas configurações. + + QObject - + DuckStation Error Erro no Duckstation - + Failed to initialize host interface. Cannot continue. Falha ao Iniciar Interface. Não é possivel Continuar. - + Failed to open URL Falha ao abrir Página - + Failed to open URL. The URL was: %1 @@ -1965,62 +3245,62 @@ The URL was: %1 QtHostInterface - + Game Save %1 (%2) Jogo Salvo %1 (%2) - + Game Save %1 (Empty) Jogo Salvo %1 (Vazio) - + Global Save %1 (%2) Compartimento Global %1 (%2) - + Global Save %1 (Empty) Compartimento Global %1 (Vazio) - + Resume Resumir - + Load State Carregar Estado - + Resume (%1) Resumir (%1) - + %1 Save %2 (%3) %1 Salvo %2 (%3) - + Game Jogo - + Delete Save States... Apagar Jogos Salvos... - + Confirm Save State Deletion Confirma deleção de Estado Salvo - + Are you sure you want to delete all save states for %1? The saves will not be recoverable. @@ -2100,7 +3380,7 @@ The saves will not be recoverable. Audio Settings - Configurações de Aúdio + Configurações de Áudio @@ -2145,7 +3425,7 @@ The saves will not be recoverable. <strong>Audio Settings</strong><hr>These options control the audio output of the console. Mouse over an option for additional information. - <strong>Configurações de Aúdio</strong><hr>Estas opções controlam a saída do som no emulador. passe o ponteiro do mouse para mais informações. + <strong>Configurações de Áudio</strong><hr>Estas opções controlam a saída do som no emulador. passe o ponteiro do mouse para mais informações. @@ -2158,4 +3438,37 @@ The saves will not be recoverable. Recomendado + + System + + + Save state is incompatible: expecting version %u but state is version %u. + Estado salvo incompatível: versão do mesmo esperada %u não a versão %u. + + + + Failed to open CD image from save state: '%s'. + Falha ao abrir estado salvo: '%s'. + + + + Per-game memory card cannot be used for slot %u as the running game has no code. Using shared card instead. + Caminho para o Cartão de Memória no compartimento %u não pôde ser usado pois o jogo iniciado não possui um cód. válido. Será usado cartão compartilhado. + + + + Per-game memory card cannot be used for slot %u as the running game has no title. Using shared card instead. + Caminho para o Cartão de Memória no compartimento %u não pôde ser usado pois o jogo iniciado não possui um nome. válido. Será usado cartão compartilhado. + + + + Memory card path for slot %u is missing, using default. + Caminho para o Cartão de Memória %u incorreto, usando o padrão. + + + + Game changed, reloading memory cards. + Jogo trocado, recarregando Cartões de Memória. + + diff --git a/src/duckstation-qt/translations/duckstation-qt_zh-cn.ts b/src/duckstation-qt/translations/duckstation-qt_zh-cn.ts index 06a36d008..a77ee4c53 100644 --- a/src/duckstation-qt/translations/duckstation-qt_zh-cn.ts +++ b/src/duckstation-qt/translations/duckstation-qt_zh-cn.ts @@ -128,21 +128,194 @@ - + Show Debug Menu + + + + + Use Debug Host GPU Device 使用调试主机GPU设备 - + Unchecked 不勾选 - + Enables the usage of debug devices and shaders for rendering APIs which support them. Should only be used when debugging the emulator. 允许使用调试设备和着色器渲染支持它们的API。只应在调试模拟器时使用。 + + AnalogController + + + + Controller %u switched to analog mode. + + + + + + Controller %u switched to digital mode. + + + + + Controller %u is locked to analog mode by the game. + + + + + Controller %u is locked to digital mode by the game. + + + + + LeftX + + + + + LeftY + + + + + RightX + + + + + RightY + + + + + Up + + + + + Down + + + + + Left + + + + + Right + + + + + Select + + + + + Start + 开始 + + + + Triangle + + + + + Cross + + + + + Circle + + + + + Square + + + + + L1 + + + + + L2 + + + + + R1 + + + + + R2 + + + + + L3 + + + + + R3 + + + + + Analog + + + + + Enable Analog Mode on Reset + 重启时启用模拟模式 + + + + Automatically enables analog mode when the console is reset/powered on. + + + + + Analog Axis Scale + 模拟摇杆灵敏度 + + + + Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. + + + + + AudioBackend + + + Null (No Output) + + + + + Cubeb + + + + + SDL + + + AudioSettingsWidget @@ -172,11 +345,13 @@ + Sync To Output 同步到输出 + Start Dumping On Boot 启动时开始转储 @@ -192,7 +367,7 @@ - + Mute 静音 @@ -202,68 +377,72 @@ 100% - + Audio Backend 音频后端 - + The audio backend determines how frames produced by the emulator are submitted to the host. Cubeb provides the lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio output. 音频后端决定如何将模拟器生成的帧提交到主机。Cubeb提供了最低的延迟, 如果遇到问题, 请尝试SDL后端。空后端禁用所有主机音频输出。 - + Buffer Size 缓存大小 - + The buffer size determines the size of the chunks of audio which will be pulled by the host. Smaller values reduce the output latency, but may cause hitches if the emulation speed is inconsistent. Note that the Cubeb backend uses smaller chunks regardless of this value, so using a low value here may not significantly change latency. 缓冲区大小决定主机将要拉入的音频块的大小。较小的值可减少输出延迟, 但如果仿真速度不一致, 则可能会导致挂起。请注意, Cubeb后端使用更小的块, 而不管这个值如何, 因此在这里使用较低的值可能不会显著改变延迟。 - + Checked 勾选 - Throttles the emulation speed based on the audio backend pulling audio frames. Sync will automatically be disabled if not running at 100% speed. - 根据音频后端拉取音频帧来限制模拟速度。如果没有以100%的速度运行, 同步将自动禁用。 + 根据音频后端拉取音频帧来限制模拟速度。如果没有以100%的速度运行, 同步将自动禁用。 - - + + Throttles the emulation speed based on the audio backend pulling audio frames. This helps to remove noises or crackling if emulation is too fast. Sync will automatically be disabled if not running at 100% speed. + + + + + Unchecked 不勾选 - + Start dumping audio to file as soon as the emulator is started. Mainly useful as a debug option. 一旦模拟器启动, 就开始将音频转储到文件中。主要用作调试选项。 - + Volume 音量 - + Controls the volume of the audio played on the host. Values are in percentage. 控制主机上播放的音频的音量。值以百分比表示。 - + Prevents the emulator from producing any audible sound. 防止模拟器产生任何可听见的声音。 - + Maximum latency: %1 frames (%2ms) 最大延迟: %1帧 (%2ms) - + %1% %1% @@ -272,8 +451,8 @@ AutoUpdaterDialog - - + + Automatic Updater 自动更新程序 @@ -303,46 +482,105 @@ 下载并安装 - + Skip This Update 跳过这个更新 - + Remind Me Later 稍后提醒我 - + Updater Error 更新错误 - + No updates are currently available. Please try again later. 当前没有可用的更新。请稍后再试。 - + Current Version: %1 (%2) 当前版本: %1 (%2) - + New Version: %1 (%2) 新版本: %1 (%2) - + + Loading... + + + + Downloading %1... 下载中 %1... - + Cancel 取消 + + CPUExecutionMode + + + Intepreter (Slowest) + 解释器 (最慢) + + + + Cached Interpreter (Faster) + 缓存解释器 (较快) + + + + Recompiler (Fastest) + 重编译器 (最快) + + + + CommonHostInterface + + + Are you sure you want to stop emulation? + + + + + The current state will be saved. + + + + + ConsoleRegion + + + Auto-Detect + + + + + NTSC-J (Japan) + + + + + NTSC-U (US) + + + + + PAL (Europe, Australia) + + + ConsoleSettingsWidget @@ -367,7 +605,7 @@ - + Fast Boot 快速启动 @@ -412,141 +650,333 @@ 将图像预加载到内存 - - + + Unchecked 不勾选 - + Patches the BIOS to skip the console's boot animation. Does not work with all games, but usually safe to enabled. 对BIOS应用补丁以跳过主机的启动动画, 不适用于所有游戏, 但通常可以安全启用。 - + Preload Image to RAM 将图像预加载到内存 - - Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. - 将游戏图像加载到内存中。对于可能在游戏过程中变得不可靠的网络路径非常有用。 + + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some cases also eliminates stutter when games initiate audio track playback. + - + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. + 将游戏图像加载到内存中。对于可能在游戏过程中变得不可靠的网络路径非常有用。 + + + Select BIOS Image 选择BIOS文件 + + ControllerInterface + + + None + + + + + SDL + + + + + XInput + + + ControllerSettingsWidget - + Controller Type: 控制器类型: - + Load Profile 读取配置 - + Save Profile 保存配置 - + Clear All 清除全部 - + Clear Bindings 清除绑定 - + Are you sure you want to clear all bound controls? This can not be reversed. 确实要清除所有绑定控件吗?这是无法撤消的。 - - + + Rebind All 全部重新绑定 - + Are you sure you want to rebind all controls? All currently-bound controls will be irreversibly cleared. Rebinding will begin after confirmation. 是否确实要重新绑定所有控件?所有当前绑定的控件都将被不可逆转地清除。确认后将开始重新绑定。 - + Port %1 接口%1 - + Button Bindings: 按钮绑定: - + Axis Bindings: 轴绑定: - + Rumble Rumble - - - + + + Browse... 浏览... - + Select File 选择文件 - - + + Select path to input profile ini 选择输入配置文件ini的路径 - + New... 新建... - - + + Enter Input Profile Name 输入输入配置文件名 - - + + Error 错误 - + No name entered, input profile was not saved. 未输入名称, 未保存输入配置文件。 - + No path selected, input profile was not saved. 未选择路径, 未保存输入配置文件。 + + ControllerType + + + None + + + + + Digital Controller + + + + + Analog Controller (DualShock) + + + + + Namco GunCon + + + + + PlayStation Mouse + + + + + NeGcon + + + + + DigitalController + + + Up + + + + + Down + + + + + Left + + + + + Right + + + + + Select + + + + + Start + 开始 + + + + Triangle + + + + + Cross + + + + + Circle + + + + + Square + + + + + L1 + + + + + L2 + + + + + R1 + + + + + R2 + + + + + DiscRegion + + + NTSC-J (Japan) + + + + + NTSC-U (US) + + + + + PAL (Europe, Australia) + + + + + Other + + + + + DisplayCropMode + + + None + + + + + Only Overscan Area + 仅限过扫描区域 + + + + All Borders + + + + + GPURenderer + + + Hardware (D3D11) + + + + + Hardware (Vulkan) + + + + + Hardware (OpenGL) + + + + + Software + + + GPUSettingsWidget @@ -586,19 +1016,19 @@ - + Linear Upscaling 线性放大 - + Integer Upscaling 整数放大 - + VSync 垂直同步 @@ -626,7 +1056,7 @@ - + Disable Interlacing (force progressive render/scan) 禁用隔行扫描 (强制渐进式渲染/扫描) @@ -678,76 +1108,79 @@ 顶点缓存 - + + + CPU Mode + + + + Renderer 渲染器 - Chooses the backend to use for rendering tasks for the the console GPU. Depending on your system and hardware, Direct3D 11 and OpenGL hardware backends may be available. The software renderer offers the best compatibility, but is the slowest and does not offer any enhancements. - 选择用于呈现控制台GPU任务的后端。根据您的系统和硬件, Direct3D 11和OpenGL硬件后端可能可用。软件渲染器提供了最好的兼容性, 但速度最慢, 并且不提供任何增强功能。 + 选择用于呈现控制台GPU任务的后端。根据您的系统和硬件, Direct3D 11和OpenGL硬件后端可能可用。软件渲染器提供了最好的兼容性, 但速度最慢, 并且不提供任何增强功能。 - + Adapter 适配器 - - + + (Default) (默认) - If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware renderers. This option is only supported in Direct3D and Vulkan, OpenGL will always use the default device. - 如果系统包含多个GPU或适配器, 则可以选择要将哪个GPU用于硬件渲染器。此选项仅在Direct3D和Vulkan中受支持, OpenGL将始终使用默认设备。 + 如果系统包含多个GPU或适配器, 则可以选择要将哪个GPU用于硬件渲染器。此选项仅在Direct3D和Vulkan中受支持, OpenGL将始终使用默认设备。 - + Aspect Ratio 高宽比 - + Changes the aspect ratio used to display the console's output to the screen. The default is 4:3 which matches a typical TV of the era. 更改用于在屏幕上显示控制台输出的纵横比。默认值是4:3, 与那个时代的典型电视相匹配。 - + Crop Mode 裁剪模式 - + Only Overscan Area 仅限过扫描区域 - Determines how much of the area typically not visible on a consumer TV set to crop/hide. Some games display content in the overscan area, or use it for screen effects and may not display correctly with the All Borders setting. Only Overscan offers a good compromise between stability and hiding black borders. - 确定用户电视机上通常不可见的区域有多少要裁剪/隐藏。有些游戏在“过扫描”区域显示内容, 或将其用于屏幕效果, 在“所有边框”设置下可能无法正确显示。只有过度扫描才能在稳定和隐藏黑边界之间提供一个很好的折衷方案。 + 确定用户电视机上通常不可见的区域有多少要裁剪/隐藏。有些游戏在“过扫描”区域显示内容, 或将其用于屏幕效果, 在“所有边框”设置下可能无法正确显示。只有过度扫描才能在稳定和隐藏黑边界之间提供一个很好的折衷方案。 - - + + + Unchecked 不勾选 - Forces the rendering and display of frames to progressive mode. This removes the "combing" effect seen in 480i games by rendering them in 480p. Not all games are compatible with this option, some require interlaced rendering or render interlaced internally. Usually safe to enable. - 强制以渐进模式渲染和显示帧。这将通过在480p中渲染480i游戏中的效果来移除它们。并非所有的游戏都与此选项兼容, 有些游戏需要隔行渲染或内部渲染。通常可以安全启用。 + 强制以渐进模式渲染和显示帧。这将通过在480p中渲染480i游戏中的效果来移除它们。并非所有的游戏都与此选项兼容, 有些游戏需要隔行渲染或内部渲染。通常可以安全启用。 - - + + @@ -755,19 +1188,16 @@ 勾选 - Uses bilinear texture filtering when displaying the console's framebuffer to the screen. Disabling filtering will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. The option will be less noticable the higher the resolution scale. - 在将控制台帧缓冲区显示到屏幕时使用双线性纹理过滤。禁用过滤将生成更清晰、更块状/像素化的图像。启用将使图像平滑。分辨率越高, 选项就越不引人注意。 + 在将控制台帧缓冲区显示到屏幕时使用双线性纹理过滤。禁用过滤将生成更清晰、更块状/像素化的图像。启用将使图像平滑。分辨率越高, 选项就越不引人注意。 - Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games. - 向显示区域添加填充, 以确保主机上的像素与控制台中的像素之间的比率为整数。在一些2D游戏中可能会产生更清晰的图像。 + 向显示区域添加填充, 以确保主机上的像素与控制台中的像素之间的比率为整数。在一些2D游戏中可能会产生更清晰的图像。 - Enables synchronization with the host display when possible. Enabling this option will provide better frame pacing and smoother motion with fewer duplicated frames. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). - 如果可能, 启用与主机显示的同步。启用此选项将以更少的重复帧提供更好的帧间距和更平滑的运动。当不可能时(例如, 以非100%速度运行), 垂直同步将自动禁用。 + 如果可能, 启用与主机显示的同步。启用此选项将以更少的重复帧提供更好的帧间距和更平滑的运动。当不可能时(例如, 以非100%速度运行), 垂直同步将自动禁用。 @@ -775,9 +1205,8 @@ 分辨率缩放 - Enables the upscaling of 3D objects rendered to the console's framebuffer. Only applies to the hardware backends. This option is usually safe, with most games looking fine at higher resolutions. Higher resolutions require a more powerful GPU. - 允许放大渲染到控制台帧缓冲区的三维对象。仅适用于硬件后端。这个选项通常是安全的, 大多数游戏在更高的分辨率下看起来很好。更高的分辨率需要更强大的GPU。 + 允许放大渲染到控制台帧缓冲区的三维对象。仅适用于硬件后端。这个选项通常是安全的, 大多数游戏在更高的分辨率下看起来很好。更高的分辨率需要更强大的GPU。 @@ -785,24 +1214,80 @@ 强制输出到控制台的帧缓冲区的颜色精度使用每个通道的全部8位精度。这会产生更好看的渐变, 但代价是使某些颜色看起来稍有不同。禁用该选项也会启用色彩抖动, 这会通过在这些像素周围应用图案来减少颜色之间的过渡。大多数游戏都与此选项兼容, 但也有一部分游戏不支持此选项, 并且在启用该选项后会产生中断效果。仅适用于硬件渲染器。 - Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. Usually safe to enable, and only supported by the hardware renderers. - 将着色彩抖动式缩放到模拟GPU的分辨率级别。这使得着色彩抖动式在更高分辨率下变得不那么明显。通常可以安全启用, 并且仅由硬件渲染器支持。 + 将着色彩抖动式缩放到模拟GPU的分辨率级别。这使得着色彩抖动式在更高分辨率下变得不那么明显。通常可以安全启用, 并且仅由硬件渲染器支持。 + + + Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. For variable frame rate games, it may not affect the speed. + 当游戏机处于PAL模式时使用NTSC帧计时, 强制PAL游戏以60hz运行。对于大多数速度与帧速率相关的游戏, 这将导致游戏运行速度大约快17%。对于可变帧速率游戏, 它可能不会影响速度。 + + + Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. + 利用双线性滤波消除三维物体上放大纹理的块状。会对更高分辨率的尺度产生更大的影响。目前, 在许多游戏中, 这个选项会在对象周围产生瑕疵, 需要进一步的工作。仅适用于硬件渲染器。 + + + Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <b><u>May not be compatible with all games.</u></b> + 将屏幕空间中的顶点位置缩放到宽屏幕的纵横比, 基本上将3D游戏中的视野从4:3增加到16:9。<br>对于2D游戏, 或使用预渲染背景的游戏, 此增强将无法按预期工作。<b><u>可能不兼容所有游戏。</u></b> + + + + Chooses the backend to use for rendering the console/game visuals. <br>Depending on your system and hardware, Direct3D 11 and OpenGL hardware backends may be available. <br>The software renderer offers the best compatibility, but is the slowest and does not offer any enhancements. + + + + + If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default device. + + + + + Determines how much of the area typically not visible on a consumer TV set to crop/hide. <br>Some games display content in the overscan area, or use it for screen effects. <br>May not display correctly with the "All Borders" setting. "Only Overscan" offers a good compromise between stability and hiding black borders. + + + + + Forces the rendering and display of frames to progressive mode. <br>This removes the "combing" effect seen in 480i games by rendering them in 480p. Usually safe to enable.<br> <b><u>May not be compatible with all games.</u></b> + + + + + Uses bilinear texture filtering when displaying the console's framebuffer to the screen. <br>Disabling filtering will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. <br>The option will be less noticable the higher the resolution scale. + + + + + Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. <br>May result in a sharper image in some 2D games. + + + + + Enable this option to match DuckStation's refresh rate with your current monitor or screen. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). + + + + + Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies to the hardware backends. <br>This option is usually safe, with most games looking fine at higher resolutions. Higher resolutions require a more powerful GPU. + + + + + Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. <br>Usually safe to enable, and only supported by the hardware renderers. + - Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. For variable frame rate games, it may not affect the speed. - 当游戏机处于PAL模式时使用NTSC帧计时, 强制PAL游戏以60hz运行。对于大多数速度与帧速率相关的游戏, 这将导致游戏运行速度大约快17%。对于可变帧速率游戏, 它可能不会影响速度。 + Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. <br>For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. <br>For variable frame rate games, it may not affect the speed. + - Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. - 利用双线性滤波消除三维物体上放大纹理的块状。会对更高分辨率的尺度产生更大的影响。目前, 在许多游戏中, 这个选项会在对象周围产生瑕疵, 需要进一步的工作。仅适用于硬件渲染器。 + Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. <br>Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. + - Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <b><u>May not be compatible with all games.</u></b> - 将屏幕空间中的顶点位置缩放到宽屏幕的纵横比, 基本上将3D游戏中的视野从4:3增加到16:9。<br>对于2D游戏, 或使用预渲染背景的游戏, 此增强将无法按预期工作。<b><u>可能不兼容所有游戏。</u></b> + Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <br><b><u>May not be compatible with all games.</u></b> + @@ -825,70 +1310,108 @@ 当通过内存跟踪顶点失败时, 使用屏幕坐标作为备用。可提高PGXP兼容性。 - + + Tries to track vertex manipulation through the CPU. Some games require this option for PGXP to be effective. Very slow, and incompatible with the recompiler. + + + + (for 720p) (适合720p) - + (for 1080p) (适合1080p) - + (for 1440p) (适合1440p) - + (for 4K) (适合4K) - + Automatic based on window size 自动根据窗口大小 - + %1x%2 %1x%2 + + GameListCompatibilityRating + + + Unknown + + + + + Doesn't Boot + + + + + Crashes In Intro + + + + + Crashes In-Game + + + + + Graphical/Audio Issues + + + + + No Issues + + + GameListModel - + Type 类型 - + Code 编号 - + Title 标题 - + File Title 文件标题 - + Size 大小 - + Region 区域 - + Compatibility 兼容性 @@ -1021,97 +1544,162 @@ This will download approximately 4 megabytes over your current internet connecti Dialog - + + Properties + + + + Image Path: 文件路径: - + Game Code: 游戏编号: - + Title: 标题: - + Region: 区域: - + Compatibility: 兼容性: - + Upscaling Issues: 放大错误: - + Comments: 备注: - + Version Tested: 已测试版本: - + Set to Current 设置为当前 - + Tracks: 轨道: - + # # - + Mode 模式 - + Start 开始 - + Length 长度 - + Hash 哈希 - + + User Settings + + + + + GPU Settings + 视频设置 + + + + Crop Mode: + + + + + Aspect Ratio: + 纵横比: + + + + Widescreen Hack + 宽屏补丁 + + + + Controller Settings + 控制器设置 + + + + Controller 1 Type: + + + + + Controller 2 Type: + + + + + Compatibility Settings + + + + + Traits + + + + + Overrides + + + + + Display Active Offset: + + + + Compute Hashes 计算哈希 - + Verify Dump 验证转储 - + Export Compatibility Info 导出兼容性信息 - + Close 关闭 @@ -1121,31 +1709,106 @@ This will download approximately 4 megabytes over your current internet connecti 游戏属性 - %1 - %1 - %1 + %1 - + + + + + (unchanged) + + + + <not computed> <不兼容> - + Not yet implemented 尚未实施 - + Compatibility Info Export 兼容性信息导出 - + Press OK to copy to clipboard. 按“确定”复制到剪贴板。 + + GameSettingsTrait + + + Force Interpreter + + + + + Force Software Renderer + + + + + Enable Interlacing + + + + + Disable True Color + + + + + Disable Upscaling + + + + + Disable Scaled Dithering + + + + + Disable Widescreen + + + + + Disable PGXP + + + + + Disable PGXP Culling + + + + + Enable PGXP Vertex Cache + + + + + Enable PGXP CPU Mode + + + + + Force Digital Controller + + + + + Enable Recompiler Memory Exceptions + 启用重新编译内存异常 + + GeneralSettingsWidget @@ -1159,216 +1822,366 @@ This will download approximately 4 megabytes over your current internet connecti 行为 - - + + Pause On Start 开始时暂停 - - + + Confirm Power Off 确认关机 - - + + Save State On Exit 退出时保存即时存档 - - + + Load Devices From Save States 从即时存档读取设备 - - + + Start Fullscreen 全屏启动 - - + + Render To Main Window 渲染到主窗口 - - + + + Apply Per-Game Settings + + + + + Emulation Speed 模拟速度 - + 100% 100% - - + + Enable Speed Limiter 启用限速 - - + + Increase Timer Resolution 提高计时器分辨率 - + On-Screen Display 屏幕显示 - + Show Messages 显示消息 - - + + Show FPS 显示FPS - + Show Emulation Speed 显示模拟速度 - - + + Show VPS 显示VPS - - - - - - - + + Show Resolution + + + + + Miscellaneous + + + + + Controller Backend: + + + + + + + + + + + Checked 勾选 - + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. 确定按下热键时是否显示确认关闭模拟器/游戏的提示。 - + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. 关闭或退出时自动保存模拟器状态。然后你可以直接从你下次离开的地方继续。 - - - - - - - + + + + + + + Unchecked 不勾选 - + Automatically switches to fullscreen mode when a game is started. 游戏开始时自动切换到全屏模式。 - + Renders the display of the simulated console to the main window of the application, over the game list. If unchecked, the display will render in a separate window. 将模拟控制台的显示渲染到应用程序的主窗口, 显示在游戏列表上。如果未选中, 则显示将在单独的窗口中渲染。 - + Pauses the emulator when a game is started. 游戏开始时暂停模拟器。 - + When enabled, memory cards and controllers will be overwritten when save states are loaded. This can result in lost saves, and controller type mismatches. For deterministic save states, enable this option, otherwise leave disabled. 启用后, 记忆卡和控制器将在加载即时存档时被覆盖。这可能导致保存丢失, 以及控制器类型不匹配。对于确定性保存状态, 请启用此选项, 否则保持禁用状态。 - + + When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should leave this option enabled except when testing enhancements with incompatible games. + + + + Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will run as fast as possible, which may not be playable. 将模拟速度调节到上述选定速度。如果未选中, 模拟器将以最快的速度运行, 这可能无法播放。 - + Increases the system timer resolution when emulation is started to provide more accurate frame pacing. May increase battery usage on laptops. 在开始模拟时增加系统计时器分辨率, 以提供更精确的帧间距。可能会增加笔记本电脑的电池使用量。 - + Sets the target emulation speed. It is not guaranteed that this speed will be reached, and if not, the emulator will run as fast as it can manage. 设置目标模拟速度。不能保证达到这个速度, 如果不能, 模拟器将以它能管理的速度运行。 - + Show OSD Messages 显示屏幕消息 - + Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc. 在发生事件(如正在创建/读取即时存档、正在拍摄屏幕截图等)时显示屏幕显示消息。 - + Shows the internal frame rate of the game in the top-right corner of the display. 在显示屏的右上角显示游戏的内部帧速率。 - + Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display. 在显示屏右上角显示系统每秒显示的帧数(或垂直同步)。 - + Show Speed 显示速度 - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. 在显示屏右上角以百分比显示系统的当前模拟速度。 - - + + Controller Backend + + + + + Determines the backend which is used for controller input. Windows users may prefer to use XInput over SDL2 for compatibility. + + + + + Enable Discord Presence 启用Discord Presence - + Shows the game you are currently playing as part of your profile in Discord. 在Discord中显示您当前正在玩的游戏。 - - + + Enable Automatic Update Check 启用自动更新检查 - + Automatically checks for updates to the program on startup. Updates can be deferred until later or skipped entirely. 启动时自动检查程序的更新, 可以选择稍后更新或完全跳过。 - + %1% %1% + + Hotkeys + + + Fast Forward + + + + + Toggle Fast Forward + + + + + Toggle Fullscreen + + + + + Toggle Pause + + + + + Power Off System + + + + + Save Screenshot + + + + + Frame Step + + + + + Toggle Software Rendering + + + + + Toggle PGXP + + + + + Increase Resolution Scale + + + + + Decrease Resolution Scale + + + + + Load From Selected Slot + + + + + Save To Selected Slot + + + + + Select Previous Save Slot + + + + + Select Next Save Slot + + + + + Load Game State %u + + + + + Save Game State %u + + + + + Load Global State %u + + + + + Save Global State %u + + + + + Toggle Mute + + + + + Volume Up + + + + + Volume Down + + + InputBindingDialog @@ -1422,13 +2235,66 @@ This will download approximately 4 megabytes over your current internet connecti 按钮/轴... [%1] + + LogLevel + + + None + + + + + Error + 错误 + + + + Warning + + + + + Performance + + + + + Success + + + + + Information + 信息 + + + + Developer + + + + + Profile + + + + + Debug + + + + + Trace + + + MainWindow - - - + + + DuckStation DuckStation @@ -1439,363 +2305,408 @@ This will download approximately 4 megabytes over your current internet connecti - + Change Disc 换碟 - + + From Playlist... + + + + Load State 即时读档 - + Save State 即时存档 - + S&ettings 设置(&E) - + Theme 主题 - + Language 语言 - + &Help 帮助(&H) - + &Debug 调试(&D) - + Switch GPU Renderer 切换到GPU渲染器 - + Switch CPU Emulation Mode 切换到CPU模拟模式 - + + &View + + + + toolBar 工具栏 - + Start &Disc... 启动光盘(&D)... - + Start &BIOS 启动BIOS(&B) - + &Scan For New Games 扫描新游戏(&S) - + &Rescan All Games 重新扫描所有游戏(&R) - + Power &Off 关机(&O) - + &Reset 重启(&R) - + &Pause 暂停(&P) - + &Load State 即时读档(&L) - + &Save State 即时存档(&S) - + E&xit 退出(&X) - + C&onsole Settings... 主机设置(&O)... - + &Controller Settings... 控制器设置(&C)... - + &Hotkey Settings... 快捷键设置(&H)... - + &GPU Settings... 视频设置(&G)... - + Fullscreen 全屏 - + Resolution Scale 分辨率缩放 - + &GitHub Repository... GitHub库(&G)... - + &Issue Tracker... 问题反馈(&I)... - + &Discord Server... Discord服务器(&D)... - + Check for &Updates... 检查更新(&U)... - + &About... 关于(&A)... - + Change Disc... 换碟... - + Audio Settings... 音频设置... - + Game List Settings... 游戏列表设置... - + General Settings... 常规设置... - + Advanced Settings... 高级设置... - + Add Game Directory... 添加游戏路径... - + &Settings... 设置(&S)... - + From File... 从文件... - + From Game List... 从列表... - + Remove Disc 取出光盘 - + Resume State 恢复状态 - + Global State 全局状态 - + Show VRAM 显示显存 - + Dump CPU to VRAM Copies 将CPU转存到显存拷贝 - + Dump VRAM to CPU Copies 将显存转存到CPU拷贝 - + Dump Audio 导出音频 - + + Dump RAM... + + + + Show GPU State 显示GPU状态 - + Show CDROM State 显示光盘状态 - + Show SPU State 显示SPU状态 - + Show Timers State 显示计时器状态 - + Show MDEC State 显示MDEC状态 - + &Screenshot 截图(&S) - + &Memory Card Settings... 记忆卡设置(&M)... - + Resume 恢复 - + Resumes the last save state created. 恢复上次创建的保存状态。 - + + &Toolbar + + + + + &Status Bar + + + + + &Game List + + + + + System &Display + + + + + All File Types (*.bin *.img *.cue *.chd *.exe *.psexe *.psf);;Single-Track Raw Images (*.bin *.img);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;PlayStation Executables (*.exe *.psexe);;Portable Sound Format Files (*.psf);;Playlists (*.m3u) + + + + Failed to create host display device context. 无法创建主机显示设备内容。 - - + + Select Disc Image 选择光盘镜像 - + Properties... 属性... - + Open Containing Directory... 打开所在目录... - + Default Boot 默认启动 - + Fast Boot 快速启动 - + Full Boot 完全启动 - + Add Search Directory... 添加搜索目录... - + Language changed. Please restart the application to apply. 语言已更改, 请重新启动应用程序以应用。 - + + Destination File + + + + Default 默认 - + DarkFusion 黑色 - + QDarkStyle 深色 - + Updater Error 更新程序错误 - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> <p>抱歉, 您正在尝试更新非GitHub官方版本的DuckStation版本。为防止不兼容, 自动更新程序仅在正式版本上启用。</p><p>要获取正式版本,请按照下面链接中的"下载并运行"下的说明进行操作:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a>lt;/p> - + Automatic updating is not supported on the current platform. 当前平台不支持自动更新。 @@ -1803,41 +2714,183 @@ This will download approximately 4 megabytes over your current internet connecti MemoryCardSettingsWidget - + + All Memory Card Types (*.mcd *.mcr *.mc) + 全部记忆卡类型 (*.mcd *.mcr *.mc) + + + + Shared Settings + 共用设置 + + + + + Use Single Card For Playlist + 为列表使用单个记忆卡 + + + + Checked + 勾选 + + + + When using a playlist (m3u) and per-game (title) memory cards, a single memory card will be used for all discs. If unchecked, a separate card will be used for each disc. + 当使用列表 (m3u) 并且每个游戏使用独立记忆卡 (标题) 存储卡时, 所有光盘都将使用单个记忆卡。如果未勾选, 则每个光盘将使用各自的记忆卡。 + + + If one of the "separate card per game" memory card modes is chosen, these memory cards will be saved to the memcards directory. 如果选择了"每个游戏独立记忆卡"的存储卡模式, 这些存储卡将保存到memcards目录中。 - + Open... 打开... - + Memory Card %1 记忆卡%1 - + Memory Card Type: 记忆卡类型: - + Browse... 浏览... - + Shared Memory Card Path: 共用记忆卡路径: - + Select path to memory card image 选择记忆卡文件的路径 + + MemoryCardType + + + No Memory Card + + + + + Shared Between All Games + + + + + Separate Card Per Game (Game Code) + + + + + Separate Card Per Game (Game Title) + + + + + NamcoGunCon + + + Trigger + + + + + A + + + + + B + + + + + OSDMessage + + + System reset. + + + + + Loading state from '%s'... + + + + + Loading state from '%s' failed. Resetting. + + + + + Saving state to '%s' failed. + + + + + State saved to '%s'. + + + + + PGXP is incompatible with the software renderer, disabling PGXP. + + + + + PGXP CPU mode is incompatible with the recompiler, using Cached Interpreter instead. + + + + + Speed limiter enabled. + + + + + Speed limiter disabled. + + + + + Volume: Muted + + + + + + + Volume: %d%% + + + + + Loaded input profile from '%s' + + + + + Failed to save screenshot to '%s' + + + + + Screenshot saved to '%s'. + + + QObject @@ -1851,12 +2904,12 @@ This will download approximately 4 megabytes over your current internet connecti 初始化主机接口失败, 无法继续。 - + Failed to open URL 无法打开URL - + Failed to open URL. The URL was: %1 @@ -1868,42 +2921,62 @@ URL: %1 QtHostInterface - + + Game Save %1 (%2) + + + + + Game Save %1 (Empty) + + + + + Global Save %1 (%2) + + + + + Global Save %1 (Empty) + + + + Resume 恢复 - + Load State 即时读档 - + Resume (%1) 恢复(%1) - + %1 Save %2 (%3) "%1保存%2 (%3) - + Game 游戏 - + Delete Save States... 删除即时存档... - + Confirm Save State Deletion 确认删除即时存档 - + Are you sure you want to delete all save states for %1? The saves will not be recoverable. @@ -2043,4 +3116,37 @@ The saves will not be recoverable. 推荐 + + System + + + Save state is incompatible: expecting version %u but state is version %u. + + + + + Failed to open CD image from save state: '%s'. + + + + + Per-game memory card cannot be used for slot %u as the running game has no code. Using shared card instead. + + + + + Per-game memory card cannot be used for slot %u as the running game has no title. Using shared card instead. + + + + + Memory card path for slot %u is missing, using default. + + + + + Game changed, reloading memory cards. + + + diff --git a/src/duckstation-qt/translations/set-language.bat b/src/duckstation-qt/translations/set-language.bat new file mode 100644 index 000000000..b1a0c3694 --- /dev/null +++ b/src/duckstation-qt/translations/set-language.bat @@ -0,0 +1,24 @@ +@echo off +echo Set your language +echo. +echo Examples: +echo en ^<-- English +echo en-au ^<-- Australian English +echo ^<-- Remove language setting +echo. +echo For the 369-1 2-digit language code +echo https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +echo. +echo If you require a country code as well (you probably don't) +echo https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes +echo. +echo.%lang% +set /p newlang="Enter language code: " +if defined newlang ( setx lang %newlang% ) +if defined lang if not defined newlang ( + echo Removing language setting... + setx lang "" 1>nul + reg delete HKCU\Environment /F /V lang 2>nul + reg delete "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /F /V lang 2>nul +) +pause diff --git a/src/duckstation-qt/translations/update-and-edit-language.bat b/src/duckstation-qt/translations/update-and-edit-language.bat new file mode 100644 index 000000000..7931b953a --- /dev/null +++ b/src/duckstation-qt/translations/update-and-edit-language.bat @@ -0,0 +1,12 @@ +@echo off + +if not defined lang (echo Please set your language first & pause & exit) + +set "linguist=..\..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin" +set context=..\ + +"%linguist%\lupdate.exe" %context% -ts duckstation-qt_%lang%.ts +pause + +cd "%linguist%" +start /B linguist.exe "%~dp0\duckstation-qt_%lang%.ts" diff --git a/src/duckstation-sdl/duckstation-sdl.ico b/src/duckstation-sdl/duckstation-sdl.ico index 082a9d8b8..f95b67991 100644 Binary files a/src/duckstation-sdl/duckstation-sdl.ico and b/src/duckstation-sdl/duckstation-sdl.ico differ diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 84b20e750..d1ac582f6 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -420,6 +420,8 @@ void SDLHostInterface::Shutdown() { DestroySystem(); + CommonHostInterface::Shutdown(); + if (m_display) { DestroyDisplay(); @@ -428,8 +430,6 @@ void SDLHostInterface::Shutdown() if (m_window) DestroySDLWindow(); - - CommonHostInterface::Shutdown(); } std::string SDLHostInterface::GetStringSettingValue(const char* section, const char* key, @@ -950,6 +950,11 @@ void SDLHostInterface::DrawDebugMenu() settings_changed |= ImGui::MenuItem("Show Timers State", nullptr, &debug_settings.show_timers_state); settings_changed |= ImGui::MenuItem("Show MDEC State", nullptr, &debug_settings.show_mdec_state); + ImGui::Separator(); + + settings_changed |= ImGui::MenuItem("Recompiler Memory Exceptions", nullptr, &m_settings_copy.cpu_recompiler_memory_exceptions); + settings_changed |= ImGui::MenuItem("Recompiler ICache", nullptr, &m_settings_copy.cpu_recompiler_icache); + if (settings_changed) { // have to apply it to the copy too, otherwise it won't save diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt index ddae45acc..710f81141 100644 --- a/src/frontend-common/CMakeLists.txt +++ b/src/frontend-common/CMakeLists.txt @@ -1,60 +1,95 @@ add_library(frontend-common - common_host_interface.cpp - common_host_interface.h - controller_interface.cpp - controller_interface.h - icon.cpp - icon.h - imgui_styles.cpp - imgui_styles.h - ini_settings_interface.cpp - ini_settings_interface.h opengl_host_display.cpp opengl_host_display.h - save_state_selector_ui.cpp - save_state_selector_ui.h vulkan_host_display.cpp vulkan_host_display.h ) -target_link_libraries(frontend-common PUBLIC core common imgui simpleini scmversion glad vulkan-loader) +target_link_libraries(frontend-common PUBLIC core common glad vulkan-loader) if(WIN32) target_sources(frontend-common PRIVATE d3d11_host_display.cpp d3d11_host_display.h - xinput_controller_interface.cpp - xinput_controller_interface.h ) target_link_libraries(frontend-common PRIVATE d3d11.lib dxgi.lib) endif() -if(SDL2_FOUND AND NOT BUILD_LIBRETRO_CORE) - target_sources(frontend-common PRIVATE - sdl_audio_stream.cpp - sdl_audio_stream.h - sdl_controller_interface.cpp - sdl_controller_interface.h - sdl_initializer.cpp - sdl_initializer.h +if(NOT BUILD_LIBRETRO_CORE) + target_sources(frontend-common PRIVATE + imgui_impl_opengl3.h + imgui_impl_opengl3.cpp + ) + + target_sources(frontend-common PRIVATE + imgui_impl_vulkan.h + imgui_impl_vulkan.cpp ) - target_compile_definitions(frontend-common PUBLIC "WITH_SDL2=1") - target_include_directories(frontend-common PRIVATE ${SDL2_INCLUDE_DIRS}) - target_link_libraries(frontend-common PRIVATE ${SDL2_LIBRARIES}) - # Copy bundled SDL2 to output on Windows. if(WIN32) - add_custom_command(TARGET frontend-common POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL2_DLL_PATH}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/SDL2.dll") + target_sources(frontend-common PRIVATE + imgui_impl_dx11.h + imgui_impl_dx11.cpp + ) endif() + + target_sources(frontend-common PRIVATE + common_host_interface.cpp + common_host_interface.h + controller_interface.cpp + controller_interface.h + game_list.cpp + game_list.h + game_settings.cpp + game_settings.h + icon.cpp + icon.h + imgui_styles.cpp + imgui_styles.h + ini_settings_interface.cpp + ini_settings_interface.h + save_state_selector_ui.cpp + save_state_selector_ui.h + ) + + if(WIN32) + target_sources(frontend-common PRIVATE + xinput_controller_interface.cpp + xinput_controller_interface.h + ) + endif() + + target_compile_definitions(frontend-common PRIVATE "WITH_IMGUI=1") + target_link_libraries(frontend-common PUBLIC imgui simpleini tinyxml2 scmversion) + + if(SDL2_FOUND) + target_sources(frontend-common PRIVATE + sdl_audio_stream.cpp + sdl_audio_stream.h + sdl_controller_interface.cpp + sdl_controller_interface.h + sdl_initializer.cpp + sdl_initializer.h + ) + target_compile_definitions(frontend-common PUBLIC "WITH_SDL2=1") + target_include_directories(frontend-common PRIVATE ${SDL2_INCLUDE_DIRS}) + target_link_libraries(frontend-common PRIVATE ${SDL2_LIBRARIES}) + + # Copy bundled SDL2 to output on Windows. + if(WIN32) + add_custom_command(TARGET frontend-common POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL2_DLL_PATH}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/SDL2.dll") + endif() + endif() + + if(ENABLE_DISCORD_PRESENCE AND NOT BUILD_LIBRETRO_CORE) + target_compile_definitions(frontend-common PUBLIC -DWITH_DISCORD_PRESENCE=1) + target_link_libraries(frontend-common PRIVATE discord-rpc) + endif() + + # Copy the provided data directory to the output directory. + add_custom_command(TARGET frontend-common POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/data" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" + ) endif() -if(ENABLE_DISCORD_PRESENCE AND NOT BUILD_LIBRETRO_CORE) - target_compile_definitions(frontend-common PUBLIC -DWITH_DISCORD_PRESENCE=1) - target_link_libraries(frontend-common PRIVATE discord-rpc) -endif() - -# Copy the provided data directory to the output directory. -add_custom_command(TARGET frontend-common POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/data" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" -) diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index b29b205fb..6d7b197d7 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -7,10 +7,8 @@ #include "common/string_util.h" #include "controller_interface.h" #include "core/cdrom.h" -#include "core/controller.h" #include "core/cpu_code_cache.h" #include "core/dma.h" -#include "core/game_list.h" #include "core/gpu.h" #include "core/host_display.h" #include "core/mdec.h" @@ -19,6 +17,7 @@ #include "core/spu.h" #include "core/system.h" #include "core/timers.h" +#include "game_list.h" #include "imgui.h" #include "ini_settings_interface.h" #include "save_state_selector_ui.h" @@ -369,7 +368,7 @@ bool CommonHostInterface::ParseCommandLineParameters(int argc, char* argv[], else { // find the game id, and get its save state path - std::string game_code = m_game_list->GetGameCodeForPath(boot_filename.c_str()); + std::string game_code = System::GetGameCodeForPath(boot_filename.c_str()); if (game_code.empty()) { Log_WarningPrintf("Could not identify game code for '%s', cannot load save state %d.", boot_filename.c_str(), @@ -461,6 +460,7 @@ void CommonHostInterface::UpdateControllerInterface() if (m_controller_interface) { + ClearInputMap(); m_controller_interface->Shutdown(); m_controller_interface.reset(); } @@ -829,6 +829,12 @@ void CommonHostInterface::AddOSDMessage(std::string message, float duration /*= m_osd_messages.push_back(std::move(msg)); } +void CommonHostInterface::ClearOSDMessages() +{ + std::unique_lock lock(m_osd_messages_lock); + m_osd_messages.clear(); +} + void CommonHostInterface::DrawOSDMessages() { constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | @@ -939,15 +945,20 @@ bool CommonHostInterface::HandleHostMouseEvent(HostMouseButton button, bool pres void CommonHostInterface::UpdateInputMap(SettingsInterface& si) { - m_keyboard_input_handlers.clear(); - m_mouse_input_handlers.clear(); - if (m_controller_interface) - m_controller_interface->ClearBindings(); - + ClearInputMap(); UpdateControllerInputMap(si); UpdateHotkeyInputMap(si); } +void CommonHostInterface::ClearInputMap() +{ + m_keyboard_input_handlers.clear(); + m_mouse_input_handlers.clear(); + m_controller_vibration_motors.clear(); + if (m_controller_interface) + m_controller_interface->ClearBindings(); +} + void CommonHostInterface::AddControllerRumble(u32 controller_index, u32 num_motors, ControllerRumbleCallback callback) { ControllerRumbleState rumble; @@ -1049,8 +1060,9 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) const auto axis_names = Controller::GetAxisNames(ctype); for (const auto& it : axis_names) { - const std::string& axis_name = it.first; - const s32 axis_code = it.second; + const std::string& axis_name = std::get(it); + const s32 axis_code = std::get(it); + const auto axis_type = std::get(it); const std::vector bindings = si.GetStringList(category, TinyString::FromFormat("Axis%s", axis_name.c_str())); @@ -1060,7 +1072,7 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) if (!SplitBinding(binding, &device, &axis)) continue; - AddAxisToInputMap(binding, device, axis, [this, controller_index, axis_code](float value) { + AddAxisToInputMap(binding, device, axis, axis_type, [this, controller_index, axis_code](float value) { if (System::IsShutdown()) return; @@ -1192,8 +1204,44 @@ bool CommonHostInterface::AddButtonToInputMap(const std::string& binding, const } bool CommonHostInterface::AddAxisToInputMap(const std::string& binding, const std::string_view& device, - const std::string_view& axis, InputAxisHandler handler) + const std::string_view& axis, Controller::AxisType axis_type, + InputAxisHandler handler) { + if (axis_type == Controller::AxisType::Half) + { + if (device == "Keyboard") + { + std::optional key_id = GetHostKeyCode(axis); + if (!key_id.has_value()) + { + Log_WarningPrintf("Unknown keyboard key in binding '%s'", binding.c_str()); + return false; + } + + m_keyboard_input_handlers.emplace(key_id.value(), std::move(handler)); + return true; + } + + if (device == "Mouse") + { + if (StringUtil::StartsWith(axis, "Button")) + { + const std::optional button_index = StringUtil::FromChars(axis.substr(6)); + if (!button_index.has_value()) + { + Log_WarningPrintf("Invalid button in mouse binding '%s'", binding.c_str()); + return false; + } + + m_mouse_input_handlers.emplace(static_cast(button_index.value()), std::move(handler)); + return true; + } + + Log_WarningPrintf("Malformed mouse binding '%s'", binding.c_str()); + return false; + } + } + if (StringUtil::StartsWith(device, "Controller")) { if (!m_controller_interface) @@ -1221,6 +1269,18 @@ bool CommonHostInterface::AddAxisToInputMap(const std::string& binding, const st return true; } + else if (StringUtil::StartsWith(axis, "Button") && axis_type == Controller::AxisType::Half) + { + const std::optional button_index = StringUtil::FromChars(axis.substr(6)); + if (!button_index || + !m_controller_interface->BindControllerButtonToAxis(*controller_index, *button_index, std::move(handler))) + { + Log_WarningPrintf("Failed to bind controller button '%s' to axis", binding.c_str()); + return false; + } + + return true; + } Log_WarningPrintf("Malformed controller binding '%s' in button", binding.c_str()); return false; @@ -1351,6 +1411,8 @@ void CommonHostInterface::RegisterGraphicsHotkeys() if (g_settings.gpu_pgxp_enable) PGXP::Initialize(); + else + PGXP::Shutdown(); // we need to recompile all blocks if pgxp is toggled on/off if (g_settings.IsUsingCodeCache()) @@ -1527,7 +1589,7 @@ void CommonHostInterface::ClearAllControllerBindings(SettingsInterface& si) si.DeleteValue(section_name, button.first.c_str()); for (const auto& axis : Controller::GetAxisNames(ctype)) - si.DeleteValue(section_name, axis.first.c_str()); + si.DeleteValue(section_name, std::get(axis).c_str()); if (Controller::GetVibrationMotorCount(ctype) > 0) si.DeleteValue(section_name, "Rumble"); @@ -1571,8 +1633,8 @@ void CommonHostInterface::ApplyInputProfile(const char* profile_path, SettingsIn for (const auto& axis : Controller::GetAxisNames(*ctype)) { - const auto key_name = TinyString::FromFormat("Axis%s", axis.first.c_str()); - si.DeleteValue(section_name, axis.first.c_str()); + const auto key_name = TinyString::FromFormat("Axis%s", std::get(axis).c_str()); + si.DeleteValue(section_name, std::get(axis).c_str()); const std::vector bindings = profile.GetStringList(section_name, key_name); for (const std::string& binding : bindings) si.AddToStringList(section_name, key_name, binding.c_str()); @@ -1630,7 +1692,7 @@ bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInt for (const auto& axis : Controller::GetAxisNames(ctype)) { - const auto key_name = TinyString::FromFormat("Axis%s", axis.first.c_str()); + const auto key_name = TinyString::FromFormat("Axis%s", std::get(axis).c_str()); const std::vector bindings = si.GetStringList(section_name, key_name); for (const std::string& binding : bindings) profile.AddToStringList(section_name, key_name, binding.c_str()); @@ -1745,8 +1807,7 @@ CommonHostInterface::GetExtendedSaveStateInfo(const char* game_code, s32 slot) if (header.version != SAVE_STATE_VERSION) { - ssi.title = StringUtil::StdStringFromFormat("Invalid version %u (expected %u)", header.version, header.magic, - SAVE_STATE_VERSION); + ssi.title = StringUtil::StdStringFromFormat("Invalid version %u (expected %u)", header.version, SAVE_STATE_VERSION); return ssi; } @@ -1758,6 +1819,7 @@ CommonHostInterface::GetExtendedSaveStateInfo(const char* game_code, s32 slot) if (header.screenshot_width > 0 && header.screenshot_height > 0 && header.screenshot_size > 0 && (static_cast(header.offset_to_screenshot) + static_cast(header.screenshot_size)) <= stream->GetSize()) { + stream->SeekAbsolute(header.offset_to_screenshot); ssi.screenshot_data.resize((header.screenshot_size + 3u) / 4u); if (stream->Read2(ssi.screenshot_data.data(), header.screenshot_size)) { @@ -1972,13 +2034,13 @@ void CommonHostInterface::GetGameInfo(const char* path, CDImage* image, std::str else { if (image) - *code = GameList::GetGameCodeForImage(image); + *code = System::GetGameCodeForImage(image); const GameListDatabaseEntry* db_entry = (!code->empty()) ? m_game_list->GetDatabaseEntryForCode(*code) : nullptr; if (db_entry) *title = db_entry->title; else - *title = GameList::GetTitleForPath(path); + *title = System::GetTitleForPath(path); } } diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 0c110dac5..9364df3b2 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -1,5 +1,6 @@ #pragma once #include "common/string.h" +#include "core/controller.h" #include "core/host_interface.h" #include #include @@ -128,6 +129,7 @@ public: /// Adds OSD messages, duration is in seconds. void AddOSDMessage(std::string message, float duration = 2.0f) override; + void ClearOSDMessages(); /// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks /// such as compiling shaders when starting up. @@ -195,7 +197,8 @@ protected: virtual bool AddButtonToInputMap(const std::string& binding, const std::string_view& device, const std::string_view& button, InputButtonHandler handler); virtual bool AddAxisToInputMap(const std::string& binding, const std::string_view& device, - const std::string_view& axis, InputAxisHandler handler); + const std::string_view& axis, Controller::AxisType axis_type, + InputAxisHandler handler); virtual bool AddRumbleToInputMap(const std::string& binding, u32 controller_index, u32 num_motors); /// Reloads the input map from config. Callable from controller interface. @@ -217,6 +220,7 @@ protected: bool HandleHostKeyEvent(HostKeyCode code, bool pressed); bool HandleHostMouseEvent(HostMouseButton button, bool pressed); void UpdateInputMap(SettingsInterface& si); + void ClearInputMap(); void AddControllerRumble(u32 controller_index, u32 num_motors, ControllerRumbleCallback callback); void UpdateControllerRumble(); diff --git a/src/frontend-common/controller_interface.h b/src/frontend-common/controller_interface.h index ab877a72a..60e287f11 100644 --- a/src/frontend-common/controller_interface.h +++ b/src/frontend-common/controller_interface.h @@ -3,9 +3,9 @@ #include "core/types.h" #include #include -#include #include #include +#include class HostInterface; class Controller; @@ -54,6 +54,7 @@ public: virtual bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) = 0; virtual bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) = 0; + virtual bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) = 0; virtual void PollEvents() = 0; diff --git a/src/frontend-common/d3d11_host_display.cpp b/src/frontend-common/d3d11_host_display.cpp index e4d9ce613..80162f505 100644 --- a/src/frontend-common/d3d11_host_display.cpp +++ b/src/frontend-common/d3d11_host_display.cpp @@ -8,8 +8,10 @@ #include #ifndef LIBRETRO #include -#include -#include +#endif +#ifdef WITH_IMGUI +#include "imgui.h" +#include "imgui_impl_dx11.h" #endif Log_SetChannel(D3D11HostDisplay); @@ -154,15 +156,15 @@ bool D3D11HostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, { ID3D11ShaderResourceView* srv = const_cast(static_cast(texture_handle)); - ID3D11Resource* srv_resource; + ComPtr srv_resource; D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc; - srv->GetResource(&srv_resource); + srv->GetResource(srv_resource.GetAddressOf()); srv->GetDesc(&srv_desc); if (!m_readback_staging_texture.EnsureSize(m_context.Get(), width, height, srv_desc.Format, false)) return false; - m_readback_staging_texture.CopyFromTexture(m_context.Get(), srv_resource, 0, x, y, 0, 0, width, height); + m_readback_staging_texture.CopyFromTexture(m_context.Get(), srv_resource.Get(), 0, x, y, 0, 0, width, height); return m_readback_staging_texture.ReadPixels(m_context.Get(), 0, 0, width, height, out_data_stride / sizeof(u32), static_cast(out_data)); } @@ -298,7 +300,7 @@ bool D3D11HostDisplay::InitializeRenderDevice(std::string_view shader_cache_dire if (!CreateResources()) return false; -#ifndef LIBRETRO +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext() && !CreateImGuiContext()) return false; #endif @@ -308,7 +310,7 @@ bool D3D11HostDisplay::InitializeRenderDevice(std::string_view shader_cache_dire void D3D11HostDisplay::DestroyRenderDevice() { -#ifndef LIBRETRO +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) DestroyImGuiContext(); #endif @@ -532,9 +534,9 @@ void D3D11HostDisplay::DestroyResources() m_display_rasterizer_state.Reset(); } -#ifndef LIBRETRO bool D3D11HostDisplay::CreateImGuiContext() { +#ifdef WITH_IMGUI ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); @@ -542,14 +544,16 @@ bool D3D11HostDisplay::CreateImGuiContext() return false; ImGui_ImplDX11_NewFrame(); +#endif return true; } void D3D11HostDisplay::DestroyImGuiContext() { +#ifdef WITH_IMGUI ImGui_ImplDX11_Shutdown(); -} #endif +} bool D3D11HostDisplay::Render() { @@ -560,8 +564,10 @@ bool D3D11HostDisplay::Render() RenderDisplay(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) RenderImGui(); +#endif RenderSoftwareCursor(); @@ -570,8 +576,10 @@ bool D3D11HostDisplay::Render() else m_swap_chain->Present(BoolToUInt32(m_vsync), 0); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) ImGui_ImplDX11_NewFrame(); +#endif #else RenderDisplay(); RenderSoftwareCursor(); @@ -580,15 +588,13 @@ bool D3D11HostDisplay::Render() return true; } -#ifndef LIBRETRO - void D3D11HostDisplay::RenderImGui() { +#ifdef WITH_IMGUI ImGui::Render(); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); -} - #endif +} void D3D11HostDisplay::RenderDisplay() { diff --git a/src/frontend-common/d3d11_host_display.h b/src/frontend-common/d3d11_host_display.h index b10d8ed15..e992c8ac4 100644 --- a/src/frontend-common/d3d11_host_display.h +++ b/src/frontend-common/d3d11_host_display.h @@ -65,20 +65,17 @@ protected: virtual bool CreateResources(); virtual void DestroyResources(); -#ifndef LIBRETRO virtual bool CreateImGuiContext(); virtual void DestroyImGuiContext(); +#ifndef LIBRETRO bool CreateSwapChain(); bool CreateSwapChainRTV(); #endif void RenderDisplay(); void RenderSoftwareCursor(); - -#ifndef LIBRETRO void RenderImGui(); -#endif void RenderDisplay(s32 left, s32 top, s32 width, s32 height, void* texture_handle, u32 texture_width, s32 texture_height, s32 texture_view_x, s32 texture_view_y, s32 texture_view_width, diff --git a/src/frontend-common/frontend-common.vcxproj b/src/frontend-common/frontend-common.vcxproj index ebcae7ee0..22d7690d0 100644 --- a/src/frontend-common/frontend-common.vcxproj +++ b/src/frontend-common/frontend-common.vcxproj @@ -53,6 +53,9 @@ {3773f4cc-614e-4028-8595-22e08ca649e3} + + {933118a9-68c5-47b4-b151-b03c93961623} + {9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035} @@ -67,7 +70,12 @@ + + + + + @@ -82,7 +90,12 @@ + + + + + @@ -238,10 +251,10 @@ Level3 Disabled - WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 true @@ -266,10 +279,10 @@ Level3 Disabled - WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default false true @@ -297,10 +310,10 @@ Level3 Disabled - WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 true @@ -325,10 +338,10 @@ Level3 Disabled - WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default false true @@ -358,9 +371,9 @@ MaxSpeed true true - WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 false @@ -388,9 +401,9 @@ MaxSpeed true true - WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -420,9 +433,9 @@ MaxSpeed true true - WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 false @@ -450,9 +463,9 @@ MaxSpeed true true - WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + WITH_IMGUI=1;WITH_SDL2=1;WITH_DISCORD_PRESENCE=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\discord-rpc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 diff --git a/src/frontend-common/frontend-common.vcxproj.filters b/src/frontend-common/frontend-common.vcxproj.filters index d84627ceb..8b9148ca3 100644 --- a/src/frontend-common/frontend-common.vcxproj.filters +++ b/src/frontend-common/frontend-common.vcxproj.filters @@ -14,6 +14,11 @@ + + + + + @@ -29,6 +34,11 @@ + + + + + diff --git a/src/core/game_list.cpp b/src/frontend-common/game_list.cpp similarity index 81% rename from src/core/game_list.cpp rename to src/frontend-common/game_list.cpp index fef61d38b..58db28b40 100644 --- a/src/core/game_list.cpp +++ b/src/frontend-common/game_list.cpp @@ -1,5 +1,4 @@ #include "game_list.h" -#include "bios.h" #include "common/assert.h" #include "common/byte_stream.h" #include "common/cd_image.h" @@ -8,12 +7,13 @@ #include "common/log.h" #include "common/progress_callback.h" #include "common/string_util.h" -#include "host_interface.h" -#include "settings.h" +#include "core/bios.h" +#include "core/host_interface.h" +#include "core/settings.h" +#include "core/system.h" #include #include #include -#include #include #include #include @@ -36,238 +36,6 @@ const char* GameList::EntryCompatibilityRatingToString(GameListCompatibilityRati return names[static_cast(rating)]; } -std::string GameList::GetGameCodeForPath(const char* image_path) -{ - std::unique_ptr cdi = CDImage::Open(image_path); - if (!cdi) - return {}; - - return GetGameCodeForImage(cdi.get()); -} - -std::string GameList::GetGameCodeForImage(CDImage* cdi) -{ - ISOReader iso; - if (!iso.Open(cdi, 1)) - return {}; - - // Read SYSTEM.CNF - std::vector system_cnf_data; - if (!iso.ReadFile("SYSTEM.CNF", &system_cnf_data)) - return {}; - - // Parse lines - std::vector> lines; - std::pair current_line; - bool reading_value = false; - for (size_t pos = 0; pos < system_cnf_data.size(); pos++) - { - const char ch = static_cast(system_cnf_data[pos]); - if (ch == '\r' || ch == '\n') - { - if (!current_line.first.empty()) - { - lines.push_back(std::move(current_line)); - current_line = {}; - reading_value = false; - } - } - else if (ch == ' ' || (ch >= 0x09 && ch <= 0x0D)) - { - continue; - } - else if (ch == '=' && !reading_value) - { - reading_value = true; - } - else - { - if (reading_value) - current_line.second.push_back(ch); - else - current_line.first.push_back(ch); - } - } - - if (!current_line.first.empty()) - lines.push_back(std::move(current_line)); - - // Find the BOOT line - auto iter = std::find_if(lines.begin(), lines.end(), - [](const auto& it) { return StringUtil::Strcasecmp(it.first.c_str(), "boot") == 0; }); - if (iter == lines.end()) - return {}; - - // cdrom:\SCES_123.45;1 - std::string code = iter->second; - std::string::size_type pos = code.rfind('\\'); - if (pos != std::string::npos) - { - code.erase(0, pos + 1); - } - else - { - // cdrom:SCES_123.45;1 - pos = code.rfind(':'); - if (pos != std::string::npos) - code.erase(0, pos + 1); - } - - pos = code.find(';'); - if (pos != std::string::npos) - code.erase(pos); - - // SCES_123.45 -> SCES-12345 - for (pos = 0; pos < code.size();) - { - if (code[pos] == '.') - { - code.erase(pos, 1); - continue; - } - - if (code[pos] == '_') - code[pos] = '-'; - else - code[pos] = static_cast(std::toupper(code[pos])); - - pos++; - } - - return code; -} - -DiscRegion GameList::GetRegionForCode(std::string_view code) -{ - std::string prefix; - for (size_t pos = 0; pos < code.length(); pos++) - { - const int ch = std::tolower(code[pos]); - if (ch < 'a' || ch > 'z') - break; - - prefix.push_back(static_cast(ch)); - } - - if (prefix == "sces" || prefix == "sced" || prefix == "sles" || prefix == "sled") - return DiscRegion::PAL; - else if (prefix == "scps" || prefix == "slps" || prefix == "slpm" || prefix == "sczs" || prefix == "papx") - return DiscRegion::NTSC_J; - else if (prefix == "scus" || prefix == "slus") - return DiscRegion::NTSC_U; - else - return DiscRegion::Other; -} - -DiscRegion GameList::GetRegionFromSystemArea(CDImage* cdi) -{ - // The license code is on sector 4 of the disc. - u8 sector[CDImage::DATA_SECTOR_SIZE]; - if (!cdi->Seek(1, 4) || cdi->Read(CDImage::ReadMode::DataOnly, 1, sector) != 1) - return DiscRegion::Other; - - static constexpr char ntsc_u_string[] = " Licensed by Sony Computer Entertainment Amer ica "; - static constexpr char ntsc_j_string[] = " Licensed by Sony Computer Entertainment Inc."; - static constexpr char pal_string[] = " Licensed by Sony Computer Entertainment Euro pe"; - - // subtract one for the terminating null - if (std::equal(ntsc_u_string, ntsc_u_string + countof(ntsc_u_string) - 1, sector)) - return DiscRegion::NTSC_U; - else if (std::equal(ntsc_j_string, ntsc_j_string + countof(ntsc_j_string) - 1, sector)) - return DiscRegion::NTSC_J; - else if (std::equal(pal_string, pal_string + countof(pal_string) - 1, sector)) - return DiscRegion::PAL; - else - return DiscRegion::Other; -} - -DiscRegion GameList::GetRegionForImage(CDImage* cdi) -{ - DiscRegion system_area_region = GetRegionFromSystemArea(cdi); - if (system_area_region != DiscRegion::Other) - return system_area_region; - - std::string code = GetGameCodeForImage(cdi); - if (code.empty()) - return DiscRegion::Other; - - return GetRegionForCode(code); -} - -std::optional GameList::GetRegionForPath(const char* image_path) -{ - std::unique_ptr cdi = CDImage::Open(image_path); - if (!cdi) - return {}; - - return GetRegionForImage(cdi.get()); -} - -bool GameList::IsExeFileName(const char* path) -{ - const char* extension = std::strrchr(path, '.'); - return (extension && - (StringUtil::Strcasecmp(extension, ".exe") == 0 || StringUtil::Strcasecmp(extension, ".psexe") == 0)); -} - -bool GameList::IsPsfFileName(const char* path) -{ - const char* extension = std::strrchr(path, '.'); - return (extension && StringUtil::Strcasecmp(extension, ".psf") == 0); -} - -bool GameList::IsM3UFileName(const char* path) -{ - const char* extension = std::strrchr(path, '.'); - return (extension && StringUtil::Strcasecmp(extension, ".m3u") == 0); -} - -std::vector GameList::ParseM3UFile(const char* path) -{ - std::ifstream ifs(path); - if (!ifs.is_open()) - { - Log_ErrorPrintf("Failed to open %s", path); - return {}; - } - - std::vector entries; - std::string line; - while (std::getline(ifs, line)) - { - u32 start_offset = 0; - while (start_offset < line.size() && std::isspace(line[start_offset])) - start_offset++; - - // skip comments - if (start_offset == line.size() || line[start_offset] == '#') - continue; - - // strip ending whitespace - u32 end_offset = static_cast(line.size()) - 1; - while (std::isspace(line[end_offset]) && end_offset > start_offset) - end_offset--; - - // anything? - if (start_offset == end_offset) - continue; - - std::string entry_path(line.begin() + start_offset, line.begin() + end_offset + 1); - if (!FileSystem::IsAbsolutePath(entry_path)) - { - SmallString absolute_path; - FileSystem::BuildPathRelativeToFile(absolute_path, path, entry_path.c_str()); - entry_path = absolute_path; - } - - Log_DevPrintf("Read path from m3u: '%s'", entry_path.c_str()); - entries.push_back(std::move(entry_path)); - } - - Log_InfoPrintf("Loaded %zu paths from m3u '%s'", entries.size(), path); - return entries; -} - const char* GameList::GetGameListCompatibilityRatingString(GameListCompatibilityRating rating) { static constexpr std::array(GameListCompatibilityRating::Count)> names = { @@ -292,21 +60,6 @@ static std::string_view GetFileNameFromPath(const char* path) return std::string_view(filename_start + 1, filename_end - filename_start); } -std::string_view GameList::GetTitleForPath(const char* path) -{ - const char* extension = std::strrchr(path, '.'); - if (path == extension) - return path; - - const char* path_end = path + std::strlen(path); - const char* title_end = extension ? (extension - 1) : (path_end); - const char* title_start = std::max(std::strrchr(path, '/'), std::strrchr(path, '\\')); - if (!title_start || title_start == path) - return std::string_view(path, title_end - title_start); - else - return std::string_view(title_start + 1, title_end - title_start); -} - bool GameList::GetExeListEntry(const char* path, GameListEntry* entry) { FILESYSTEM_STAT_DATA ffd; @@ -360,12 +113,12 @@ bool GameList::GetM3UListEntry(const char* path, GameListEntry* entry) if (!FileSystem::StatFile(path, &ffd)) return false; - std::vector entries = ParseM3UFile(path); + std::vector entries = System::ParseM3UFile(path); if (entries.empty()) return false; entry->code.clear(); - entry->title = GetTitleForPath(path); + entry->title = System::GetTitleForPath(path); entry->path = path; entry->region = DiscRegion::Other; entry->total_size = 0; @@ -385,11 +138,11 @@ bool GameList::GetM3UListEntry(const char* path, GameListEntry* entry) entry->total_size += static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(entry_image->GetLBACount()); if (entry->region == DiscRegion::Other) - entry->region = GetRegionForImage(entry_image.get()); + entry->region = System::GetRegionForImage(entry_image.get()); if (entry->compatibility_rating == GameListCompatibilityRating::Unknown) { - std::string code = GetGameCodeForImage(entry_image.get()); + std::string code = System::GetGameCodeForImage(entry_image.get()); const GameListCompatibilityEntry* compatibility_entry = GetCompatibilityEntryForCode(entry->code); if (compatibility_entry) entry->compatibility_rating = compatibility_entry->compatibility_rating; @@ -403,19 +156,19 @@ bool GameList::GetM3UListEntry(const char* path, GameListEntry* entry) bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry) { - if (IsExeFileName(path.c_str())) + if (System::IsExeFileName(path.c_str())) return GetExeListEntry(path.c_str(), entry); - if (IsM3UFileName(path.c_str())) + if (System::IsM3UFileName(path.c_str())) return GetM3UListEntry(path.c_str(), entry); std::unique_ptr cdi = CDImage::Open(path.c_str()); if (!cdi) return false; - std::string code = GetGameCodeForImage(cdi.get()); - DiscRegion region = GetRegionFromSystemArea(cdi.get()); + std::string code = System::GetGameCodeForImage(cdi.get()); + DiscRegion region = System::GetRegionFromSystemArea(cdi.get()); if (region == DiscRegion::Other) - region = GetRegionForCode(code); + region = System::GetRegionForCode(code); entry->path = path; entry->code = std::move(code); @@ -428,7 +181,7 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry) if (entry->code.empty()) { // no game code, so use the filename title - entry->title = GetTitleForPath(path.c_str()); + entry->title = System::GetTitleForPath(path.c_str()); entry->compatibility_rating = GameListCompatibilityRating::Unknown; } else @@ -444,7 +197,7 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry) else { Log_WarningPrintf("'%s' not found in database", entry->code.c_str()); - entry->title = GetTitleForPath(path.c_str()); + entry->title = System::GetTitleForPath(path.c_str()); } const GameListCompatibilityEntry* compatibility_entry = GetCompatibilityEntryForCode(entry->code); @@ -825,7 +578,7 @@ public: { GameListDatabaseEntry gde; gde.code = std::move(code); - gde.region = GameList::GetRegionForCode(gde.code); + gde.region = System::GetRegionForCode(gde.code); gde.title = name; m_database.emplace(gde.code, std::move(gde)); } diff --git a/src/core/game_list.h b/src/frontend-common/game_list.h similarity index 84% rename from src/core/game_list.h rename to src/frontend-common/game_list.h index 7b4f3fc80..7b00c602a 100644 --- a/src/core/game_list.h +++ b/src/frontend-common/game_list.h @@ -1,6 +1,6 @@ #pragma once +#include "core/types.h" #include "game_settings.h" -#include "types.h" #include #include #include @@ -74,29 +74,9 @@ public: static const char* EntryTypeToString(GameListEntryType type); static const char* EntryCompatibilityRatingToString(GameListCompatibilityRating rating); - /// Returns true if the filename is a PlayStation executable we can inject. - static bool IsExeFileName(const char* path); - - /// Returns true if the filename is a Portable Sound Format file we can uncompress/load. - static bool IsPsfFileName(const char* path); - - /// Returns true if the filename is a M3U Playlist we can handle. - static bool IsM3UFileName(const char* path); - - /// Parses an M3U playlist, returning the entries. - static std::vector ParseM3UFile(const char* path); - /// Returns a string representation of a compatibility level. static const char* GetGameListCompatibilityRatingString(GameListCompatibilityRating rating); - static std::string GetGameCodeForImage(CDImage* cdi); - static std::string GetGameCodeForPath(const char* image_path); - static DiscRegion GetRegionForCode(std::string_view code); - static DiscRegion GetRegionFromSystemArea(CDImage* cdi); - static DiscRegion GetRegionForImage(CDImage* cdi); - static std::optional GetRegionForPath(const char* image_path); - static std::string_view GetTitleForPath(const char* path); - const EntryList& GetEntries() const { return m_entries; } const u32 GetEntryCount() const { return static_cast(m_entries.size()); } @@ -132,7 +112,7 @@ private: enum : u32 { GAME_LIST_CACHE_SIGNATURE = 0x45434C47, - GAME_LIST_CACHE_VERSION = 6 + GAME_LIST_CACHE_VERSION = 8 }; using DatabaseMap = std::unordered_map; diff --git a/src/core/game_settings.cpp b/src/frontend-common/game_settings.cpp similarity index 52% rename from src/core/game_settings.cpp rename to src/frontend-common/game_settings.cpp index 0db275879..4e655de20 100644 --- a/src/core/game_settings.cpp +++ b/src/frontend-common/game_settings.cpp @@ -5,8 +5,8 @@ #include "common/log.h" #include "common/string.h" #include "common/string_util.h" -#include "host_interface.h" -#include "settings.h" +#include "core/host_interface.h" +#include "core/settings.h" #include #include Log_SetChannel(GameSettings); @@ -21,17 +21,19 @@ namespace GameSettings { std::array, static_cast(Trait::Count)> s_trait_names = {{ {"ForceInterpreter", TRANSLATABLE("GameSettingsTrait", "Force Interpreter")}, {"ForceSoftwareRenderer", TRANSLATABLE("GameSettingsTrait", "Force Software Renderer")}, - {"EnableInterlacing", TRANSLATABLE("GameSettingsTrait", "Enable Interlacing")}, + {"ForceInterlacing", TRANSLATABLE("GameSettingsTrait", "Force Interlacing")}, {"DisableTrueColor", TRANSLATABLE("GameSettingsTrait", "Disable True Color")}, {"DisableUpscaling", TRANSLATABLE("GameSettingsTrait", "Disable Upscaling")}, {"DisableScaledDithering", TRANSLATABLE("GameSettingsTrait", "Disable Scaled Dithering")}, + {"DisableForceNTSCTimings", TRANSLATABLE("GameSettingsTrait", "Disallow Forcing NTSC Timings")}, {"DisableWidescreen", TRANSLATABLE("GameSettingsTrait", "Disable Widescreen")}, {"DisablePGXP", TRANSLATABLE("GameSettingsTrait", "Disable PGXP")}, {"DisablePGXPCulling", TRANSLATABLE("GameSettingsTrait", "Disable PGXP Culling")}, - {"EnablePGXPVertexCache", TRANSLATABLE("GameSettingsTrait", "Enable PGXP Vertex Cache")}, - {"EnablePGXPCPUMode", TRANSLATABLE("GameSettingsTrait", "Enable PGXP CPU Mode")}, + {"ForcePGXPVertexCache", TRANSLATABLE("GameSettingsTrait", "Force PGXP Vertex Cache")}, + {"ForcePGXPCPUMode", TRANSLATABLE("GameSettingsTrait", "Force PGXP CPU Mode")}, {"ForceDigitalController", TRANSLATABLE("GameSettingsTrait", "Force Digital Controller")}, - {"EnableRecompilerMemoryExceptions", TRANSLATABLE("GameSettingsTrait", "Enable Recompiler Memory Exceptions")}, + {"ForceRecompilerMemoryExceptions", TRANSLATABLE("GameSettingsTrait", "Force Recompiler Memory Exceptions")}, + {"ForceRecompilerICache", TRANSLATABLE("GameSettingsTrait", "Force Recompiler ICache")}, }}; const char* GetTraitName(Trait trait) @@ -69,6 +71,19 @@ bool ReadOptionalFromStream(ByteStream* stream, std::optional* dest) return true; } +static bool ReadStringFromStream(ByteStream* stream, std::string* dest) +{ + u32 size; + if (!stream->Read2(&size, sizeof(size))) + return false; + + dest->resize(size); + if (!stream->Read2(dest->data(), size)) + return false; + + return true; +} + template bool WriteOptionalToStream(ByteStream* stream, const std::optional& src) { @@ -82,6 +97,12 @@ bool WriteOptionalToStream(ByteStream* stream, const std::optional& src) return stream->Write2(&src.value(), sizeof(T)); } +static bool WriteStringToStream(ByteStream* stream, const std::string& str) +{ + const u32 size = static_cast(str.size()); + return (stream->Write2(&size, sizeof(size)) && (size == 0 || stream->Write2(str.data(), size))); +} + bool Entry::LoadFromStream(ByteStream* stream) { constexpr u32 num_bytes = (static_cast(Trait::Count) + 7) / 8; @@ -90,7 +111,17 @@ bool Entry::LoadFromStream(ByteStream* stream) if (!stream->Read2(bits.data(), num_bytes) || !ReadOptionalFromStream(stream, &display_active_start_offset) || !ReadOptionalFromStream(stream, &display_active_end_offset) || !ReadOptionalFromStream(stream, &display_crop_mode) || !ReadOptionalFromStream(stream, &display_aspect_ratio) || - !ReadOptionalFromStream(stream, &controller_1_type) || !ReadOptionalFromStream(stream, &controller_2_type)) + !ReadOptionalFromStream(stream, &display_linear_upscaling) || + !ReadOptionalFromStream(stream, &display_integer_upscaling) || + !ReadOptionalFromStream(stream, &gpu_resolution_scale) || !ReadOptionalFromStream(stream, &gpu_true_color) || + !ReadOptionalFromStream(stream, &gpu_scaled_dithering) || + !ReadOptionalFromStream(stream, &gpu_force_ntsc_timings) || + !ReadOptionalFromStream(stream, &gpu_bilinear_texture_filtering) || + !ReadOptionalFromStream(stream, &gpu_widescreen_hack) || !ReadOptionalFromStream(stream, &gpu_pgxp) || + !ReadOptionalFromStream(stream, &controller_1_type) || !ReadOptionalFromStream(stream, &controller_2_type) || + !ReadOptionalFromStream(stream, &memory_card_1_type) || !ReadOptionalFromStream(stream, &memory_card_2_type) || + !ReadStringFromStream(stream, &memory_card_1_shared_path) || + !ReadStringFromStream(stream, &memory_card_2_shared_path)) { return false; } @@ -118,8 +149,17 @@ bool Entry::SaveToStream(ByteStream* stream) const return stream->Write2(bits.data(), num_bytes) && WriteOptionalToStream(stream, display_active_start_offset) && WriteOptionalToStream(stream, display_active_end_offset) && WriteOptionalToStream(stream, display_crop_mode) && - WriteOptionalToStream(stream, display_aspect_ratio) && WriteOptionalToStream(stream, controller_1_type) && - WriteOptionalToStream(stream, controller_2_type); + WriteOptionalToStream(stream, display_linear_upscaling) && + WriteOptionalToStream(stream, display_integer_upscaling) && + WriteOptionalToStream(stream, display_aspect_ratio) && WriteOptionalToStream(stream, gpu_resolution_scale) && + WriteOptionalToStream(stream, gpu_true_color) && WriteOptionalToStream(stream, gpu_scaled_dithering) && + WriteOptionalToStream(stream, gpu_force_ntsc_timings) && + WriteOptionalToStream(stream, gpu_bilinear_texture_filtering) && + WriteOptionalToStream(stream, gpu_widescreen_hack) && WriteOptionalToStream(stream, gpu_pgxp) && + WriteOptionalToStream(stream, controller_1_type) && WriteOptionalToStream(stream, controller_2_type) && + WriteOptionalToStream(stream, memory_card_1_type) && WriteOptionalToStream(stream, memory_card_2_type) && + WriteStringToStream(stream, memory_card_1_shared_path) && + WriteStringToStream(stream, memory_card_2_shared_path); } static void ParseIniSection(Entry* entry, const char* section, const CSimpleIniA& ini) @@ -143,6 +183,34 @@ static void ParseIniSection(Entry* entry, const char* section, const CSimpleIniA cvalue = ini.GetValue(section, "DisplayAspectRatio", nullptr); if (cvalue) entry->display_aspect_ratio = Settings::ParseDisplayAspectRatio(cvalue); + cvalue = ini.GetValue(section, "DisplayLinearUpscaling", nullptr); + if (cvalue) + entry->display_linear_upscaling = StringUtil::FromChars(cvalue); + cvalue = ini.GetValue(section, "DisplayIntegerUpscaling", nullptr); + if (cvalue) + entry->display_integer_upscaling = StringUtil::FromChars(cvalue); + + cvalue = ini.GetValue(section, "GPUResolutionScale", nullptr); + if (cvalue) + entry->gpu_resolution_scale = StringUtil::FromChars(cvalue); + cvalue = ini.GetValue(section, "GPUTrueColor", nullptr); + if (cvalue) + entry->gpu_true_color = StringUtil::FromChars(cvalue); + cvalue = ini.GetValue(section, "GPUScaledDithering", nullptr); + if (cvalue) + entry->gpu_scaled_dithering = StringUtil::FromChars(cvalue); + cvalue = ini.GetValue(section, "GPUBilinearTextureFiltering", nullptr); + if (cvalue) + entry->gpu_bilinear_texture_filtering = StringUtil::FromChars(cvalue); + cvalue = ini.GetValue(section, "GPUForceNTSCTimings", nullptr); + if (cvalue) + entry->gpu_force_ntsc_timings = StringUtil::FromChars(cvalue); + cvalue = ini.GetValue(section, "GPUWidescreenHack", nullptr); + if (cvalue) + entry->gpu_widescreen_hack = StringUtil::FromChars(cvalue); + cvalue = ini.GetValue(section, "GPUPGXP", nullptr); + if (cvalue) + entry->gpu_pgxp = StringUtil::FromChars(cvalue); cvalue = ini.GetValue(section, "Controller1Type", nullptr); if (cvalue) @@ -151,9 +219,18 @@ static void ParseIniSection(Entry* entry, const char* section, const CSimpleIniA if (cvalue) entry->controller_2_type = Settings::ParseControllerTypeName(cvalue); - cvalue = ini.GetValue(section, "GPUWidescreenHack", nullptr); + cvalue = ini.GetValue(section, "MemoryCard1Type", nullptr); if (cvalue) - entry->gpu_widescreen_hack = StringUtil::FromChars(cvalue); + entry->memory_card_1_type = Settings::ParseMemoryCardTypeName(cvalue); + cvalue = ini.GetValue(section, "MemoryCard2Type", nullptr); + if (cvalue) + entry->memory_card_2_type = Settings::ParseMemoryCardTypeName(cvalue); + cvalue = ini.GetValue(section, "MemoryCard1SharedPath"); + if (cvalue) + entry->memory_card_1_shared_path = cvalue; + cvalue = ini.GetValue(section, "MemoryCard2SharedPath"); + if (cvalue) + entry->memory_card_2_shared_path = cvalue; } static void StoreIniSection(const Entry& entry, const char* section, CSimpleIniA& ini) @@ -177,14 +254,46 @@ static void StoreIniSection(const Entry& entry, const char* section, CSimpleIniA ini.SetValue(section, "DisplayAspectRatio", Settings::GetDisplayAspectRatioName(entry.display_aspect_ratio.value())); } + if (entry.display_linear_upscaling.has_value()) + ini.SetValue(section, "DisplayLinearUpscaling", entry.display_linear_upscaling.value() ? "true" : "false"); + if (entry.display_integer_upscaling.has_value()) + ini.SetValue(section, "DisplayIntegerUpscaling", entry.display_integer_upscaling.value() ? "true" : "false"); + + if (entry.gpu_resolution_scale.has_value()) + ini.SetLongValue(section, "GPUResolutionScale", static_cast(entry.gpu_resolution_scale.value())); + if (entry.gpu_true_color.has_value()) + ini.SetValue(section, "GPUTrueColor", entry.gpu_true_color.value() ? "true" : "false"); + if (entry.gpu_scaled_dithering.has_value()) + ini.SetValue(section, "GPUScaledDithering", entry.gpu_scaled_dithering.value() ? "true" : "false"); + if (entry.gpu_bilinear_texture_filtering.has_value()) + { + ini.SetValue(section, "GPUBilinearTextureFiltering", + entry.gpu_bilinear_texture_filtering.value() ? "true" : "false"); + } + if (entry.gpu_force_ntsc_timings.has_value()) + ini.SetValue(section, "GPUForceNTSCTimings", entry.gpu_force_ntsc_timings.value() ? "true" : "false"); + if (entry.gpu_widescreen_hack.has_value()) + ini.SetValue(section, "GPUWidescreenHack", entry.gpu_widescreen_hack.value() ? "true" : "false"); + if (entry.gpu_pgxp.has_value()) + ini.SetValue(section, "GPUPGXP", entry.gpu_pgxp.value() ? "true" : "false"); if (entry.controller_1_type.has_value()) ini.SetValue(section, "Controller1Type", Settings::GetControllerTypeName(entry.controller_1_type.value())); if (entry.controller_2_type.has_value()) ini.SetValue(section, "Controller2Type", Settings::GetControllerTypeName(entry.controller_2_type.value())); - if (entry.gpu_widescreen_hack.has_value()) - ini.SetValue(section, "GPUWidescreenHack", entry.gpu_widescreen_hack.value() ? "true" : "false"); + if (entry.controller_1_type.has_value()) + ini.SetValue(section, "Controller1Type", Settings::GetControllerTypeName(entry.controller_1_type.value())); + if (entry.controller_2_type.has_value()) + ini.SetValue(section, "Controller2Type", Settings::GetControllerTypeName(entry.controller_2_type.value())); + if (entry.memory_card_1_type.has_value()) + ini.SetValue(section, "MemoryCard1Type", Settings::GetMemoryCardTypeName(entry.memory_card_1_type.value())); + if (entry.memory_card_2_type.has_value()) + ini.SetValue(section, "MemoryCard2Type", Settings::GetMemoryCardTypeName(entry.memory_card_2_type.value())); + if (!entry.memory_card_1_shared_path.empty()) + ini.SetValue(section, "MemoryCard1SharedPath", entry.memory_card_1_shared_path.c_str()); + if (!entry.memory_card_2_shared_path.empty()) + ini.SetValue(section, "MemoryCard2SharedPath", entry.memory_card_2_shared_path.c_str()); } Database::Database() = default; @@ -295,17 +404,47 @@ void Entry::ApplySettings(bool display_osd_messages) const g_settings.display_crop_mode = display_crop_mode.value(); if (display_aspect_ratio.has_value()) g_settings.display_aspect_ratio = display_aspect_ratio.value(); + if (display_linear_upscaling.has_value()) + g_settings.display_linear_filtering = display_linear_upscaling.value(); + if (display_integer_upscaling.has_value()) + g_settings.display_integer_scaling = display_integer_upscaling.value(); + + if (gpu_resolution_scale.has_value()) + g_settings.gpu_resolution_scale = gpu_resolution_scale.value(); + if (gpu_true_color.has_value()) + g_settings.gpu_true_color = gpu_true_color.value(); + if (gpu_scaled_dithering.has_value()) + g_settings.gpu_scaled_dithering = gpu_scaled_dithering.value(); + if (gpu_force_ntsc_timings.has_value()) + g_settings.gpu_force_ntsc_timings = gpu_force_ntsc_timings.value(); + if (gpu_bilinear_texture_filtering) + g_settings.gpu_texture_filtering = gpu_bilinear_texture_filtering.value(); + if (gpu_widescreen_hack.has_value()) + g_settings.gpu_widescreen_hack = gpu_widescreen_hack.value(); + if (gpu_pgxp.has_value()) + g_settings.gpu_pgxp_enable = gpu_pgxp.value(); + if (controller_1_type.has_value()) g_settings.controller_types[0] = controller_1_type.value(); if (controller_2_type.has_value()) g_settings.controller_types[1] = controller_2_type.value(); - if (gpu_widescreen_hack.has_value()) - g_settings.gpu_widescreen_hack = gpu_widescreen_hack.value(); + + if (memory_card_1_type.has_value()) + g_settings.memory_card_types[0] = memory_card_1_type.value(); + if (!memory_card_1_shared_path.empty()) + g_settings.memory_card_paths[0] = memory_card_1_shared_path; + if (memory_card_2_type.has_value()) + g_settings.memory_card_types[1] = memory_card_2_type.value(); + if (!memory_card_1_shared_path.empty()) + g_settings.memory_card_paths[1] = memory_card_2_shared_path; if (HasTrait(Trait::ForceInterpreter)) { if (display_osd_messages && g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter) - g_host_interface->AddOSDMessage("CPU execution mode forced to interpreter by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "CPU interpreter forced by game settings."), osd_duration); + } g_settings.cpu_execution_mode = CPUExecutionMode::Interpreter; } @@ -313,15 +452,21 @@ void Entry::ApplySettings(bool display_osd_messages) const if (HasTrait(Trait::ForceSoftwareRenderer)) { if (display_osd_messages && g_settings.gpu_renderer != GPURenderer::Software) - g_host_interface->AddOSDMessage("GPU renderer forced to software by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "Software renderer forced by game settings."), osd_duration); + } g_settings.gpu_renderer = GPURenderer::Software; } - if (HasTrait(Trait::EnableInterlacing)) + if (HasTrait(Trait::ForceInterlacing)) { if (display_osd_messages && g_settings.gpu_disable_interlacing) - g_host_interface->AddOSDMessage("Interlacing enabled by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "Interlacing forced by game settings."), osd_duration); + } g_settings.gpu_disable_interlacing = false; } @@ -329,7 +474,10 @@ void Entry::ApplySettings(bool display_osd_messages) const if (HasTrait(Trait::DisableTrueColor)) { if (display_osd_messages && g_settings.gpu_true_color) - g_host_interface->AddOSDMessage("True color disabled by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "True color disabled by game settings."), osd_duration); + } g_settings.gpu_true_color = false; } @@ -337,7 +485,10 @@ void Entry::ApplySettings(bool display_osd_messages) const if (HasTrait(Trait::DisableUpscaling)) { if (display_osd_messages && g_settings.gpu_resolution_scale > 1) - g_host_interface->AddOSDMessage("Upscaling disabled by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "Upscaling disabled by game settings."), osd_duration); + } g_settings.gpu_resolution_scale = 1; } @@ -345,7 +496,11 @@ void Entry::ApplySettings(bool display_osd_messages) const if (HasTrait(Trait::DisableScaledDithering)) { if (display_osd_messages && g_settings.gpu_scaled_dithering) - g_host_interface->AddOSDMessage("Scaled dithering disabled by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "Scaled dithering disabled by game settings."), + osd_duration); + } g_settings.gpu_scaled_dithering = false; } @@ -355,17 +510,34 @@ void Entry::ApplySettings(bool display_osd_messages) const if (display_osd_messages && (g_settings.display_aspect_ratio == DisplayAspectRatio::R16_9 || g_settings.gpu_widescreen_hack)) { - g_host_interface->AddOSDMessage("Widescreen disabled by game settings.", osd_duration); + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "Widescreen disabled by game settings."), osd_duration); } g_settings.display_aspect_ratio = DisplayAspectRatio::R4_3; g_settings.gpu_widescreen_hack = false; } + if (HasTrait(Trait::DisableForceNTSCTimings)) + { + if (display_osd_messages && g_settings.gpu_force_ntsc_timings) + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "Forcing NTSC Timings disallowed by game settings."), + osd_duration); + } + + g_settings.gpu_force_ntsc_timings = false; + } + if (HasTrait(Trait::DisablePGXP)) { if (display_osd_messages && g_settings.gpu_pgxp_enable) - g_host_interface->AddOSDMessage("PGXP geometry correction disabled by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "PGXP geometry correction disabled by game settings."), + osd_duration); + } g_settings.gpu_pgxp_enable = false; } @@ -373,23 +545,32 @@ void Entry::ApplySettings(bool display_osd_messages) const if (HasTrait(Trait::DisablePGXPCulling)) { if (display_osd_messages && g_settings.gpu_pgxp_culling) - g_host_interface->AddOSDMessage("PGXP culling disabled by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "PGXP culling disabled by game settings."), osd_duration); + } g_settings.gpu_pgxp_culling = false; } - if (HasTrait(Trait::EnablePGXPVertexCache)) + if (HasTrait(Trait::ForcePGXPVertexCache)) { if (display_osd_messages && g_settings.gpu_pgxp_enable && !g_settings.gpu_pgxp_vertex_cache) - g_host_interface->AddOSDMessage("PGXP vertex cache enabled by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "PGXP vertex cache forced by game settings."), osd_duration); + } g_settings.gpu_pgxp_vertex_cache = true; } - if (HasTrait(Trait::EnablePGXPCPUMode)) + if (HasTrait(Trait::ForcePGXPCPUMode)) { if (display_osd_messages && g_settings.gpu_pgxp_enable && !g_settings.gpu_pgxp_cpu) - g_host_interface->AddOSDMessage("PGXP CPU mode enabled by game settings.", osd_duration); + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "PGXP CPU mode forced by game settings."), osd_duration); + } g_settings.gpu_pgxp_cpu = true; } @@ -403,8 +584,10 @@ void Entry::ApplySettings(bool display_osd_messages) const { if (display_osd_messages) { - g_host_interface->AddFormattedOSDMessage(osd_duration, "Controller %u changed to digital by game settings.", - i + 1u); + g_host_interface->AddFormattedOSDMessage( + osd_duration, + g_host_interface->TranslateString("OSDMessage", "Controller %u changed to digital by game settings."), + i + 1u); } g_settings.controller_types[i] = ControllerType::DigitalController; @@ -412,18 +595,30 @@ void Entry::ApplySettings(bool display_osd_messages) const } } - if (HasTrait(Trait::EnableRecompilerMemoryExceptions)) + if (HasTrait(Trait::ForceRecompilerMemoryExceptions)) { if (display_osd_messages && g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler && !g_settings.cpu_recompiler_memory_exceptions) { - g_host_interface->AddOSDMessage("Recompiler memory exceptions enabled by game settings.", osd_duration); + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "Recompiler memory exceptions forced by game settings."), + osd_duration); } g_settings.cpu_recompiler_memory_exceptions = true; } - // TODO: Overscan settings. + if (HasTrait(Trait::ForceRecompilerICache)) + { + if (display_osd_messages && g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter && + !g_settings.cpu_recompiler_icache) + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("OSDMessage", "Recompiler ICache forced by game settings."), osd_duration); + } + + g_settings.cpu_recompiler_icache = true; + } } } // namespace GameSettings \ No newline at end of file diff --git a/src/core/game_settings.h b/src/frontend-common/game_settings.h similarity index 71% rename from src/core/game_settings.h rename to src/frontend-common/game_settings.h index 09907dd79..3f67eb08c 100644 --- a/src/core/game_settings.h +++ b/src/frontend-common/game_settings.h @@ -1,5 +1,5 @@ #pragma once -#include "types.h" +#include "core/types.h" #include #include #include @@ -12,17 +12,19 @@ enum class Trait : u32 { ForceInterpreter, ForceSoftwareRenderer, - EnableInterlacing, + ForceInterlacing, DisableTrueColor, DisableUpscaling, DisableScaledDithering, + DisableForceNTSCTimings, DisableWidescreen, DisablePGXP, DisablePGXPCulling, - EnablePGXPVertexCache, - EnablePGXPCPUMode, + ForcePGXPVertexCache, + ForcePGXPCPUMode, ForceDigitalController, - EnableRecompilerMemoryExceptions, + ForceRecompilerMemoryExceptions, + ForceRecompilerICache, Count }; @@ -39,9 +41,21 @@ struct Entry // user settings std::optional display_crop_mode; std::optional display_aspect_ratio; + std::optional display_linear_upscaling; + std::optional display_integer_upscaling; + std::optional gpu_resolution_scale; + std::optional gpu_true_color; + std::optional gpu_scaled_dithering; + std::optional gpu_force_ntsc_timings; + std::optional gpu_bilinear_texture_filtering; + std::optional gpu_widescreen_hack; + std::optional gpu_pgxp; std::optional controller_1_type; std::optional controller_2_type; - std::optional gpu_widescreen_hack; + std::optional memory_card_1_type; + std::optional memory_card_2_type; + std::string memory_card_1_shared_path; + std::string memory_card_2_shared_path; ALWAYS_INLINE bool HasTrait(Trait trait) const { return traits[static_cast(trait)]; } ALWAYS_INLINE void AddTrait(Trait trait) { traits[static_cast(trait)] = true; } diff --git a/dep/imgui/src/imgui_impl_dx11.cpp b/src/frontend-common/imgui_impl_dx11.cpp similarity index 100% rename from dep/imgui/src/imgui_impl_dx11.cpp rename to src/frontend-common/imgui_impl_dx11.cpp diff --git a/dep/imgui/include/imgui_impl_dx11.h b/src/frontend-common/imgui_impl_dx11.h similarity index 100% rename from dep/imgui/include/imgui_impl_dx11.h rename to src/frontend-common/imgui_impl_dx11.h diff --git a/dep/imgui/src/imgui_impl_opengl3.cpp b/src/frontend-common/imgui_impl_opengl3.cpp similarity index 100% rename from dep/imgui/src/imgui_impl_opengl3.cpp rename to src/frontend-common/imgui_impl_opengl3.cpp diff --git a/dep/imgui/include/imgui_impl_opengl3.h b/src/frontend-common/imgui_impl_opengl3.h similarity index 100% rename from dep/imgui/include/imgui_impl_opengl3.h rename to src/frontend-common/imgui_impl_opengl3.h diff --git a/dep/imgui/src/imgui_impl_vulkan.cpp b/src/frontend-common/imgui_impl_vulkan.cpp similarity index 96% rename from dep/imgui/src/imgui_impl_vulkan.cpp rename to src/frontend-common/imgui_impl_vulkan.cpp index 3fb10d235..50c12ef75 100644 --- a/dep/imgui/src/imgui_impl_vulkan.cpp +++ b/src/frontend-common/imgui_impl_vulkan.cpp @@ -46,6 +46,7 @@ #include "imgui.h" #include "imgui_impl_vulkan.h" +#include "common/vulkan/texture.h" #include // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() @@ -76,16 +77,15 @@ static VkDeviceSize g_BufferMemoryAlignment = 256; static VkPipelineCreateFlags g_PipelineCreateFlags = 0x00; static VkDescriptorSetLayout g_DescriptorSetLayout = VK_NULL_HANDLE; static VkPipelineLayout g_PipelineLayout = VK_NULL_HANDLE; -static VkDescriptorSet g_DescriptorSet = VK_NULL_HANDLE; static VkPipeline g_Pipeline = VK_NULL_HANDLE; // Font data static VkSampler g_FontSampler = VK_NULL_HANDLE; static VkDeviceMemory g_FontMemory = VK_NULL_HANDLE; static VkImage g_FontImage = VK_NULL_HANDLE; -static VkImageView g_FontView = VK_NULL_HANDLE; static VkDeviceMemory g_UploadBufferMemory = VK_NULL_HANDLE; static VkBuffer g_UploadBuffer = VK_NULL_HANDLE; +static Vulkan::Texture g_FontTexture; // Render buffers static ImGui_ImplVulkanH_WindowRenderBuffers g_MainWindowRenderBuffers; @@ -269,8 +269,6 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkCommandBu // Bind pipeline and descriptor sets: { vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_Pipeline); - VkDescriptorSet desc_set[1] = { g_DescriptorSet }; - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_PipelineLayout, 0, 1, desc_set, 0, NULL); } // Bind Vertex And Index Buffer: @@ -307,6 +305,33 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkCommandBu } } +static void ImGui_ImplVulkan_UpdateAndBindDescriptors(const ImDrawCmd* pcmd, VkCommandBuffer command_buffer) +{ + const Vulkan::Texture* tex = static_cast(pcmd->TextureId); + + VkDescriptorSet desc_set; + VkDescriptorSetAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = g_VulkanInitInfo.DescriptorPool; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &g_DescriptorSetLayout; + VkResult err = vkAllocateDescriptorSets(g_VulkanInitInfo.Device, &alloc_info, &desc_set); + check_vk_result(err); + + VkDescriptorImageInfo desc_image[1] = {}; + desc_image[0].sampler = g_FontSampler; + desc_image[0].imageView = tex->GetView(); + desc_image[0].imageLayout = tex->GetLayout(); + VkWriteDescriptorSet write_desc[1] = {}; + write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_desc[0].dstSet = desc_set; + write_desc[0].descriptorCount = 1; + write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_desc[0].pImageInfo = desc_image; + vkUpdateDescriptorSets(g_VulkanInitInfo.Device, 1, write_desc, 0, NULL); + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_PipelineLayout, 0, 1, &desc_set, 0, NULL); +} + // Render function // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer) @@ -407,6 +432,8 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) { + ImGui_ImplVulkan_UpdateAndBindDescriptors(pcmd, command_buffer); + // Negative offsets are illegal for vkCmdSetScissor if (clip_rect.x < 0.0f) clip_rect.x = 0.0f; @@ -475,31 +502,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) // Create the Image View: { - VkImageViewCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - info.image = g_FontImage; - info.viewType = VK_IMAGE_VIEW_TYPE_2D; - info.format = VK_FORMAT_R8G8B8A8_UNORM; - info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - info.subresourceRange.levelCount = 1; - info.subresourceRange.layerCount = 1; - err = vkCreateImageView(v->Device, &info, v->Allocator, &g_FontView); - check_vk_result(err); - } - - // Update the Descriptor Set: - { - VkDescriptorImageInfo desc_image[1] = {}; - desc_image[0].sampler = g_FontSampler; - desc_image[0].imageView = g_FontView; - desc_image[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - VkWriteDescriptorSet write_desc[1] = {}; - write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write_desc[0].dstSet = g_DescriptorSet; - write_desc[0].descriptorCount = 1; - write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - write_desc[0].pImageInfo = desc_image; - vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, NULL); + g_FontTexture.Adopt(g_FontImage, VK_IMAGE_VIEW_TYPE_2D, width, height, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, VK_SAMPLE_COUNT_1_BIT); } // Create the Upload Buffer: @@ -578,7 +581,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) } // Store our identifier - io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontImage; + io.Fonts->TexID = (ImTextureID)(intptr_t)&g_FontTexture; return true; } @@ -639,17 +642,6 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() check_vk_result(err); } - // Create Descriptor Set: - { - VkDescriptorSetAllocateInfo alloc_info = {}; - alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - alloc_info.descriptorPool = v->DescriptorPool; - alloc_info.descriptorSetCount = 1; - alloc_info.pSetLayouts = &g_DescriptorSetLayout; - err = vkAllocateDescriptorSets(v->Device, &alloc_info, &g_DescriptorSet); - check_vk_result(err); - } - if (!g_PipelineLayout) { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix @@ -795,7 +787,7 @@ void ImGui_ImplVulkan_DestroyDeviceObjects() ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &g_MainWindowRenderBuffers, v->Allocator); ImGui_ImplVulkan_DestroyFontUploadObjects(); - if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } + g_FontTexture.Destroy(false); if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; } if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; } if (g_FontSampler) { vkDestroySampler(v->Device, g_FontSampler, v->Allocator); g_FontSampler = VK_NULL_HANDLE; } @@ -832,8 +824,9 @@ void ImGui_ImplVulkan_Shutdown() ImGui_ImplVulkan_DestroyDeviceObjects(); } -void ImGui_ImplVulkan_NewFrame() +void ImGui_ImplVulkan_NewFrame(VkDescriptorPool DescriptorPool) { + g_VulkanInitInfo.DescriptorPool = DescriptorPool; } void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) diff --git a/dep/imgui/include/imgui_impl_vulkan.h b/src/frontend-common/imgui_impl_vulkan.h similarity index 98% rename from dep/imgui/include/imgui_impl_vulkan.h rename to src/frontend-common/imgui_impl_vulkan.h index 8ad8180eb..6ee263a9b 100644 --- a/dep/imgui/include/imgui_impl_vulkan.h +++ b/src/frontend-common/imgui_impl_vulkan.h @@ -45,7 +45,7 @@ struct ImGui_ImplVulkan_InitInfo // Called by user code IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass); IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(VkDescriptorPool DescriptorPool); IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer); IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer); IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontUploadObjects(); diff --git a/src/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index 69f10faaf..200fe7433 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -1,10 +1,12 @@ #include "opengl_host_display.h" #include "common/assert.h" #include "common/log.h" -#include "imgui.h" #include -#include #include +#ifdef WITH_IMGUI +#include "imgui.h" +#include "imgui_impl_opengl3.h" +#endif Log_SetChannel(LibretroOpenGLHostDisplay); namespace FrontendCommon { @@ -219,8 +221,10 @@ bool OpenGLHostDisplay::InitializeRenderDevice(std::string_view shader_cache_dir if (!CreateResources()) return false; +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext() && !CreateImGuiContext()) return false; +#endif return true; } @@ -246,8 +250,10 @@ void OpenGLHostDisplay::DestroyRenderDevice() if (!m_gl_context) return; +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) DestroyImGuiContext(); +#endif DestroyResources(); @@ -269,11 +275,13 @@ bool OpenGLHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) m_window_info.surface_width = m_gl_context->GetSurfaceWidth(); m_window_info.surface_height = m_gl_context->GetSurfaceHeight(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) { ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); } +#endif return true; } @@ -287,11 +295,13 @@ void OpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_ m_window_info.surface_width = m_gl_context->GetSurfaceWidth(); m_window_info.surface_height = m_gl_context->GetSurfaceHeight(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) { ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); } +#endif } void OpenGLHostDisplay::DestroyRenderSurface() @@ -306,6 +316,7 @@ void OpenGLHostDisplay::DestroyRenderSurface() bool OpenGLHostDisplay::CreateImGuiContext() { +#ifdef WITH_IMGUI ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); @@ -313,12 +324,15 @@ bool OpenGLHostDisplay::CreateImGuiContext() return false; ImGui_ImplOpenGL3_NewFrame(); +#endif return true; } void OpenGLHostDisplay::DestroyImGuiContext() { +#ifdef WITH_IMGUI ImGui_ImplOpenGL3_Shutdown(); +#endif } bool OpenGLHostDisplay::CreateResources() @@ -424,24 +438,30 @@ bool OpenGLHostDisplay::Render() RenderDisplay(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) RenderImGui(); +#endif RenderSoftwareCursor(); m_gl_context->SwapBuffers(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) ImGui_ImplOpenGL3_NewFrame(); +#endif return true; } void OpenGLHostDisplay::RenderImGui() { +#ifdef WITH_IMGUI ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); GL::Program::ResetLastProgram(); +#endif } void OpenGLHostDisplay::RenderDisplay() diff --git a/src/frontend-common/sdl_controller_interface.cpp b/src/frontend-common/sdl_controller_interface.cpp index 919228d49..7d3818758 100644 --- a/src/frontend-common/sdl_controller_interface.cpp +++ b/src/frontend-common/sdl_controller_interface.cpp @@ -35,8 +35,7 @@ bool SDLControllerInterface::Initialize(CommonHostInterface* host_interface) Log_InfoPrintf("Loading game controller mappings from '%s'", gcdb_file_name.c_str()); if (SDL_GameControllerAddMappingsFromFile(gcdb_file_name.c_str()) < 0) { - Log_ErrorPrintf("SDL_GameControllerAddMappingsFromFile(%s) failed: %s", - gcdb_file_name.c_str(), SDL_GetError()); + Log_ErrorPrintf("SDL_GameControllerAddMappingsFromFile(%s) failed: %s", gcdb_file_name.c_str(), SDL_GetError()); } } @@ -293,6 +292,19 @@ bool SDLControllerInterface::BindControllerAxisToButton(int controller_index, in return true; } +bool SDLControllerInterface::BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) +{ + auto it = GetControllerDataForPlayerId(controller_index); + if (it == m_controllers.end()) + return false; + + if (button_number < 0 || button_number >= MAX_NUM_BUTTONS) + return false; + + it->button_axis_mapping[button_number] = std::move(callback); + return true; +} + bool SDLControllerInterface::HandleControllerAxisEvent(const SDL_Event* ev) { const float value = static_cast(ev->caxis.value) / (ev->caxis.value < 0 ? 32768.0f : 32767.0f); @@ -350,11 +362,20 @@ bool SDLControllerInterface::HandleControllerButtonEvent(const SDL_Event* ev) return true; const ButtonCallback& cb = it->button_mapping[ev->cbutton.button]; - if (!cb) - return false; + if (cb) + { + cb(pressed); + return true; + } - cb(pressed); - return true; + // Assume a half-axis, i.e. in 0..1 range + const AxisCallback& axis_cb = it->button_axis_mapping[ev->cbutton.button]; + if (axis_cb) + { + axis_cb(pressed ? 1.0f : 0.0f); + } + + return false; } u32 SDLControllerInterface::GetControllerRumbleMotorCount(int controller_index) diff --git a/src/frontend-common/sdl_controller_interface.h b/src/frontend-common/sdl_controller_interface.h index 4fa43e556..d839b73a0 100644 --- a/src/frontend-common/sdl_controller_interface.h +++ b/src/frontend-common/sdl_controller_interface.h @@ -1,10 +1,10 @@ #pragma once -#include "core/types.h" #include "controller_interface.h" +#include "core/types.h" #include #include -#include #include +#include union SDL_Event; @@ -27,7 +27,9 @@ public: // Binding to events. If a binding for this axis/button already exists, returns false. bool BindControllerAxis(int controller_index, int axis_number, AxisCallback callback) override; bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override; - bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) override; + bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, + ButtonCallback callback) override; + bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override; // Changing rumble strength. u32 GetControllerRumbleMotorCount(int controller_index) override; @@ -59,6 +61,7 @@ private: std::array axis_mapping; std::array button_mapping; std::array, MAX_NUM_AXISES> axis_button_mapping; + std::array button_axis_mapping; }; using ControllerDataVector = std::vector; diff --git a/src/frontend-common/vulkan_host_display.cpp b/src/frontend-common/vulkan_host_display.cpp index e90965bbd..fd6f5dd29 100644 --- a/src/frontend-common/vulkan_host_display.cpp +++ b/src/frontend-common/vulkan_host_display.cpp @@ -9,9 +9,11 @@ #include "common/vulkan/stream_buffer.h" #include "common/vulkan/swap_chain.h" #include "common/vulkan/util.h" +#include +#ifdef WITH_IMGUI #include "imgui.h" #include "imgui_impl_vulkan.h" -#include +#endif Log_SetChannel(VulkanHostDisplay); namespace FrontendCommon { @@ -134,11 +136,13 @@ bool VulkanHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) m_window_info.surface_width = m_swap_chain->GetWidth(); m_window_info.surface_height = m_swap_chain->GetHeight(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) { ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); } +#endif return true; } @@ -153,11 +157,13 @@ void VulkanHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_ m_window_info.surface_width = m_swap_chain->GetWidth(); m_window_info.surface_height = m_swap_chain->GetHeight(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) { ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); } +#endif } void VulkanHostDisplay::DestroyRenderSurface() @@ -250,8 +256,10 @@ bool VulkanHostDisplay::InitializeRenderDevice(std::string_view shader_cache_dir if (!CreateResources()) return false; +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext() && !CreateImGuiContext()) return false; +#endif return true; } @@ -400,7 +408,9 @@ void VulkanHostDisplay::DestroyResources() void VulkanHostDisplay::DestroyImGuiContext() { +#ifdef WITH_IMGUI ImGui_ImplVulkan_Shutdown(); +#endif } void VulkanHostDisplay::DestroyRenderDevice() @@ -410,8 +420,10 @@ void VulkanHostDisplay::DestroyRenderDevice() g_vulkan_context->WaitForGPUIdle(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) DestroyImGuiContext(); +#endif DestroyResources(); @@ -432,6 +444,7 @@ bool VulkanHostDisplay::DoneRenderContextCurrent() bool VulkanHostDisplay::CreateImGuiContext() { +#ifdef WITH_IMGUI ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); @@ -453,7 +466,9 @@ bool VulkanHostDisplay::CreateImGuiContext() return false; } - ImGui_ImplVulkan_NewFrame(); + ImGui_ImplVulkan_NewFrame(g_vulkan_context->GetCurrentDescriptorPool()); +#endif + return true; } @@ -499,8 +514,10 @@ bool VulkanHostDisplay::Render() RenderDisplay(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) RenderImGui(); +#endif RenderSoftwareCursor(); @@ -513,8 +530,10 @@ bool VulkanHostDisplay::Render() m_swap_chain->GetCurrentImageIndex()); g_vulkan_context->MoveToNextCommandBuffer(); +#ifdef WITH_IMGUI if (ImGui::GetCurrentContext()) - ImGui_ImplVulkan_NewFrame(); + ImGui_ImplVulkan_NewFrame(g_vulkan_context->GetCurrentDescriptorPool()); +#endif return true; } @@ -565,8 +584,10 @@ void VulkanHostDisplay::RenderDisplay(s32 left, s32 top, s32 width, s32 height, void VulkanHostDisplay::RenderImGui() { +#ifdef WITH_IMGUI ImGui::Render(); ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), g_vulkan_context->GetCurrentCommandBuffer()); +#endif } void VulkanHostDisplay::RenderSoftwareCursor() diff --git a/src/frontend-common/xinput_controller_interface.cpp b/src/frontend-common/xinput_controller_interface.cpp index 92866fd72..3ef93d90d 100644 --- a/src/frontend-common/xinput_controller_interface.cpp +++ b/src/frontend-common/xinput_controller_interface.cpp @@ -23,15 +23,19 @@ ControllerInterface::Backend XInputControllerInterface::GetBackend() const bool XInputControllerInterface::Initialize(CommonHostInterface* host_interface) { - m_xinput_module = LoadLibraryA("xinput1_4.dll"); + m_xinput_module = LoadLibraryW(L"xinput1_4"); if (!m_xinput_module) { - m_xinput_module = LoadLibraryA("xinput9_1_0.dll"); - if (!m_xinput_module) - { - Log_ErrorPrintf("Failed to load XInput module."); - return false; - } + m_xinput_module = LoadLibraryW(L"xinput1_3"); + } + if (!m_xinput_module) + { + m_xinput_module = LoadLibraryW(L"xinput9_1_0"); + } + if (!m_xinput_module) + { + Log_ErrorPrintf("Failed to load XInput module."); + return false; } // Try the hidden version of XInputGetState(), which lets us query the guide button. @@ -39,11 +43,9 @@ bool XInputControllerInterface::Initialize(CommonHostInterface* host_interface) reinterpret_cast(GetProcAddress(m_xinput_module, reinterpret_cast(100))); if (!m_xinput_get_state) reinterpret_cast(GetProcAddress(m_xinput_module, "XInputGetState")); - m_xinput_get_capabilities = - reinterpret_cast(GetProcAddress(m_xinput_module, "XInputGetCapabilities")); m_xinput_set_state = reinterpret_cast(GetProcAddress(m_xinput_module, "XInputSetState")); - if (!m_xinput_get_state || !m_xinput_get_capabilities || !m_xinput_set_state) + if (!m_xinput_get_state || !m_xinput_set_state) { Log_ErrorPrintf("Failed to get XInput function pointers."); return false; @@ -72,7 +74,6 @@ void XInputControllerInterface::PollEvents() if (!cd.connected) { cd.connected = true; - UpdateCapabilities(i); OnControllerConnected(static_cast(i)); } @@ -155,17 +156,6 @@ void XInputControllerInterface::CheckForStateChanges(u32 index, const XINPUT_STA } } -void XInputControllerInterface::UpdateCapabilities(u32 index) -{ - ControllerData& cd = m_controllers[index]; - - XINPUT_CAPABILITIES caps = {}; - m_xinput_get_capabilities(index, 0, &caps); - cd.supports_rumble = (caps.Flags & 0x0001 /* XINPUT_CAPS_FFB_SUPPORTED */); - - Log_InfoPrintf("Controller %u: Rumble is %s", index, cd.supports_rumble ? "supported" : "not supported"); -} - void XInputControllerInterface::ClearBindings() { for (auto& it : m_controllers) @@ -214,6 +204,19 @@ bool XInputControllerInterface::BindControllerAxisToButton(int controller_index, return true; } +bool XInputControllerInterface::BindControllerButtonToAxis(int controller_index, int button_number, + AxisCallback callback) +{ + if (static_cast(controller_index) >= m_controllers.size() || !m_controllers[controller_index].connected) + return false; + + if (button_number < 0 || button_number >= MAX_NUM_BUTTONS) + return false; + + m_controllers[controller_index].button_axis_mapping[button_number] = std::move(callback); + return true; +} + bool XInputControllerInterface::HandleAxisEvent(u32 index, Axis axis, s32 value) { const float f_value = static_cast(value) / (value < 0 ? 32768.0f : 32767.0f); @@ -265,10 +268,18 @@ bool XInputControllerInterface::HandleButtonEvent(u32 index, u32 button, bool pr return true; const ButtonCallback& cb = m_controllers[index].button_mapping[button]; - if (!cb) - return false; + if (cb) + { + cb(pressed); + return true; + } - cb(pressed); + // Assume a half-axis, i.e. in 0..1 range + const AxisCallback& axis_cb = m_controllers[index].button_axis_mapping[button]; + if (axis_cb) + { + axis_cb(pressed ? 1.0f : 0.0f); + } return true; } @@ -277,7 +288,7 @@ u32 XInputControllerInterface::GetControllerRumbleMotorCount(int controller_inde if (static_cast(controller_index) >= XUSER_MAX_COUNT || !m_controllers[controller_index].connected) return 0; - return m_controllers[controller_index].supports_rumble ? NUM_RUMBLE_MOTORS : 0; + return NUM_RUMBLE_MOTORS; } void XInputControllerInterface::SetControllerRumbleStrength(int controller_index, const float* strengths, diff --git a/src/frontend-common/xinput_controller_interface.h b/src/frontend-common/xinput_controller_interface.h index 5949922e6..21802ff40 100644 --- a/src/frontend-common/xinput_controller_interface.h +++ b/src/frontend-common/xinput_controller_interface.h @@ -26,6 +26,7 @@ public: bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override; bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) override; + bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override; // Changing rumble strength. u32 GetControllerRumbleMotorCount(int controller_index) override; @@ -60,7 +61,6 @@ private: { XINPUT_STATE last_state = {}; bool connected = false; - bool supports_rumble = false; // Scaling value of 1.30f to 1.40f recommended when using recent controllers float axis_scale = 1.00f; @@ -69,12 +69,12 @@ private: std::array axis_mapping; std::array button_mapping; std::array, MAX_NUM_AXISES> axis_button_mapping; + std::array button_axis_mapping; }; using ControllerDataArray = std::array; void CheckForStateChanges(u32 index, const XINPUT_STATE& new_state); - void UpdateCapabilities(u32 index); bool HandleAxisEvent(u32 index, Axis axis, s32 value); bool HandleButtonEvent(u32 index, u32 button, bool pressed); @@ -82,7 +82,6 @@ private: HMODULE m_xinput_module{}; DWORD(WINAPI* m_xinput_get_state)(DWORD, XINPUT_STATE*); - DWORD(WINAPI* m_xinput_get_capabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*); DWORD(WINAPI* m_xinput_set_state)(DWORD, XINPUT_VIBRATION*); std::mutex m_event_intercept_mutex; Hook::Callback m_event_intercept_callback;