Merge pull request #1 from stenzek/master

update
This commit is contained in:
RaydenX93 2020-09-06 11:16:02 +02:00 committed by GitHub
commit 529927e043
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
169 changed files with 7524 additions and 2890 deletions

View file

@ -233,9 +233,46 @@ jobs:
name: "android" name: "android"
path: "duckstation-android-aarch64.apk" 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: 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" runs-on: "ubuntu-latest"
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
steps: steps:
@ -279,6 +316,11 @@ jobs:
with: with:
name: "android" name: "android"
- name: Download Mac App
uses: actions/download-artifact@v1
with:
name: "macos-x64"
- name: Create release - name: Create release
uses: "marvinpinto/action-automatic-releases@latest" uses: "marvinpinto/action-automatic-releases@latest"
with: with:
@ -296,4 +338,5 @@ jobs:
linux-libretro/duckstation_libretro_linux_aarch64.so.zip linux-libretro/duckstation_libretro_linux_aarch64.so.zip
linux-libretro/duckstation_libretro_android_aarch64.so.zip linux-libretro/duckstation_libretro_android_aarch64.so.zip
android/duckstation-android-aarch64.apk android/duckstation-android-aarch64.apk
macos-x64/duckstation-mac-release.zip

3
.gitignore vendored
View file

@ -37,3 +37,6 @@ CMakeLists.txt.user
# python bytecode # python bytecode
__pycache__ __pycache__
# other repos
/dep/mac

View file

@ -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="$<TARGET_FILE_DIR:${target}>/../.."
-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}")

View file

@ -29,6 +29,8 @@ The following people have contributed to the project in some way, and are credit
- @heckez-sys - @heckez-sys
- @Damaniel - @Damaniel
- @RaydenX93 - @RaydenX93
- @gp2man
- @Richard-L
## Special Thanks ## Special Thanks
The following people did not directly contribute to the emulator, but it would not be in the state if not for them. The following people did not directly contribute to the emulator, but it would not be in the state if not for them.

View file

@ -13,6 +13,8 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c
## Latest News ## 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/22: XInput controller backend added.
- 2020/08/20: Per-game setting overrides added. Mostly for compatibility, but some options are customizable. - 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. - 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. - 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. - 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 ### 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). 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`) - Qt 5 (`brew install qt5`)
1. Clone the repository. Submodules aren't necessary, there is only one and it is only used for Windows. 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`. 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. 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. 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`. 5. Run the binary, located in the build directory under `bin/duckstation-sdl`, or `bin/DuckStation.app` for Qt.
Application bundles/.apps are currently not created, so you can't launch it via Finder yet. This is planned for the future.
### Android ### Android
**NOTE:** The Android frontend is still incomplete, not all functionality is available yet. User directory is hardcoded to `/sdcard/duckstation` for now. **NOTE:** The Android frontend is still incomplete, not all functionality is available yet. User directory is hardcoded to `/sdcard/duckstation` for now.

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View file

@ -24,6 +24,10 @@ android {
version "3.10.2" version "3.10.2"
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig { defaultConfig {
externalNativeBuild { externalNativeBuild {
cmake { cmake {

View file

@ -5,10 +5,10 @@
#include "common/string.h" #include "common/string.h"
#include "common/timestamp.h" #include "common/timestamp.h"
#include "core/controller.h" #include "core/controller.h"
#include "core/game_list.h"
#include "core/gpu.h" #include "core/gpu.h"
#include "core/host_display.h" #include "core/host_display.h"
#include "core/system.h" #include "core/system.h"
#include "frontend-common/game_list.h"
#include "frontend-common/imgui_styles.h" #include "frontend-common/imgui_styles.h"
#include "frontend-common/opengl_host_display.h" #include "frontend-common/opengl_host_display.h"
#include "frontend-common/vulkan_host_display.h" #include "frontend-common/vulkan_host_display.h"
@ -20,7 +20,14 @@ Log_SetChannel(AndroidHostInterface);
static JavaVM* s_jvm; static JavaVM* s_jvm;
static jclass s_AndroidHostInterface_class; static jclass s_AndroidHostInterface_class;
static jmethodID s_AndroidHostInterface_constructor; 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 { namespace AndroidHelpers {
// helper for retrieving the current per-thread jni environment // helper for retrieving the current per-thread jni environment
@ -36,7 +43,7 @@ JNIEnv* GetJNIEnv()
AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj) AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj)
{ {
return reinterpret_cast<AndroidHostInterface*>( return reinterpret_cast<AndroidHostInterface*>(
static_cast<uintptr_t>(env->GetLongField(obj, s_AndroidHostInterface_field_nativePointer))); static_cast<uintptr_t>(env->GetLongField(obj, s_AndroidHostInterface_field_mNativePointer)));
} }
std::string JStringToString(JNIEnv* env, jstring str) std::string JStringToString(JNIEnv* env, jstring str)
@ -95,12 +102,26 @@ void AndroidHostInterface::RequestExit()
void AndroidHostInterface::ReportError(const char* message) 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) 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) 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); 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()); Assert(!IsEmulationThreadRunning());
emulation_activity = AndroidHelpers::GetJNIEnv()->NewGlobalRef(emulation_activity);
Log_DevPrintf("Starting emulation thread..."); Log_DevPrintf("Starting emulation thread...");
m_emulation_thread_stop_request.store(false); m_emulation_thread_stop_request.store(false);
m_emulation_thread = m_emulation_thread = std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, emulation_activity,
std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, initial_surface, std::move(boot_params)); initial_surface, std::move(boot_params), resume_state);
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;
}
return true; return true;
} }
@ -196,36 +212,48 @@ void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function,
m_callback_mutex.unlock(); 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; JNIEnv* thread_env;
if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK) if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK)
{ {
Log_ErrorPrintf("Failed to attach JNI to thread"); ReportError("Failed to attach JNI to thread");
m_emulation_thread_start_result.store(false);
m_emulation_thread_started.Signal();
return; return;
} }
CreateImGuiContext(); CreateImGuiContext();
m_surface = initial_surface; m_surface = initial_surface;
ApplySettings(); m_emulation_activity_object = emulation_activity;
ApplySettings(true);
// Boot system. // 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(); DestroyImGuiContext();
m_emulation_thread_start_result.store(false); thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped);
m_emulation_thread_started.Signal(); thread_env->DeleteGlobalRef(m_emulation_activity_object);
m_emulation_activity_object = {};
s_jvm->DetachCurrentThread(); s_jvm->DetachCurrentThread();
return; return;
} }
// System is ready to go. // System is ready to go.
m_emulation_thread_start_result.store(true); thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted);
m_emulation_thread_started.Signal();
while (!m_emulation_thread_stop_request.load()) while (!m_emulation_thread_stop_request.load())
{ {
// run any events // 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(); DestroyImGuiContext();
thread_env->DeleteGlobalRef(m_emulation_activity_object);
m_emulation_activity_object = {};
s_jvm->DetachCurrentThread(); s_jvm->DetachCurrentThread();
} }
@ -308,9 +339,27 @@ void AndroidHostInterface::ReleaseHostDisplay()
m_display.reset(); 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) 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_surface == surface)
{ {
if (m_display) if (m_display)
@ -412,10 +461,11 @@ void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidat
m_game_list->Refresh(invalidate_cache, invalidate_database); 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); Settings old_settings = std::move(g_settings);
CommonHostInterface::LoadSettings(m_settings_interface); CommonHostInterface::LoadSettings(m_settings_interface);
CommonHostInterface::FixIncompatibleSettings(display_osd_messages);
CheckForSettingsChanges(old_settings); CheckForSettingsChanges(old_settings);
} }
@ -439,10 +489,26 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
return -1; return -1;
} }
if ((s_AndroidHostInterface_constructor = env->GetMethodID(s_AndroidHostInterface_class, "<init>", "()V")) == jclass emulation_activity_class;
nullptr || if ((s_AndroidHostInterface_constructor =
(s_AndroidHostInterface_field_nativePointer = env->GetMethodID(s_AndroidHostInterface_class, "<init>", "(Landroid/content/Context;)V")) == nullptr ||
env->GetFieldID(s_AndroidHostInterface_class, "nativePointer", "J")) == 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"); Log_ErrorPrint("AndroidHostInterface lookups failed");
return -1; return -1;
@ -457,12 +523,13 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
#define DEFINE_JNI_ARGS_METHOD(return_type, name, ...) \ #define DEFINE_JNI_ARGS_METHOD(return_type, name, ...) \
extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env, __VA_ARGS__) 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); Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG);
// initialize the java side // 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) if (!java_obj)
{ {
Log_ErrorPrint("Failed to create Java AndroidHostInterface"); Log_ErrorPrint("Failed to create Java AndroidHostInterface");
@ -483,7 +550,7 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, job
return nullptr; return nullptr;
} }
env->SetLongField(java_obj, s_AndroidHostInterface_field_nativePointer, env->SetLongField(java_obj, s_AndroidHostInterface_field_mNativePointer,
static_cast<long>(reinterpret_cast<uintptr_t>(cpp_obj))); static_cast<long>(reinterpret_cast<uintptr_t>(cpp_obj)));
return java_obj; return java_obj;
@ -494,8 +561,8 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isEmulationThreadRunning,
return AndroidHelpers::GetNativeClass(env, obj)->IsEmulationThreadRunning(); return AndroidHelpers::GetNativeClass(env, obj)->IsEmulationThreadRunning();
} }
DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject surface, DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject emulationActivity,
jstring filename, jstring state_filename) jobject surface, jstring filename, jboolean resume_state, jstring state_filename)
{ {
ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface); ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface);
if (!native_surface) if (!native_surface)
@ -509,7 +576,8 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobj
SystemBootParameters boot_params; SystemBootParameters boot_params;
boot_params.filename = AndroidHelpers::JStringToString(env, filename); 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) 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); AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
hi->RunOnEmulationThread( 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) 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); AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
if (hi->IsEmulationThreadRunning()) if (hi->IsEmulationThreadRunning())
{ {
hi->RunOnEmulationThread([hi]() { hi->ApplySettings(); }); hi->RunOnEmulationThread([hi]() { hi->ApplySettings(false); });
} }
else else
{ {
hi->ApplySettings(); hi->ApplySettings(false);
} }
} }

View file

@ -35,7 +35,8 @@ public:
float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override; float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override;
bool IsEmulationThreadRunning() const { return m_emulation_thread.joinable(); } 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<void()> function, bool blocking = false); void RunOnEmulationThread(std::function<void()> function, bool blocking = false);
void StopEmulationThread(); void StopEmulationThread();
@ -46,7 +47,7 @@ public:
void SetControllerAxisState(u32 index, s32 button_code, float value); void SetControllerAxisState(u32 index, s32 button_code, float value);
void RefreshGameList(bool invalidate_cache, bool invalidate_database); void RefreshGameList(bool invalidate_cache, bool invalidate_database);
void ApplySettings(); void ApplySettings(bool display_osd_messages);
protected: protected:
void SetUserDirectory() override; void SetUserDirectory() override;
@ -56,13 +57,18 @@ protected:
bool AcquireHostDisplay() override; bool AcquireHostDisplay() override;
void ReleaseHostDisplay() override; void ReleaseHostDisplay() override;
void OnSystemDestroyed() override;
void OnRunningGameChanged() override;
private: 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 CreateImGuiContext();
void DestroyImGuiContext(); void DestroyImGuiContext();
jobject m_java_object = {}; jobject m_java_object = {};
jobject m_emulation_activity_object = {};
AndroidSettingsInterface m_settings_interface; AndroidSettingsInterface m_settings_interface;
@ -73,8 +79,6 @@ private:
std::thread m_emulation_thread; std::thread m_emulation_thread;
std::atomic_bool m_emulation_thread_stop_request{false}; 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 { namespace AndroidHelpers {

View file

@ -4,37 +4,57 @@ import android.content.Context;
import android.os.Environment; import android.os.Environment;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import android.widget.Toast;
public class AndroidHostInterface import com.google.android.material.snackbar.Snackbar;
{
private long nativePointer; public class AndroidHostInterface {
private long mNativePointer;
private Context mContext;
static public native AndroidHostInterface create(Context context, String userDirectory); static public native AndroidHostInterface create(Context context, String userDirectory);
public AndroidHostInterface(long nativePointer) public AndroidHostInterface(Context context) {
{ this.mContext = context;
this.nativePointer = nativePointer; }
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 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 stopEmulationThread();
public native void surfaceChanged(Surface surface, int format, int width, int height); public native void surfaceChanged(Surface surface, int format, int width, int height);
// TODO: Find a better place for this. // TODO: Find a better place for this.
public native void setControllerType(int index, String typeName); public native void setControllerType(int index, String typeName);
public native void setControllerButtonState(int index, int buttonCode, boolean pressed); public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
public native void setControllerAxisState(int index, int axisCode, float value); public native void setControllerAxisState(int index, int axisCode, float value);
public static native int getControllerButtonCode(String controllerType, String buttonName); public static native int getControllerButtonCode(String controllerType, String buttonName);
public static native int getControllerAxisCode(String controllerType, String axisName); public static native int getControllerAxisCode(String controllerType, String axisName);
public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase); public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase);
public native GameListEntry[] getGameListEntries(); public native GameListEntry[] getGameListEntries();
public native void resetSystem(); public native void resetSystem();
public native void loadState(boolean global, int slot); public native void loadState(boolean global, int slot);
public native void saveState(boolean global, int slot); public native void saveState(boolean global, int slot);
public native void applySettings(); public native void applySettings();
static { static {
@ -42,6 +62,7 @@ public class AndroidHostInterface
} }
static private AndroidHostInterface mInstance; static private AndroidHostInterface mInstance;
static public boolean createInstance(Context context) { static public boolean createInstance(Context context) {
// Set user path. // Set user path.
String externalStorageDirectory = Environment.getExternalStorageDirectory().getAbsolutePath(); String externalStorageDirectory = Environment.getExternalStorageDirectory().getAbsolutePath();
@ -57,6 +78,7 @@ public class AndroidHostInterface
static public boolean hasInstance() { static public boolean hasInstance() {
return mInstance != null; return mInstance != null;
} }
static public AndroidHostInterface getInstance() { static public AndroidHostInterface getInstance() {
return mInstance; return mInstance;
} }

View file

@ -3,6 +3,7 @@ package com.github.stenzek.duckstation;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent; import android.content.Intent;
@ -11,14 +12,12 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MotionEvent;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View; import android.view.View;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.core.app.NavUtils;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
/** /**
@ -30,77 +29,69 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
* Settings interfaces. * Settings interfaces.
*/ */
SharedPreferences mPreferences; SharedPreferences mPreferences;
private boolean getBooleanSetting(String key, boolean defaultValue) { private boolean getBooleanSetting(String key, boolean defaultValue) {
return mPreferences.getBoolean(key, defaultValue); return mPreferences.getBoolean(key, defaultValue);
} }
private void setBooleanSetting(String key, boolean value) { private void setBooleanSetting(String key, boolean value) {
SharedPreferences.Editor editor = mPreferences.edit(); SharedPreferences.Editor editor = mPreferences.edit();
editor.putBoolean(key, value); editor.putBoolean(key, value);
editor.apply(); editor.apply();
} }
private String getStringSetting(String key, String defaultValue) { private String getStringSetting(String key, String defaultValue) {
return mPreferences.getString(key, defaultValue); return mPreferences.getString(key, defaultValue);
} }
/** public void reportError(String message) {
* Touchscreen controller overlay Log.e("EmulationActivity", message);
*/
TouchscreenControllerView mTouchscreenController;
private boolean mTouchscreenControllerVisible = true;
/** Object lock = new Object();
* Whether or not the system UI should be auto-hidden after runOnUiThread(() -> {
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds. // Toast.makeText(this, message, Toast.LENGTH_LONG);
*/ new AlertDialog.Builder(this)
private static final boolean AUTO_HIDE = true; .setTitle("Error")
.setMessage(message)
/** .setPositiveButton("OK", (dialog, button) -> {
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after dialog.dismiss();
* user interaction before hiding the system UI. synchronized (lock) {
*/ lock.notify();
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() { .create()
@Override .show();
public void run() { });
// Delayed display of UI elements
ActionBar actionBar = getSupportActionBar(); synchronized (lock) {
if (actionBar != null) { try {
actionBar.show(); 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 @Override
public void surfaceCreated(SurfaceHolder holder) { public void surfaceCreated(SurfaceHolder holder) {
@ -114,16 +105,11 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
return; return;
} }
String bootPath = getIntent().getStringExtra("bootPath"); final String bootPath = getIntent().getStringExtra("bootPath");
String bootSaveStatePath = getIntent().getStringExtra("bootSaveStatePath"); final boolean resumeState = getIntent().getBooleanExtra("resumeState", false);
boolean resumeState = getIntent().getBooleanExtra("resumeState", false); final String bootSaveStatePath = getIntent().getStringExtra("saveStatePath");
if (!AndroidHostInterface.getInstance() AndroidHostInterface.getInstance().startEmulationThread(this, holder.getSurface(), bootPath, resumeState, bootSaveStatePath);
.startEmulationThread(holder.getSurface(), bootPath, bootSaveStatePath)) {
Log.e("EmulationActivity", "Failed to start emulation thread");
finishActivity(0);
return;
}
} }
@Override @Override
@ -146,14 +132,14 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
} }
mVisible = true; mSystemUIVisible = true;
mContentView = findViewById(R.id.fullscreen_content); mContentView = findViewById(R.id.fullscreen_content);
mContentView.getHolder().addCallback(this); mContentView.getHolder().addCallback(this);
mContentView.setOnClickListener(new View.OnClickListener() { mContentView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
if (mVisible) if (mSystemUIVisible)
hide(); hideSystemUI();
} }
}); });
@ -173,11 +159,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
@Override @Override
protected void onPostCreate(Bundle savedInstanceState) { protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
hideSystemUI();
}
// Trigger the initial hide() shortly after the activity has been @Override
// created, to briefly hint to the user that UI controls protected void onStop() {
// are available. super.onStop();
delayedHide(100);
if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) {
AndroidHostInterface.getInstance().stopEmulationThread();
}
} }
@Override @Override
@ -228,37 +219,79 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (mVisible) { if (mSystemUIVisible) {
finish(); finish();
return; 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 // Hide UI first
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBar.hide(); actionBar.hide();
} }
mVisible = false; mSystemUIVisible = false;
// Schedule a runnable to remove the status and navigation bar after a delay // Schedule a runnable to remove the status and navigation bar after a delay
mHideHandler.removeCallbacks(mShowPart2Runnable); mSystemUIHideHandler.removeCallbacks(mShowPart2Runnable);
mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); mSystemUIHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
private void show() { private void showSystemUI() {
// Show the system bar // Show the system bar
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mVisible = true; mSystemUIVisible = true;
// Schedule a runnable to display UI elements after a delay // Schedule a runnable to display UI elements after a delay
mHideHandler.removeCallbacks(mHidePart2Runnable); mSystemUIHideHandler.removeCallbacks(mHidePart2Runnable);
mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); mSystemUIHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
} }
/** /**
@ -266,10 +299,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
* previously scheduled calls. * previously scheduled calls.
*/ */
private void delayedHide(int delayMillis) { private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable); mSystemUIHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis); mSystemUIHideHandler.postDelayed(mHideRunnable, delayMillis);
} }
/**
* Touchscreen controller overlay
*/
TouchscreenControllerView mTouchscreenController;
private boolean mTouchscreenControllerVisible = true;
private void setTouchscreenControllerVisibility(boolean visible) { private void setTouchscreenControllerVisibility(boolean visible) {
mTouchscreenControllerVisible = visible; mTouchscreenControllerVisible = visible;
mTouchscreenController.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); mTouchscreenController.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);

View file

@ -37,8 +37,7 @@ public final class FileUtil {
return volumePath + documentPath; return volumePath + documentPath;
else else
return volumePath + File.separator + documentPath; return volumePath + File.separator + documentPath;
} } else return volumePath;
else return volumePath;
} }

View file

@ -12,6 +12,8 @@ import android.widget.TextView;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set; import java.util.Set;
public class GameList { public class GameList {
@ -25,10 +27,19 @@ public class GameList {
mEntries = new GameListEntry[0]; mEntries = new GameListEntry[0];
} }
private class GameListEntryComparator implements Comparator<GameListEntry> {
@Override
public int compare(GameListEntry left, GameListEntry right) {
return left.getTitle().compareTo(right.getTitle());
}
}
public void refresh(boolean invalidateCache, boolean invalidateDatabase) { public void refresh(boolean invalidateCache, boolean invalidateDatabase) {
// Search and get entries from native code // Search and get entries from native code
AndroidHostInterface.getInstance().refreshGameList(invalidateCache, invalidateDatabase); AndroidHostInterface.getInstance().refreshGameList(invalidateCache, invalidateDatabase);
mEntries = AndroidHostInterface.getInstance().getGameListEntries(); mEntries = AndroidHostInterface.getInstance().getGameListEntries();
Arrays.sort(mEntries, new GameListEntryComparator());
mAdapter.notifyDataSetChanged(); mAdapter.notifyDataSetChanged();
} }

View file

@ -7,14 +7,12 @@ import android.widget.TextView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
public class GameListEntry { public class GameListEntry {
public enum EntryType public enum EntryType {
{
Disc, Disc,
PSExe PSExe
} }
public enum CompatibilityRating public enum CompatibilityRating {
{
Unknown, Unknown,
DoesntBoot, DoesntBoot,
CrashesInIntro, CrashesInIntro,
@ -72,15 +70,21 @@ public class GameListEntry {
return mTitle; return mTitle;
} }
public String getModifiedTime() { return mModifiedTime; } public String getModifiedTime() {
return mModifiedTime;
}
public DiscRegion getRegion() { public DiscRegion getRegion() {
return mRegion; 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) { public void fillView(View view) {
((TextView) view.findViewById(R.id.game_list_view_entry_title)).setText(mTitle); ((TextView) view.findViewById(R.id.game_list_view_entry_title)).setText(mTitle);

View file

@ -28,27 +28,26 @@ import android.view.MenuItem;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.Toast;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import static com.google.android.material.snackbar.Snackbar.make;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1; private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1;
private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2; private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2;
private GameList mGameList; private GameList mGameList;
private ListView mGameListView; private ListView mGameListView;
private boolean mHasExternalStoragePermissions = false;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(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); setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
@ -86,6 +85,14 @@ public class MainActivity extends AppCompatActivity {
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override @Override
public boolean onMenuItemClick(MenuItem item) { 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; return false;
} }
}); });
@ -93,6 +100,18 @@ public class MainActivity extends AppCompatActivity {
return true; 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); mGameList.refresh(false, false);
} }
@ -122,12 +141,17 @@ public class MainActivity extends AppCompatActivity {
int id = item.getItemId(); int id = item.getItemId();
//noinspection SimplifiableIfStatement //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(); startAddGameDirectory();
} else if (id == R.id.action_scan_for_new_games) { } else if (id == R.id.action_scan_for_new_games) {
mGameList.refresh(false, false); 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) { if (id == R.id.action_settings) {
Intent intent = new Intent(this, SettingsActivity.class); Intent intent = new Intent(this, SettingsActivity.class);
@ -190,19 +214,21 @@ public class MainActivity extends AppCompatActivity {
int[] grantResults) { int[] grantResults) {
// check that all were successful // check that all were successful
for (int i = 0; i < grantResults.length; i++) { for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Snackbar.make(mGameListView, if (!mHasExternalStoragePermissions) {
"External storage permissions are required to start emulation.", mHasExternalStoragePermissions = true;
Snackbar.LENGTH_LONG); completeStartup();
}
} else {
Toast.makeText(this,
"External storage permissions are required to use DuckStation.",
Toast.LENGTH_LONG);
finish();
} }
} }
} }
private boolean startEmulation(String bootPath, boolean resumeState) { private boolean startEmulation(String bootPath, boolean resumeState) {
if (!checkForExternalStoragePermissions()) {
return false;
}
Intent intent = new Intent(this, EmulationActivity.class); Intent intent = new Intent(this, EmulationActivity.class);
intent.putExtra("bootPath", bootPath); intent.putExtra("bootPath", bootPath);
intent.putExtra("resumeState", resumeState); intent.putExtra("resumeState", resumeState);

View file

@ -19,8 +19,7 @@ public class TouchscreenControllerButtonView extends View {
private String mButtonName = ""; private String mButtonName = "";
private ButtonStateChangedListener mListener; private ButtonStateChangedListener mListener;
public interface ButtonStateChangedListener public interface ButtonStateChangedListener {
{
void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed); void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed);
} }
@ -80,15 +79,12 @@ public class TouchscreenControllerButtonView extends View {
} }
@Override @Override
public boolean onTouchEvent(MotionEvent event) public boolean onTouchEvent(MotionEvent event) {
{
final boolean oldState = mPressed; final boolean oldState = mPressed;
switch (event.getAction()) switch (event.getAction()) {
{
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_DOWN: {
{
mPressed = true; mPressed = true;
invalidate(); invalidate();
@ -99,8 +95,7 @@ public class TouchscreenControllerButtonView extends View {
} }
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_UP: {
{
mPressed = false; mPressed = false;
invalidate(); invalidate();
@ -114,8 +109,7 @@ public class TouchscreenControllerButtonView extends View {
return super.onTouchEvent(event); return super.onTouchEvent(event);
} }
public boolean isPressed() public boolean isPressed() {
{
return mPressed; return mPressed;
} }
@ -127,13 +121,11 @@ public class TouchscreenControllerButtonView extends View {
mButtonName = buttonName; mButtonName = buttonName;
} }
public int getButtonCode() public int getButtonCode() {
{
return mButtonCode; return mButtonCode;
} }
public void setButtonCode(int code) public void setButtonCode(int code) {
{
mButtonCode = code; mButtonCode = code;
} }
@ -153,8 +145,7 @@ public class TouchscreenControllerButtonView extends View {
mUnpressedDrawable = unpressedDrawable; mUnpressedDrawable = unpressedDrawable;
} }
public void setButtonStateChangedListener(ButtonStateChangedListener listener) public void setButtonStateChangedListener(ButtonStateChangedListener listener) {
{
mListener = listener; mListener = listener;
} }
} }

View file

@ -50,8 +50,7 @@ public class TouchscreenControllerView extends FrameLayout implements Touchscree
linkButton(view, R.id.controller_button_r2, "R2"); linkButton(view, R.id.controller_button_r2, "R2");
} }
private void linkButton(View view, int id, String buttonName) private void linkButton(View view, int id, String buttonName) {
{
TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id);
buttonView.setButtonName(buttonName); buttonView.setButtonName(buttonName);
buttonView.setButtonStateChangedListener(this); buttonView.setButtonStateChangedListener(this);

View file

@ -1,36 +1,70 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_us2.xml -->
<!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_us2.xml --> <vector android:height="15dp"
<vector android:height="15dp" android:viewportHeight="15" android:viewportHeight="15"
android:viewportWidth="21" android:width="21dp" android:viewportWidth="21"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="21dp"
<path android:fillType="evenOdd" android:pathData="M0,0h21v15h-21z" xmlns:aapt="http://schemas.android.com/aapt"
android:strokeColor="#00000000" android:strokeWidth="1"> xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillType="evenOdd"
android:pathData="M0,0h21v15h-21z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="10.5" android:endY="15" <gradient
android:startX="10.5" android:startY="0" android:type="linear"> android:endX="10.5"
<item android:color="#FFFFFFFF" android:offset="0"/> android:endY="15"
<item android:color="#FFF0F0F0" android:offset="1"/> android:startX="10.5"
android:startY="0"
android:type="linear">
<item
android:color="#FFFFFFFF"
android:offset="0" />
<item
android:color="#FFF0F0F0"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path android:fillType="evenOdd" android:pathData="M0,0h21v15h-21z" <path
android:strokeColor="#00000000" android:strokeWidth="1"> android:fillType="evenOdd"
android:pathData="M0,0h21v15h-21z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="10.5" android:endY="15" <gradient
android:startX="10.5" android:startY="0" android:type="linear"> android:endX="10.5"
<item android:color="#FF043CAE" android:offset="0"/> android:endY="15"
<item android:color="#FF00339A" android:offset="1"/> android:startX="10.5"
android:startY="0"
android:type="linear">
<item
android:color="#FF043CAE"
android:offset="0" />
<item
android:color="#FF00339A"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path android:fillType="evenOdd" <path
android:fillType="evenOdd"
android:pathData="M10.5,3L9.7929,3.2071L10,2.5L9.7929,1.7929L10.5,2L11.2071,1.7929L11,2.5L11.2071,3.2071L10.5,3ZM10.5,13L9.7929,13.2071L10,12.5L9.7929,11.7929L10.5,12L11.2071,11.7929L11,12.5L11.2071,13.2071L10.5,13ZM15.5,8L14.7929,8.2071L15,7.5L14.7929,6.7929L15.5,7L16.2071,6.7929L16,7.5L16.2071,8.2071L15.5,8ZM5.5,8L4.7929,8.2071L5,7.5L4.7929,6.7929L5.5,7L6.2071,6.7929L6,7.5L6.2071,8.2071L5.5,8ZM14.8301,5.5L14.123,5.7071L14.3301,5L14.123,4.2929L14.8301,4.5L15.5372,4.2929L15.3301,5L15.5372,5.7071L14.8301,5.5ZM6.1699,10.5L5.4628,10.7071L5.6699,10L5.4628,9.2929L6.1699,9.5L6.877,9.2929L6.6699,10L6.877,10.7071L6.1699,10.5ZM13,3.6699L12.2929,3.877L12.5,3.1699L12.2929,2.4628L13,2.6699L13.7071,2.4628L13.5,3.1699L13.7071,3.877L13,3.6699ZM8,12.3301L7.2929,12.5372L7.5,11.8301L7.2929,11.123L8,11.3301L8.7071,11.123L8.5,11.8301L8.7071,12.5372L8,12.3301ZM14.8301,10.5L14.123,10.7071L14.3301,10L14.123,9.2929L14.8301,9.5L15.5372,9.2929L15.3301,10L15.5372,10.7071L14.8301,10.5ZM6.1699,5.5L5.4628,5.7071L5.6699,5L5.4628,4.2929L6.1699,4.5L6.877,4.2929L6.6699,5L6.877,5.7071L6.1699,5.5ZM13,12.3301L12.2929,12.5372L12.5,11.8301L12.2929,11.123L13,11.3301L13.7071,11.123L13.5,11.8301L13.7071,12.5372L13,12.3301ZM8,3.6699L7.2929,3.877L7.5,3.1699L7.2929,2.4628L8,2.6699L8.7071,2.4628L8.5,3.1699L8.7071,3.877L8,3.6699Z" android:pathData="M10.5,3L9.7929,3.2071L10,2.5L9.7929,1.7929L10.5,2L11.2071,1.7929L11,2.5L11.2071,3.2071L10.5,3ZM10.5,13L9.7929,13.2071L10,12.5L9.7929,11.7929L10.5,12L11.2071,11.7929L11,12.5L11.2071,13.2071L10.5,13ZM15.5,8L14.7929,8.2071L15,7.5L14.7929,6.7929L15.5,7L16.2071,6.7929L16,7.5L16.2071,8.2071L15.5,8ZM5.5,8L4.7929,8.2071L5,7.5L4.7929,6.7929L5.5,7L6.2071,6.7929L6,7.5L6.2071,8.2071L5.5,8ZM14.8301,5.5L14.123,5.7071L14.3301,5L14.123,4.2929L14.8301,4.5L15.5372,4.2929L15.3301,5L15.5372,5.7071L14.8301,5.5ZM6.1699,10.5L5.4628,10.7071L5.6699,10L5.4628,9.2929L6.1699,9.5L6.877,9.2929L6.6699,10L6.877,10.7071L6.1699,10.5ZM13,3.6699L12.2929,3.877L12.5,3.1699L12.2929,2.4628L13,2.6699L13.7071,2.4628L13.5,3.1699L13.7071,3.877L13,3.6699ZM8,12.3301L7.2929,12.5372L7.5,11.8301L7.2929,11.123L8,11.3301L8.7071,11.123L8.5,11.8301L8.7071,12.5372L8,12.3301ZM14.8301,10.5L14.123,10.7071L14.3301,10L14.123,9.2929L14.8301,9.5L15.5372,9.2929L15.3301,10L15.5372,10.7071L14.8301,10.5ZM6.1699,5.5L5.4628,5.7071L5.6699,5L5.4628,4.2929L6.1699,4.5L6.877,4.2929L6.6699,5L6.877,5.7071L6.1699,5.5ZM13,12.3301L12.2929,12.5372L12.5,11.8301L12.2929,11.123L13,11.3301L13.7071,11.123L13.5,11.8301L13.7071,12.5372L13,12.3301ZM8,3.6699L7.2929,3.877L7.5,3.1699L7.2929,2.4628L8,2.6699L8.7071,2.4628L8.5,3.1699L8.7071,3.877L8,3.6699Z"
android:strokeColor="#00000000" android:strokeWidth="1"> android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="10.5" android:endY="13.2071" <gradient
android:startX="10.5" android:startY="1.7929" android:type="linear"> android:endX="10.5"
<item android:color="#FFFFD429" android:offset="0"/> android:endY="13.2071"
<item android:color="#FFFFCC00" android:offset="1"/> android:startX="10.5"
android:startY="1.7929"
android:type="linear">
<item
android:color="#FFFFD429"
android:offset="0" />
<item
android:color="#FFFFCC00"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>

View file

@ -1,26 +1,49 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_hp.xml -->
<!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_hp.xml --> <vector android:height="15dp"
<vector android:height="15dp" android:viewportHeight="15" android:viewportHeight="15"
android:viewportWidth="21" android:width="21dp" android:viewportWidth="21"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="21dp"
<path android:fillType="evenOdd" android:pathData="M0,0h21v15h-21z" xmlns:aapt="http://schemas.android.com/aapt"
android:strokeColor="#00000000" android:strokeWidth="1"> xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillType="evenOdd"
android:pathData="M0,0h21v15h-21z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="10.5" android:endY="15" <gradient
android:startX="10.5" android:startY="0" android:type="linear"> android:endX="10.5"
<item android:color="#FFFFFFFF" android:offset="0"/> android:endY="15"
<item android:color="#FFF0F0F0" android:offset="1"/> android:startX="10.5"
android:startY="0"
android:type="linear">
<item
android:color="#FFFFFFFF"
android:offset="0" />
<item
android:color="#FFF0F0F0"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path android:fillType="evenOdd" <path
android:fillType="evenOdd"
android:pathData="M10.5,7.5m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0" android:pathData="M10.5,7.5m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0"
android:strokeColor="#00000000" android:strokeWidth="1"> android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="10.5" android:endY="12" <gradient
android:startX="10.5" android:startY="3" android:type="linear"> android:endX="10.5"
<item android:color="#FFD81441" android:offset="0"/> android:endY="12"
<item android:color="#FFBB0831" android:offset="1"/> android:startX="10.5"
android:startY="3"
android:type="linear">
<item
android:color="#FFD81441"
android:offset="0" />
<item
android:color="#FFBB0831"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_us2.xml -->
<!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_us2.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="21dp" android:width="21dp"
android:height="21dp" android:height="21dp"
@ -48,32 +47,56 @@
android:fillColor="#192f5d" android:fillColor="#192f5d"
android:pathData="M0 0h5.2v7h-5.2Z" /> android:pathData="M0 0h5.2v7h-5.2Z" />
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="4.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
@ -81,32 +104,56 @@
<group android:translateY="1.4"> <group android:translateY="1.4">
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="4.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
@ -115,32 +162,56 @@
<group android:translateY="2.9"> <group android:translateY="2.9">
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="4.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
@ -149,32 +220,56 @@
<group android:translateY="4.3"> <group android:translateY="4.3">
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="4.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
@ -183,32 +278,56 @@
<group android:translateY="5.6"> <group android:translateY="5.6">
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="4.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="4.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
@ -217,112 +336,200 @@
<!-- Odd stars --> <!-- Odd stars -->
<group android:translateY="0.7" android:translateX="0.4"> <group
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> android:translateY="0.7"
android:translateX="0.4">
<group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
</group> </group>
<group android:translateY="2.1" android:translateX="0.4"> <group
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> android:translateY="2.1"
android:translateX="0.4">
<group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
</group> </group>
<group android:translateY="3.6" android:translateX="0.4"> <group
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> android:translateY="3.6"
android:translateX="0.4">
<group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
</group> </group>
<group android:translateY="5.0" android:translateX="0.4"> <group
<group android:translateX="0.2" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> android:translateY="5.0"
android:translateX="0.4">
<group
android:translateX="0.2"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.0" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.0"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="1.8" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="1.8"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="2.6" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="2.6"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
</group> </group>
<group android:translateX="3.4" android:translateY="0.2" android:scaleX="0.009" android:scaleY="0.012"> <group
android:translateX="3.4"
android:translateY="0.2"
android:scaleX="0.009"
android:scaleY="0.012">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" /> android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />

View file

@ -12,8 +12,7 @@
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.40909088" android:fillAlpha="0.40909088"
android:strokeLineCap="butt"> android:strokeLineCap="butt"></path>
</path>
<path <path
android:pathData="M23.25,0.4688C22.7846,0.5006 22.3322,0.5727 21.875,0.625L21.8438,0.625L20.75,6.5938C18.9673,6.9997 17.2901,7.6887 15.7813,8.625L10.875,5.0938C9.5487,6.1234 8.3418,7.3243 7.2813,8.625L10.6875,13.5938C9.6533,15.1743 8.8755,16.979 8.4375,18.875C8.4374,18.884 8.4374,18.9047 8.4375,18.9063L2.5,19.8438C2.3914,20.7304 2.3438,21.6467 2.3438,22.5625C2.3438,23.3118 2.3644,24.0511 2.4375,24.7813L8.375,25.8438C8.7973,27.9056 9.5995,29.8313 10.7188,31.5313L7.1875,36.375C8.1988,37.6305 9.3664,38.7736 10.625,39.7813L15.625,36.3438C17.3724,37.4585 19.3231,38.2401 21.4375,38.625L22.375,44.5313C23.0412,44.5919 23.7243,44.5938 24.4063,44.5938C25.3689,44.5938 26.2885,44.5573 27.2188,44.4375L28.3438,38.4063C30.3513,37.9067 32.2372,37.04 33.875,35.875L38.6875,39.375C39.9355,38.3132 41.0768,37.0927 42.0625,35.7813L38.5625,30.7188C39.5104,29.0818 40.1671,27.2756 40.5,25.3438L46.4063,24.4063C46.458,23.7899 46.4688,23.1922 46.4688,22.5625C46.4688,21.4683 46.3416,20.3954 46.1875,19.3438L40.1875,18.25C39.7173,16.5138 38.9458,14.8939 37.9688,13.4375L41.5,8.5938C40.4054,7.2551 39.1568,6.0186 37.7813,4.9688L32.6875,8.4688C31.2235,7.6029 29.648,6.9386 27.9375,6.5625L27,0.625C26.1467,0.5246 25.2864,0.4688 24.4063,0.4688C24.1684,0.4688 23.9236,0.4613 23.6875,0.4688C23.5724,0.4724 23.4585,0.4621 23.3438,0.4688C23.3127,0.4706 23.281,0.4666 23.25,0.4688zM24.0625,15.6563C24.1767,15.6505 24.2907,15.6563 24.4063,15.6563C28.1054,15.6563 31.125,18.6759 31.125,22.375C31.125,26.0741 28.1054,29.0625 24.4063,29.0625C20.7071,29.0625 17.7188,26.0741 17.7188,22.375C17.7188,18.7915 20.5233,15.8358 24.0625,15.6563z" android:pathData="M23.25,0.4688C22.7846,0.5006 22.3322,0.5727 21.875,0.625L21.8438,0.625L20.75,6.5938C18.9673,6.9997 17.2901,7.6887 15.7813,8.625L10.875,5.0938C9.5487,6.1234 8.3418,7.3243 7.2813,8.625L10.6875,13.5938C9.6533,15.1743 8.8755,16.979 8.4375,18.875C8.4374,18.884 8.4374,18.9047 8.4375,18.9063L2.5,19.8438C2.3914,20.7304 2.3438,21.6467 2.3438,22.5625C2.3438,23.3118 2.3644,24.0511 2.4375,24.7813L8.375,25.8438C8.7973,27.9056 9.5995,29.8313 10.7188,31.5313L7.1875,36.375C8.1988,37.6305 9.3664,38.7736 10.625,39.7813L15.625,36.3438C17.3724,37.4585 19.3231,38.2401 21.4375,38.625L22.375,44.5313C23.0412,44.5919 23.7243,44.5938 24.4063,44.5938C25.3689,44.5938 26.2885,44.5573 27.2188,44.4375L28.3438,38.4063C30.3513,37.9067 32.2372,37.04 33.875,35.875L38.6875,39.375C39.9355,38.3132 41.0768,37.0927 42.0625,35.7813L38.5625,30.7188C39.5104,29.0818 40.1671,27.2756 40.5,25.3438L46.4063,24.4063C46.458,23.7899 46.4688,23.1922 46.4688,22.5625C46.4688,21.4683 46.3416,20.3954 46.1875,19.3438L40.1875,18.25C39.7173,16.5138 38.9458,14.8939 37.9688,13.4375L41.5,8.5938C40.4054,7.2551 39.1568,6.0186 37.7813,4.9688L32.6875,8.4688C31.2235,7.6029 29.648,6.9386 27.9375,6.5625L27,0.625C26.1467,0.5246 25.2864,0.4688 24.4063,0.4688C24.1684,0.4688 23.9236,0.4613 23.6875,0.4688C23.5724,0.4724 23.4585,0.4621 23.3438,0.4688C23.3127,0.4706 23.281,0.4666 23.25,0.4688zM24.0625,15.6563C24.1767,15.6505 24.2907,15.6563 24.4063,15.6563C28.1054,15.6563 31.125,18.6759 31.125,22.375C31.125,26.0741 28.1054,29.0625 24.4063,29.0625C20.7071,29.0625 17.7188,26.0741 17.7188,22.375C17.7188,18.7915 20.5233,15.8358 24.0625,15.6563z"
android:strokeAlpha="1" android:strokeAlpha="1"
@ -22,8 +21,7 @@
android:strokeColor="#808080" android:strokeColor="#808080"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="1" android:fillAlpha="1"
android:strokeLineCap="butt"> android:strokeLineCap="butt"></path>
</path>
<path <path
android:pathData="M32.13,22.3608A7.7197,7.7197 45,1 1,16.6905 22.3608A7.7197,7.7197 135,1 1,32.13 22.3608z" android:pathData="M32.13,22.3608A7.7197,7.7197 45,1 1,16.6905 22.3608A7.7197,7.7197 135,1 1,32.13 22.3608z"
android:strokeAlpha="0.64772725" android:strokeAlpha="0.64772725"

View file

@ -12,20 +12,17 @@
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="evenOdd" android:fillType="evenOdd"
android:fillAlpha="0.55" android:fillAlpha="0.55"
android:strokeLineCap="butt"> android:strokeLineCap="butt"></path>
</path>
<path <path
android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z" android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero"> android:fillType="nonZero"></path>
</path>
<path <path
android:strokeWidth="1" android:strokeWidth="1"
android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z" android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z"
android:strokeAlpha="1" android:strokeAlpha="1"
android:strokeColor="#808080" android:strokeColor="#808080"
android:fillType="nonZero"> android:fillType="nonZero"></path>
</path>
<path <path
android:fillColor="#FF000000" android:fillColor="#FF000000"
android:pathData="M24.347,14.8958C18.7044,14.8958 14.2428,19.4886 14.2428,25C14.2428,30.6426 18.8356,35.1042 24.347,35.1042C29.9896,35.1042 34.4512,30.5114 34.4512,25C34.4512,19.3574 29.8584,14.8958 24.347,14.8958L24.347,14.8958zM24.347,30.5114C21.3289,30.5114 18.8356,28.0181 18.8356,25C18.8356,21.9819 21.3289,19.4886 24.347,19.4886C27.3651,19.4886 29.8584,21.9819 29.8584,25C29.8584,28.0181 27.3651,30.5114 24.347,30.5114z" android:pathData="M24.347,14.8958C18.7044,14.8958 14.2428,19.4886 14.2428,25C14.2428,30.6426 18.8356,35.1042 24.347,35.1042C29.9896,35.1042 34.4512,30.5114 34.4512,25C34.4512,19.3574 29.8584,14.8958 24.347,14.8958L24.347,14.8958zM24.347,30.5114C21.3289,30.5114 18.8356,28.0181 18.8356,25C18.8356,21.9819 21.3289,19.4886 24.347,19.4886C27.3651,19.4886 29.8584,21.9819 29.8584,25C29.8584,28.0181 27.3651,30.5114 24.347,30.5114z"
@ -38,23 +35,20 @@
android:strokeAlpha="1" android:strokeAlpha="1"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="1"> android:fillAlpha="1"></path>
</path>
<path <path
android:pathData="M17.3078,43.7661L22.0431,30.1146C20.9468,29.7236 20.1077,28.956 19.5779,27.946L6.6069,34.4506C8.7939,38.7748 12.5993,42.1375 17.3078,43.7661z" android:pathData="M17.3078,43.7661L22.0431,30.1146C20.9468,29.7236 20.1077,28.956 19.5779,27.946L6.6069,34.4506C8.7939,38.7748 12.5993,42.1375 17.3078,43.7661z"
android:strokeAlpha="1" android:strokeAlpha="1"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="1"> android:fillAlpha="1"></path>
</path>
<path <path
android:strokeWidth="1" android:strokeWidth="1"
android:pathData="M24.347,5.2024C13.3735,5.2024 4.5494,14.0265 4.5494,25C4.5494,35.9735 13.3735,44.7976 24.347,44.7976C35.3205,44.7976 44.1446,35.9735 44.1446,25C44.1446,14.0265 35.3205,5.2024 24.347,5.2024L24.347,5.2024z" android:pathData="M24.347,5.2024C13.3735,5.2024 4.5494,14.0265 4.5494,25C4.5494,35.9735 13.3735,44.7976 24.347,44.7976C35.3205,44.7976 44.1446,35.9735 44.1446,25C44.1446,14.0265 35.3205,5.2024 24.347,5.2024L24.347,5.2024z"
android:strokeAlpha="0.5464481" android:strokeAlpha="0.5464481"
android:fillColor="#00000000" android:fillColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.5464481"> android:fillAlpha="0.5464481"></path>
</path>
<path <path
android:pathData="M30.494,25.019A6.0988,6.0988 0,1 1,18.2964 25.019A6.0988,6.0988 0,1 1,30.494 25.019z" android:pathData="M30.494,25.019A6.0988,6.0988 0,1 1,18.2964 25.019A6.0988,6.0988 0,1 1,30.494 25.019z"
android:strokeAlpha="0.6721311" android:strokeAlpha="0.6721311"
@ -63,48 +57,41 @@
android:fillColor="#00000000" android:fillColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.21265164" android:fillAlpha="0.21265164"
android:strokeLineCap="butt"> android:strokeLineCap="butt"></path>
</path>
<path <path
android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z" android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z"
android:strokeAlpha="0.1142857" android:strokeAlpha="0.1142857"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.1142857"> android:fillAlpha="0.1142857"></path>
</path>
<path <path
android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z" android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z"
android:strokeAlpha="0.09714284" android:strokeAlpha="0.09714284"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.09714284"> android:fillAlpha="0.09714284"></path>
</path>
<path <path
android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z" android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z"
android:strokeAlpha="0.71428573" android:strokeAlpha="0.71428573"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.71428573"> android:fillAlpha="0.71428573"></path>
</path>
<path <path
android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z" android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z"
android:strokeAlpha="0.62285715" android:strokeAlpha="0.62285715"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.62285715"> android:fillAlpha="0.62285715"></path>
</path>
<path <path
android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z" android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z"
android:strokeAlpha="0.3714286" android:strokeAlpha="0.3714286"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.3714286"> android:fillAlpha="0.3714286"></path>
</path>
<path <path
android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z" android:pathData="M24.347,4.1667C12.7994,4.1667 3.5137,13.4524 3.5137,25C3.5137,36.5476 12.7994,45.8333 24.347,45.8333C35.8946,45.8333 45.1803,36.5476 45.1803,25C45.1803,13.4524 35.8946,4.1667 24.347,4.1667L24.347,4.1667zM24.347,30C21.6089,30 19.347,27.7381 19.347,25C19.347,22.2619 21.6089,20 24.347,20C27.0851,20 29.347,22.2619 29.347,25C29.347,27.7381 27.0851,30 24.347,30z"
android:strokeAlpha="0.23428573" android:strokeAlpha="0.23428573"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:fillType="nonZero" android:fillType="nonZero"
android:fillAlpha="0.23428573"> android:fillAlpha="0.23428573"></path>
</path>
</vector> </vector>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -12,6 +11,17 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:theme="@style/AppTheme.AppBarOverlay"> android:theme="@style/AppTheme.AppBarOverlay">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView2"
android:layout_width="64dp"
android:layout_height="match_parent"
app:srcCompat="@mipmap/ic_launcher_foreground" />
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -19,6 +29,8 @@
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" /> app:popupTheme="@style/AppTheme.PopupOverlay" />
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main" /> <include layout="@layout/content_main" />

View file

@ -15,6 +15,5 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -3,11 +3,14 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<group android:id="@+id/actions"> <group android:id="@+id/actions">
<item android:id="@+id/reset" <item
android:id="@+id/reset"
android:title="Reset" /> android:title="Reset" />
<item android:id="@+id/quick_load" <item
android:id="@+id/quick_load"
android:title="Quick Load" /> android:title="Quick Load" />
<item android:id="@+id/quick_save" <item
android:id="@+id/quick_save"
android:title="Quick Save" /> android:title="Quick Save" />
</group> </group>
<group android:id="@+id/quick_settings"> <group android:id="@+id/quick_settings">

View file

@ -1,22 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto" <menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:id="@+id/game_list_entry_menu_info" android:id="@+id/game_list_entry_menu_start_game"
android:title="Show Information" /> android:title="Start Game" />
<item <item
android:id="@+id/game_list_entry_menu_fast_boot" android:id="@+id/game_list_entry_menu_resume_game"
android:title="@string/settings_console_fast_boot" /> android:title="Resume Game" />
<item
android:id="@+id/game_list_entry_menu_slow_boot"
android:title="Slow Boot" />
<item
android:id="@+id/game_list_entry_menu_load_state"
android:title="Load State" >
<menu >
<item android:title="Item" />
<item android:title="Item" />
</menu>
</item>
</menu> </menu>

View file

@ -2,15 +2,27 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context="com.github.stenzek.duckstation.MainActivity"> tools:context="com.github.stenzek.duckstation.MainActivity">
<group android:id="@+id/start_menu">
<item
android:id="@+id/action_resume"
android:title="Resume Last Session" />
<item
android:id="@+id/action_start_bios"
android:title="Start BIOS" />
</group>
<group android:id="@+id/game_list"> <group android:id="@+id/game_list">
<item android:id="@+id/action_add_game_directory" <item
android:id="@+id/action_add_game_directory"
android:title="Add Game Directory" /> android:title="Add Game Directory" />
<item android:id="@+id/action_scan_for_new_games" <item
android:id="@+id/action_scan_for_new_games"
android:title="Scan For New Games" /> android:title="Scan For New Games" />
<item android:id="@+id/action_rescan_all_games" <item
android:id="@+id/action_rescan_all_games"
android:title="Rescan All Games" /> android:title="Rescan All Games" />
</group> </group>
<item android:id="@+id/action_settings" <item
android:id="@+id/action_settings"
android:title="@string/action_settings" android:title="@string/action_settings"
android:orderInCategory="100" android:orderInCategory="100"
app:showAsAction="never" /> app:showAsAction="never" />

View file

@ -32,22 +32,22 @@
<item>Software</item> <item>Software</item>
</string-array> </string-array>
<string-array name="settings_gpu_resolution_scale_entries"> <string-array name="settings_gpu_resolution_scale_entries">
<item>1x (1024x512 VRAM)</item> <item>1x</item>
<item>2x (2048x1024 VRAM)</item> <item>2x</item>
<item>3x (3072x1536 VRAM)</item> <item>3x (for 720p)</item>
<item>4x (4096x2048 VRAM)</item> <item>4x</item>
<item>5x (5120x2560 VRAM)</item> <item>5x (for 1080p)</item>
<item>6x (6144x3072 VRAM)</item> <item>6x (for 1440p)</item>
<item>7x (7168x3584 VRAM)</item> <item>7x</item>
<item>8x (8192x4096 VRAM)</item> <item>8x</item>
<item>9x (9216x4608 VRAM)</item> <item>9x (for 4K)</item>
<item>10x (10240x5120 VRAM)</item> <item>10x</item>
<item>11x (11264x5632 VRAM)</item> <item>11x</item>
<item>12x (12288x6144 VRAM)</item> <item>12x</item>
<item>13x (13312x6656 VRAM)</item> <item>13x</item>
<item>14x (14336x7168 VRAM)</item> <item>14x</item>
<item>15x (15360x7680 VRAM)</item> <item>15x</item>
<item>16x (16384x8192 VRAM)</item> <item>16x</item>
</string-array> </string-array>
<string-array name="settings_gpu_resolution_scale_values"> <string-array name="settings_gpu_resolution_scale_values">
<item>1</item> <item>1</item>

View file

@ -30,11 +30,11 @@
<string name="settings_osd_show_show_vps">Show VPS</string> <string name="settings_osd_show_show_vps">Show VPS</string>
<!-- CPU Preferences --> <!-- CPU Preferences -->
<string name="settings_cpu_execution_mode">Execution Mode</string> <string name="settings_cpu_execution_mode">CPU Execution Mode</string>
<string name="settings_cpu_execution_mode_default">Interpreter</string> <string name="settings_cpu_execution_mode_default">Interpreter</string>
<!-- GPU Preferences --> <!-- GPU Preferences -->
<string name="settings_gpu_renderer">Renderer</string> <string name="settings_gpu_renderer">GPU Renderer</string>
<string name="settings_gpu_display_linear_filtering">Display Linear Filtering</string> <string name="settings_gpu_display_linear_filtering">Display Linear Filtering</string>
<string name="settings_gpu_resolution_scale">Resolution Scale</string> <string name="settings_gpu_resolution_scale">Resolution Scale</string>
<string name="settings_gpu_true_color">True 24-Bit Color (Disables Dithering)</string> <string name="settings_gpu_true_color">True 24-Bit Color (Disables Dithering)</string>

View file

@ -17,18 +17,67 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="Game List"> <PreferenceCategory app:title="General">
<Preference <SwitchPreferenceCompat
app:key="GameList/SearchPaths" app:key="Main/SpeedLimiterEnabled"
app:title="Game Search Directories"/> app:title="@string/settings_behavior_enable_speed_limiter"
<CheckBoxPreference app:defaultValue="true"
app:key="GameList/SearchRecursively" app:summary="Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will run as fast as possible, which may not be playable." />
app:title="Search Directories Recursively" <SwitchPreferenceCompat
app:defaultValue="true" /> app:key="Main/SaveStateOnExit"
<EditTextPreference app:title="Save State On Exit"
app:key="GameList/RedumpDatPath" app:defaultValue="true"
app:title="Redump Database Path" app:summary="Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time." />
<SwitchPreferenceCompat
app:key="Display/VSync"
app:title="Video Sync"
app:defaultValue="true"
app:summary="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)." />
<SwitchPreferenceCompat
app:key="Audio/Sync"
app:title="Audio Sync"
app:defaultValue="true"
app:summary="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." />
<ListPreference
app:key="CPU/ExecutionMode"
app:title="@string/settings_cpu_execution_mode"
app:entries="@array/settings_cpu_execution_mode_entries"
app:entryValues="@array/settings_cpu_execution_mode_values"
app:defaultValue="Recompiler"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<ListPreference
app:key="GPU/Renderer"
app:title="@string/settings_gpu_renderer"
app:entries="@array/gpu_renderer_entries"
app:entryValues="@array/gpu_renderer_values"
app:defaultValue="OpenGL"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_osd_header">
<SwitchPreferenceCompat
app:key="Display/ShowOSDMessages"
app:title="@string/settings_osd_show_messages"
app:defaultValue="true"
app:summary="Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc." />
<SwitchPreferenceCompat
app:key="Display/ShowSpeed"
app:title="@string/settings_osd_show_speed"
app:defaultValue="false"
app:summary="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." />
<SwitchPreferenceCompat
app:key="Display/ShowFPS"
app:title="@string/settings_osd_show_show_fps"
app:defaultValue="false"
app:summary="Shows the internal frame rate of the game in the top-right corner of the display." />
<SwitchPreferenceCompat
app:key="Display/ShowVPS"
app:title="@string/settings_osd_show_show_vps"
app:defaultValue="false"
app:summary="Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display." />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="@string/settings_console_header"> <PreferenceCategory app:title="@string/settings_console_header">
@ -41,79 +90,15 @@
app:defaultValue="@string/settings_console_region_default" app:defaultValue="@string/settings_console_region_default"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="BIOS/PatchTTYEnable"
app:title="@string/settings_console_tty_output"
app:defaultValue="false"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="BIOS/PatchFastBoot" app:key="BIOS/PatchFastBoot"
app:title="@string/settings_console_fast_boot" app:title="@string/settings_console_fast_boot"
app:defaultValue="false" app:defaultValue="false"
app:useSimpleSummaryProvider="true" /> app:summary="Skips the BIOS shell/intro, booting directly into the game. Usually safe to enable, but some games break." />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="@string/settings_behavior_header"> <PreferenceCategory app:title="Enhancements">
<SwitchPreferenceCompat
app:key="Main/SpeedLimiterEnabled"
app:title="@string/settings_behavior_enable_speed_limiter"
app:defaultValue="true"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="Main/StartPaused"
app:title="@string/settings_behavior_pause_on_start"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="Audio/Sync"
app:title="@string/settings_host_synchronization_sync_to_audio"
app:defaultValue="true"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_osd_header">
<SwitchPreferenceCompat
app:key="Display/ShowOSDMessages"
app:title="@string/settings_osd_show_messages"
app:defaultValue="true"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="Display/ShowSpeed"
app:title="@string/settings_osd_show_speed"
app:defaultValue="false"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="Display/ShowFPS"
app:title="@string/settings_osd_show_show_fps"
app:defaultValue="false"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="Display/ShowVPS"
app:title="@string/settings_osd_show_show_vps"
app:defaultValue="false"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_cpu_header">
<ListPreference
app:key="CPU/ExecutionMode"
app:title="@string/settings_cpu_execution_mode"
app:entries="@array/settings_cpu_execution_mode_entries"
app:entryValues="@array/settings_cpu_execution_mode_values"
app:defaultValue="Recompiler"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_gpu_header">
<ListPreference
app:key="GPU/Renderer"
app:title="@string/settings_gpu_renderer"
app:entries="@array/gpu_renderer_entries"
app:entryValues="@array/gpu_renderer_values"
app:defaultValue="OpenGL"
app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
app:key="GPU/ResolutionScale" app:key="GPU/ResolutionScale"
app:title="@string/settings_gpu_resolution_scale" app:title="@string/settings_gpu_resolution_scale"
@ -122,54 +107,57 @@
app:defaultValue="1" app:defaultValue="1"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:key="GPU/TrueColor"
app:title="@string/settings_gpu_true_color"
app:defaultValue="false"/>
</PreferenceCategory>
<PreferenceCategory app:title="Enhancements">
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="GPU/TrueColor" app:key="GPU/TrueColor"
app:title="True Color Rendering (24-bit, disables dithering)" app:title="True Color Rendering (24-bit, disables dithering)"
app:defaultValue="false"/> 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." />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="GPU/ScaledDithering" app:key="GPU/ScaledDithering"
app:title="Scaled Dithering (scale dither pattern to resolution)" app:title="Scaled Dithering (scale dither pattern to resolution)"
app:defaultValue="true"/> 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." />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="GPU/DisableInterlacing" app:key="GPU/DisableInterlacing"
app:title="Disable Interlacing (force progressive render/scan)" app:title="Disable Interlacing (force progressive render/scan)"
app:defaultValue="true"/> app:defaultValue="true"
app:summary="Forces the rendering and display of frames to progressive mode. This removes the &quot;combing&quot; effect seen in 480i games by rendering them in 480p. Usually safe to enable." />
<SwitchPreferenceCompat
app:key="GPU/TextureFiltering"
app:title="Bilinear Texture Filtering"
app:defaultValue="false"
app:summary="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." />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="GPU/ForceNTSCTimings" app:key="GPU/ForceNTSCTimings"
app:title="Force NTSC Timings (60hz-on-PAL)" app:title="Force NTSC Timings (60hz-on-PAL)"
app:defaultValue="false"/> app:defaultValue="false"
app:summary="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." />
<SwitchPreferenceCompat
app:key="GPU/WidescreenHack"
app:title="Widescreen Hack"
app:defaultValue="false"
app:summary="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." />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="GPU/PGXPEnable" app:key="GPU/PGXPEnable"
app:title="PGXP Geometry Correction" app:title="PGXP Geometry Correction"
app:defaultValue="false"/> app:defaultValue="false"
app:summary="Reduces &quot;wobbly&quot; polygons and &quot;warping&quot; textures that are common in PS1 games. >Only works with the hardware renderers. May not be compatible with all games." />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="GPU/PGXPCulling" app:key="GPU/PGXPCulling"
app:title="PGXP Culling Correction" app:title="PGXP Culling Correction"
app:defaultValue="true"/> app:defaultValue="true"
app:summary="Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction enabled." />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="GPU/PGXPTextureCorrection" app:key="GPU/PGXPTextureCorrection"
app:title="PGXP Texture Correction" app:title="PGXP Texture Correction"
app:defaultValue="true"/> app:summary="Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. Requires geometry correction enabled." />
<SwitchPreferenceCompat
app:key="GPU/PGXPVertexCache"
app:title="PGXP Vertex Cache"
app:defaultValue="false"/>
</PreferenceCategory> </PreferenceCategory>
@ -193,18 +181,15 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="Display/LinearFiltering" app:key="Display/LinearFiltering"
app:title="Linear Upscaling" app:title="Linear Upscaling"
app:defaultValue="true"/> app:defaultValue="true"
app:summary="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." />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="Display/IntegerScaling" app:key="Display/IntegerScaling"
app:title="Integer Upscaling" app:title="Integer Upscaling"
app:defaultValue="false"/> 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." />
<SwitchPreferenceCompat
app:key="Display/VSync"
app:title="VSync"
app:defaultValue="true"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="Controller"> <PreferenceCategory app:title="Controller">
@ -242,4 +227,26 @@
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="Advanced Settings">
<SwitchPreferenceCompat
app:key="GPU/PGXPVertexCache"
app:title="PGXP Vertex Cache"
app:defaultValue="false"
app:summary="Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility." />
<SwitchPreferenceCompat
app:key="GPU/PGXPCPU"
app:title="PGXP CPU Mode"
app:defaultValue="false"
app:summary="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." />
<SwitchPreferenceCompat
app:key="CPU/RecompilerICache"
app:title="CPU Recompiler ICache"
app:defaultValue="false"
app:summary="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." />
<SwitchPreferenceCompat
app:key="BIOS/PatchTTYEnable"
app:title="@string/settings_console_tty_output"
app:defaultValue="false" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -6,27 +6,11 @@ skip_tags: true
image: image:
- Visual Studio 2019 - Visual Studio 2019
- Ubuntu1804
- macOS
install: install:
- cmd: >- - cmd: >-
git submodule update --init --depth 1 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: build_script:
- cmd: >- - cmd: >-
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 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 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 test: off

View file

@ -215,6 +215,10 @@
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1391-g5f9481dd</version-tested> <version-tested>0.1-1391-g5f9481dd</version-tested>
</entry> </entry>
<entry code="SLUS-00303" compatibility="5" region="NTSC-U" title="Allied General (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1558-gf852be74</version-tested>
</entry>
<entry code="SLUS-00239" compatibility="5" region="NTSC-U" title="Alone in the Dark - One-Eyed Jack's Revenge (USA)"> <entry code="SLUS-00239" compatibility="5" region="NTSC-U" title="Alone in the Dark - One-Eyed Jack's Revenge (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1323-ga6acd33</version-tested> <version-tested>0.1-1323-ga6acd33</version-tested>
@ -259,6 +263,10 @@
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1336-gd711baa</version-tested> <version-tested>0.1-1336-gd711baa</version-tested>
</entry> </entry>
<entry code="SLUS-00264" compatibility="5" region="NTSC-U" title="Andretti Racing (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1580-g136a9d60</version-tested>
</entry>
<entry code="SLPS-00163" compatibility="5" region="NTSC-J" title="Angel Graffiti - Anata e no Profile (Japan)"> <entry code="SLPS-00163" compatibility="5" region="NTSC-J" title="Angel Graffiti - Anata e no Profile (Japan)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1304-gc8b6712</version-tested> <version-tested>0.1-1304-gc8b6712</version-tested>
@ -474,6 +482,10 @@
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1304-gc8b6712</version-tested> <version-tested>0.1-1304-gc8b6712</version-tested>
</entry> </entry>
<entry code="SLUS-00483" compatibility="5" region="NTSC-U" title="Battle Arena Toshinden 3 (USA) (En,Ja)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1558-gf852be74</version-tested>
</entry>
<entry code="SLES-00334" compatibility="5" region="PAL" title="Bedlam (Europe) (En,Fr,De,Es,It,Nl)"> <entry code="SLES-00334" compatibility="5" region="PAL" title="Bedlam (Europe) (En,Fr,De,Es,It,Nl)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
@ -717,10 +729,9 @@
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1490-g76978986</version-tested> <version-tested>0.1-1490-g76978986</version-tested>
</entry> </entry>
<entry code="SLUS-01041" compatibility="3" region="NTSC-U" title="Chrono Cross (USA) (Disc 1)"> <entry code="SLUS-01041" compatibility="5" region="NTSC-U" title="Chrono Cross (Disc 1)">
<compatibility>Crashes In-Game</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1409-ge198e315</version-tested> <version-tested>0.1-1600-g032127a7</version-tested>
<comments>Graphical errors in ingame menu. If you try to check the status, the game freezes in black screen (Issues #533 and #503).</comments>
</entry> </entry>
<entry code="SLUS-00792" compatibility="5" region="NTSC-U" title="Civilization II (USA)"> <entry code="SLUS-00792" compatibility="5" region="NTSC-U" title="Civilization II (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
@ -734,6 +745,10 @@
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1337-gcaf9943</version-tested> <version-tested>0.1-1337-gcaf9943</version-tested>
</entry> </entry>
<entry code="SLUS-00866" compatibility="5" region="NTSC-U" title="Colony Wars - Red Sun (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1558-gf852be74</version-tested>
</entry>
<entry code="SCUS-94294" compatibility="5" region="NTSC-U" title="Contender (USA)"> <entry code="SCUS-94294" compatibility="5" region="NTSC-U" title="Contender (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1400-gb527118c</version-tested> <version-tested>0.1-1400-gb527118c</version-tested>
@ -839,6 +854,11 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1425-g05f0ce6d</version-tested> <version-tested>0.1-1425-g05f0ce6d</version-tested>
</entry> </entry>
<entry code="SLUS-00658" compatibility="4" region="NTSC-U" title="DBZ - Dead Ball Zone (USA) (En,Fr,De,Es,It)">
<compatibility>Speed Issues</compatibility>
<version-tested>0.1-1558-gf852be74</version-tested>
<comments>Speed issues ingame (Issue #695).</comments>
</entry>
<entry code="SLUS-01280" compatibility="5" region="NTSC-U" title="Dance Dance Revolution (USA)"> <entry code="SLUS-01280" compatibility="5" region="NTSC-U" title="Dance Dance Revolution (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>
@ -910,6 +930,14 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-908-g9f22684</version-tested> <version-tested>0.1-908-g9f22684</version-tested>
</entry> </entry>
<entry code="SLUS-01210" compatibility="5" region="NTSC-U" title="Disney Presents Tigger's Honey Hunt (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1558-gf852be74</version-tested>
</entry>
<entry code="SLUS-01152" compatibility="5" region="NTSC-U" title="Disney's 102 Dalmatians - Puppies to the Rescue (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1558-gf852be74</version-tested>
</entry>
<entry code="SLUS-01242" compatibility="5" region="NTSC-U" title="Disney's Donald Duck - Goin' Quackers (USA)"> <entry code="SLUS-01242" compatibility="5" region="NTSC-U" title="Disney's Donald Duck - Goin' Quackers (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1333-g5a955a4</version-tested> <version-tested>0.1-1333-g5a955a4</version-tested>
@ -922,6 +950,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1333-g5a955a4</version-tested> <version-tested>0.1-1333-g5a955a4</version-tested>
</entry> </entry>
<entry code="SCUS-94646" compatibility="5" region="NTSC-U" title="Disney's Lilo &amp; Stitch (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1558-gf852be74</version-tested>
</entry>
<entry code="SCUS-94456" compatibility="5" region="NTSC-U" title="Disney's Tarzan (USA)"> <entry code="SCUS-94456" compatibility="5" region="NTSC-U" title="Disney's Tarzan (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1333-g5a955a4</version-tested> <version-tested>0.1-1333-g5a955a4</version-tested>
@ -955,6 +987,10 @@ Tetris with Card Captor Sakura (Japan)
<version-tested>0.1-908-g9f22684</version-tested> <version-tested>0.1-908-g9f22684</version-tested>
<upscaling-issues>Broken when upscaling</upscaling-issues> <upscaling-issues>Broken when upscaling</upscaling-issues>
</entry> </entry>
<entry code="SLUS-00493" compatibility="5" region="NTSC-U" title="Dragon Ball GT - Final Bout (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1580-g136a9d60</version-tested>
</entry>
<entry code="SLUS-01550" compatibility="5" region="NTSC-U" title="Dragon Ball Z - Ultimate Battle 22 (USA)"> <entry code="SLUS-01550" compatibility="5" region="NTSC-U" title="Dragon Ball Z - Ultimate Battle 22 (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1336-gd711baa</version-tested> <version-tested>0.1-1336-gd711baa</version-tested>
@ -1020,6 +1056,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-896-gc8a00c5</version-tested> <version-tested>0.1-896-gc8a00c5</version-tested>
</entry> </entry>
<entry code="SLUS-00809" compatibility="5" region="NTSC-U" title="Ehrgeiz - God Bless the Ring">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SCUS-94243" compatibility="5" region="NTSC-U" title="Einhaender (USA)"> <entry code="SCUS-94243" compatibility="5" region="NTSC-U" title="Einhaender (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-896-gc8a00c5</version-tested> <version-tested>0.1-896-gc8a00c5</version-tested>
@ -1054,6 +1094,10 @@ Tetris with Card Captor Sakura (Japan)
<upscaling-issues>The playable character gain &quot;yellow horns&quot; (actually, 2 broken polygons in head) if the upscaling is used (Issue 427).</upscaling-issues> <upscaling-issues>The playable character gain &quot;yellow horns&quot; (actually, 2 broken polygons in head) if the upscaling is used (Issue 427).</upscaling-issues>
<version-tested>0.1-1425-g05f0ce6d</version-tested> <version-tested>0.1-1425-g05f0ce6d</version-tested>
</entry> </entry>
<entry code="SLUS-00395" compatibility="5" region="NTSC-U" title="Fantastic Four (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00920" compatibility="5" region="NTSC-U" title="Fear Effect (USA) (Disc 1)"> <entry code="SLUS-00920" compatibility="5" region="NTSC-U" title="Fear Effect (USA) (Disc 1)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<upscaling-issues>If use fast forward some controllers buttons may not function properly.</upscaling-issues> <upscaling-issues>If use fast forward some controllers buttons may not function properly.</upscaling-issues>
@ -1063,11 +1107,27 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1336-gd711baa</version-tested> <version-tested>0.1-1336-gd711baa</version-tested>
</entry> </entry>
<entry code="SLUS-00711" compatibility="5" region="NTSC-U" title="Fifth Element, The (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00433" compatibility="5" region="NTSC-U" title="Fighting Force (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00934" compatibility="5" region="NTSC-U" title="Fighting Force 2 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00331" compatibility="5" region="NTSC-U" title="Final Doom (USA)"> <entry code="SLUS-00331" compatibility="5" region="NTSC-U" title="Final Doom (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
<upscaling-issues>Rendering is broken with any upscaling.</upscaling-issues> <upscaling-issues>Rendering is broken with any upscaling.</upscaling-issues>
</entry> </entry>
<entry code="SLUS-00900" compatibility="5" region="NTSC-U" title="Final Fantasy Anthology - Final Fantasy VI (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1580-g136a9d60</version-tested>
</entry>
<entry code="SLES-02965" compatibility="5" region="PAL" title="Final Fantasy IX (Europe) (Disc 1)"> <entry code="SLES-02965" compatibility="5" region="PAL" title="Final Fantasy IX (Europe) (Disc 1)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>
@ -1140,8 +1200,21 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
</entry> </entry>
<entry code="SLUS-00870" compatibility="2" region="NTSC-U" title="Formula One 99 (USA) (En,Fr,Es)"> <entry code="SLPS-02158" compatibility="5" region="NTSC-J" title="Finger Flashing">
<compatibility>Crashes In Intro</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00546" compatibility="4" region="NTSC-U" title="Formula 1 Championship Edition (USA) (En,Fr,De,Es,It)">
<compatibility>Graphical/Audio Issues</compatibility>
<version-tested>0.1-1580-g136a9d60</version-tested>
<comments>Some graphics issues in random races</comments>
</entry>
<entry code="SCES-03404" compatibility="3" region="PAL" title="Formula One 2001 (Europe) (En,Fi)">
<compatibility>Crashes In-Game</compatibility>
</entry>
<entry code="SLUS-00870" compatibility="5" region="NTSC-U" title="Formula One 99 (USA) (En,Fr,Es)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1614-g914f3ad4</version-tested>
<comments>Blackscreen after the first Loading screen (Issue #54).</comments> <comments>Blackscreen after the first Loading screen (Issue #54).</comments>
</entry> </entry>
<entry code="SLUS-01129" compatibility="5" region="NTSC-U" title="FoxKids.com - Micro Maniacs Racing (USA)"> <entry code="SLUS-01129" compatibility="5" region="NTSC-U" title="FoxKids.com - Micro Maniacs Racing (USA)">
@ -1167,10 +1240,18 @@ Tetris with Card Captor Sakura (Japan)
<entry code="SLPS-00624" compatibility="5" region="NTSC-J" title="GaiaSeed: Project Seed Trap"> <entry code="SLPS-00624" compatibility="5" region="NTSC-J" title="GaiaSeed: Project Seed Trap">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
</entry> </entry>
<entry code="SLUS-01258" compatibility="5" region="NTSC-U" title="Galaga - Destination Earth (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00986" compatibility="5" region="NTSC-U" title="Galerians (USA) (Disc 1)"> <entry code="SLUS-00986" compatibility="5" region="NTSC-U" title="Galerians (USA) (Disc 1)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1425-g05f0ce6d</version-tested> <version-tested>0.1-1425-g05f0ce6d</version-tested>
</entry> </entry>
<entry code="ARCDXEXE" compatibility="1" region="NTSC-U" title="GameShark 2 Version 2 Code Archive Disc Version 1 (USA) (Unl)">
<compatibility>Doesn't Boot</compatibility>
<version-tested>0.1-1580-g136a9d60</version-tested>
</entry>
<entry code="SLPM-86155" compatibility="5" region="NTSC-J" title="Ganbare Goemon - Kuru nara Koi! Ayashige Ikka no Kuroi Kage"> <entry code="SLPM-86155" compatibility="5" region="NTSC-J" title="Ganbare Goemon - Kuru nara Koi! Ayashige Ikka no Kuroi Kage">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
</entry> </entry>
@ -1194,6 +1275,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>
</entry> </entry>
<entry code="SLUS-00024" compatibility="5" region="NTSC-U" title="Geom Cube (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00042" compatibility="5" region="NTSC-U" title="Gex (USA)"> <entry code="SLUS-00042" compatibility="5" region="NTSC-U" title="Gex (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1304-gc8b6712</version-tested> <version-tested>0.1-1304-gc8b6712</version-tested>
@ -1250,6 +1335,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
</entry> </entry>
<entry code="SLUS-00127" compatibility="5" region="NTSC-U" title="Grand Slam (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00106" compatibility="5" region="NTSC-U" title="Grand Theft Auto (USA)"> <entry code="SLUS-00106" compatibility="5" region="NTSC-U" title="Grand Theft Auto (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>
@ -1262,6 +1351,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1425-g05f0ce6d</version-tested> <version-tested>0.1-1425-g05f0ce6d</version-tested>
</entry> </entry>
<entry code="SLUS-01466" compatibility="5" region="NTSC-U" title="Gubble (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLPS-01357" compatibility="5" region="NTSC-J" title="Guilty Gear"> <entry code="SLPS-01357" compatibility="5" region="NTSC-J" title="Guilty Gear">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
</entry> </entry>
@ -1322,6 +1415,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-887-g1eecd50</version-tested> <version-tested>0.1-887-g1eecd50</version-tested>
</entry> </entry>
<entry code="SLUS-00150" compatibility="5" region="NTSC-U" title="Incredible Hulk, The - The Pantheon Saga (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1580-g136a9d60</version-tested>
</entry>
<entry code="SLPS-01793" compatibility="5" region="NTSC-J" title="Initial D (Japan)"> <entry code="SLPS-01793" compatibility="5" region="NTSC-J" title="Initial D (Japan)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
@ -1685,6 +1782,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>
</entry> </entry>
<entry code="SLPS-00435" compatibility="5" region="NTSC-J" title="Megatudo 2096 (Japan)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1614-g914f3ad4</version-tested>
</entry>
<entry code="SLES-01047" compatibility="5" region="PAL" title="Men in Black - The Game (Europe)"> <entry code="SLES-01047" compatibility="5" region="PAL" title="Men in Black - The Game (Europe)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>
@ -1859,6 +1960,10 @@ Tetris with Card Captor Sakura (Japan)
<entry code="SLUS-00329" compatibility="5" region="NTSC-U" title="NBA Hangtime"> <entry code="SLUS-00329" compatibility="5" region="NTSC-U" title="NBA Hangtime">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
</entry> </entry>
<entry code="SLUS-00388" compatibility="5" region="NTSC-U" title="NBA Jam Extreme (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1614-g914f3ad4</version-tested>
</entry>
<entry code="SLUS-00060" compatibility="5" region="NTSC-U" title="NBA Live 96 (USA)"> <entry code="SLUS-00060" compatibility="5" region="NTSC-U" title="NBA Live 96 (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<comments>Issue 419</comments> <comments>Issue 419</comments>
@ -1918,6 +2023,14 @@ Tetris with Card Captor Sakura (Japan)
<version-tested>0.1-1443-g7ab521f7</version-tested> <version-tested>0.1-1443-g7ab521f7</version-tested>
<comments>After the initial FMV, the game crashes (Issue #717).</comments> <comments>After the initial FMV, the game crashes (Issue #717).</comments>
</entry> </entry>
<entry code="SLUS-01352" compatibility="5" region="NTSC-U" title="Nickelodeon SpongeBob SquarePants - SuperSponge (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-01047" compatibility="5" region="NTSC-U" title="Nicktoons Racing (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLPM-87048" compatibility="5" region="NTSC-J" title="Night Raid (Japan)"> <entry code="SLPM-87048" compatibility="5" region="NTSC-J" title="Night Raid (Japan)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-884-g096ed21</version-tested> <version-tested>0.1-884-g096ed21</version-tested>
@ -2075,6 +2188,24 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested> <version-tested>0.1-1530-g6d75f42e</version-tested>
</entry> </entry>
<entry code="SCUS-94952" compatibility="4" region="NTSC-U" title="PlayStation Picks (USA) (SCUS-94952)">
<compatibility>Graphical/Audio Issues</compatibility>
<version-tested>0.1-1539-gf704cc64</version-tested>
<comments>In Battle Arena Toshinden demo, game runs at a strange fast speed than it should be (Issue #695).</comments>
</entry>
<entry code="SCUS-94960" compatibility="5" region="NTSC-U" title="PlayStation Picks (USA) (SCUS-94960)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1539-gf704cc64</version-tested>
<comments>Can hang if speed up in Star Wars Rebel Assaut 2 demo.</comments>
</entry>
<entry code="SCUS-94440" compatibility="5" region="NTSC-U" title="PlayStation Underground 3.1 (USA) (Disc 1)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1539-gf704cc64</version-tested>
</entry>
<entry code="SCUS-94161" compatibility="5" region="NTSC-U" title="PlayStation Underground Number 1 (USA) (Disc 1)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1539-gf704cc64</version-tested>
</entry>
<entry code="SLPS-01360" compatibility="5" region="NTSC-J" title="Pocket Fighter (Japan)"> <entry code="SLPS-01360" compatibility="5" region="NTSC-J" title="Pocket Fighter (Japan)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-884-g096ed21</version-tested> <version-tested>0.1-884-g096ed21</version-tested>
@ -2104,6 +2235,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-774-g5a1b008</version-tested> <version-tested>0.1-774-g5a1b008</version-tested>
</entry> </entry>
<entry code="SLUS-01343" compatibility="5" region="NTSC-U" title="Power Shovel (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1539-gf704cc64</version-tested>
</entry>
<entry code="SLUS-01423" compatibility="5" region="NTSC-U" title="Powerpuff Girls, The - Chemical X-Traction (USA) (En,Es)"> <entry code="SLUS-01423" compatibility="5" region="NTSC-U" title="Powerpuff Girls, The - Chemical X-Traction (USA) (En,Es)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1529-ga895c027</version-tested> <version-tested>0.1-1529-ga895c027</version-tested>
@ -2306,6 +2441,10 @@ Tetris with Card Captor Sakura (Japan)
<version-tested>0.1-1334-g10f2366</version-tested> <version-tested>0.1-1334-g10f2366</version-tested>
<comments>The menu music is messing completely (Issue #662). </comments> <comments>The menu music is messing completely (Issue #662). </comments>
</entry> </entry>
<entry code="SLUS-01053" compatibility="5" region="NTSC-U" title="Road Rash - Jailbreak (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00524" compatibility="5" region="NTSC-U" title="Road Rash 3D (USA)"> <entry code="SLUS-00524" compatibility="5" region="NTSC-U" title="Road Rash 3D (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1333-g5a955a4</version-tested> <version-tested>0.1-1333-g5a955a4</version-tested>
@ -2834,6 +2973,11 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1265-gdd9a419</version-tested> <version-tested>0.1-1265-gdd9a419</version-tested>
</entry> </entry>
<entry code="SLES-00024" compatibility="4" region="PAL" title="Tomb Raider (Europe) (No EDC)">
<compatibility>Graphical/Audio Issues</compatibility>
<version-tested>0.1-1558-gf852be74</version-tested>
<comments>Game needs forced 60hz timing to run at correct speed, but rendered cutscenes desync as a consequence.</comments>
</entry>
<entry code="SLUS-00152" compatibility="5" region="NTSC-U" title="Tomb Raider (USA) (Rev 3)"> <entry code="SLUS-00152" compatibility="5" region="NTSC-U" title="Tomb Raider (USA) (Rev 3)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
@ -2850,6 +2994,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
</entry> </entry>
<entry code="SLUS-00691" compatibility="5" region="NTSC-U" title="Tomb Raider III - Adventures of Lara Croft (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1580-g136a9d60</version-tested>
</entry>
<entry code="SCUS-94236" compatibility="5" region="NTSC-U" title="Tomba! (USA)"> <entry code="SCUS-94236" compatibility="5" region="NTSC-U" title="Tomba! (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
@ -3054,6 +3202,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1333-g5a955a4</version-tested> <version-tested>0.1-1333-g5a955a4</version-tested>
</entry> </entry>
<entry code="SLUS-01444" compatibility="5" region="NTSC-U" title="X-Bladez - Inline Skater (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00141" compatibility="5" region="NTSC-U" title="X-COM - UFO Defense"> <entry code="SLUS-00141" compatibility="5" region="NTSC-U" title="X-COM - UFO Defense">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
</entry> </entry>
@ -3062,6 +3214,10 @@ Tetris with Card Captor Sakura (Japan)
<upscaling-issues>Small grafics errors when the cursor is over the text (don't know if this bug occurs in the real hardware).</upscaling-issues> <upscaling-issues>Small grafics errors when the cursor is over the text (don't know if this bug occurs in the real hardware).</upscaling-issues>
<version-tested>0.1-1304-gc8b6712</version-tested> <version-tested>0.1-1304-gc8b6712</version-tested>
</entry> </entry>
<entry code="SLUS-00044" compatibility="5" region="NTSC-U" title="X-Men - Children of the Atom (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLUS-00774" compatibility="5" region="NTSC-U" title="X-Men - Mutant Academy (USA)"> <entry code="SLUS-00774" compatibility="5" region="NTSC-U" title="X-Men - Mutant Academy (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
@ -3074,6 +3230,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<upscaling-issues>Sprite glitches</upscaling-issues> <upscaling-issues>Sprite glitches</upscaling-issues>
</entry> </entry>
<entry code="SLUS-00627" compatibility="5" region="NTSC-U" title="X-Men vs. Street Fighter (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1608-g79aaf908</version-tested>
</entry>
<entry code="SLPS-01063" compatibility="5" region="NTSC-J" title="X.Racing (Japan)"> <entry code="SLPS-01063" compatibility="5" region="NTSC-J" title="X.Racing (Japan)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1448-g472f1c1c</version-tested> <version-tested>0.1-1448-g472f1c1c</version-tested>

View file

@ -3,69 +3,69 @@
# Croc - Legend of the Gobbos (USA) (SLUS-00530) # Croc - Legend of the Gobbos (USA) (SLUS-00530)
[SLUS-00530] [SLUS-00530]
EnablePGXPCPUMode = true ForcePGXPCPUMode = true
# Croc 2 (USA) (SLUS-00634) # Croc 2 (USA) (SLUS-00634)
[SLUS-00634] [SLUS-00634]
EnablePGXPCPUMode = true ForcePGXPCPUMode = true
# Doom (USA) (Rev 1) (SLUS-00077) # Doom (USA) (Rev 1) (SLUS-00077)
[SLUS-00077] [SLUS-00077]
DisableUpscaling = true DisableUpscaling = true
ForceDigitalController = true
# Pop'n Music 6 (Japan) (SLPM-87089) # Pop'n Music 6 (Japan) (SLPM-87089)
[SLPM-87089] [SLPM-87089]
EnableInterlacing = true ForceInterlacing = true
# Mr. Driller G (Japan) (SLPS-03336) # Mr. Driller G (Japan) (SLPS-03336)
[SLPS-03336] [SLPS-03336]
EnableInterlacing = true ForceInterlacing = true
# Pro Pinball - Big Race USA (USA) (SLUS-01260) # Pro Pinball - Big Race USA (USA) (SLUS-01260)
[SLUS-01260] [SLUS-01260]
ForceSoftwareRenderer = true ForceSoftwareRenderer = true
EnableInterlacing = true ForceInterlacing = true
# Pro Pinball - Fantastic Journey (USA) (SLUS-01261) # Pro Pinball - Fantastic Journey (USA) (SLUS-01261)
[SLUS-01261] [SLUS-01261]
ForceSoftwareRenderer = true ForceSoftwareRenderer = true
EnableInterlacing = true ForceInterlacing = true
# True Pinball (USA) (SLUS-00337) # True Pinball (USA) (SLUS-00337)
[SLUS-00337] [SLUS-00337]
EnableInterlacing = true ForceInterlacing = true
# Dead or Alive (USA) (SLUS-00606) # Dead or Alive (USA) (SLUS-00606)
[SLUS-00606] [SLUS-00606]
EnableInterlacing = true ForceInterlacing = true
# Shinobi no Sato no Jintori Gassen (Japan) (SLPS-03553) # Shinobi no Sato no Jintori Gassen (Japan) (SLPS-03553)
[SLPS-03553] [SLPS-03553]
EnableInterlacing = true ForceInterlacing = true
# Time Bokan Series: Bokan desu yo (SLPS-01211) # Time Bokan Series: Bokan desu yo (SLPS-01211)
[SLPS-01211] [SLPS-01211]
EnableInterlacing = true ForceInterlacing = true
# Rat Attack! (USA) (SLUS-00656) # Rat Attack! (USA) (SLUS-00656)
[SLUS-00656] [SLUS-00656]
EnableInterlacing = true ForceInterlacing = true
# Arcade Party Pak (USA) (SLUS-00952) # Arcade Party Pak (USA) (SLUS-00952)
[SLUS-00952] [SLUS-00952]
EnableInterlacing = true ForceInterlacing = true
# SLUS-01222 (Colin McRae Rally 2.0 (USA) (En,Fr,Es)) # SLUS-01222 (Colin McRae Rally 2.0 (USA) (En,Fr,Es))
@ -73,3 +73,125 @@ EnableInterlacing = true
DisplayActiveStartOffset = 64 DisplayActiveStartOffset = 64
DisplayActiveEndOffset = 68 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

View file

@ -1,11 +1,8 @@
add_subdirectory(cubeb) add_subdirectory(cubeb)
add_subdirectory(glad) add_subdirectory(glad)
add_subdirectory(googletest) add_subdirectory(googletest)
add_subdirectory(imgui)
add_subdirectory(libcue) add_subdirectory(libcue)
add_subdirectory(simpleini)
add_subdirectory(stb) add_subdirectory(stb)
add_subdirectory(tinyxml2)
add_subdirectory(zlib) add_subdirectory(zlib)
add_subdirectory(minizip) add_subdirectory(minizip)
add_subdirectory(lzma) add_subdirectory(lzma)
@ -13,10 +10,15 @@ add_subdirectory(libFLAC)
add_subdirectory(libchdr) add_subdirectory(libchdr)
add_subdirectory(xxhash) add_subdirectory(xxhash)
add_subdirectory(rapidjson) add_subdirectory(rapidjson)
add_subdirectory(glslang) add_subdirectory(glslang)
add_subdirectory(vulkan-loader) add_subdirectory(vulkan-loader)
if(NOT BUILD_LIBRETRO_CORE)
add_subdirectory(imgui)
add_subdirectory(simpleini)
add_subdirectory(tinyxml2)
endif()
if(ENABLE_DISCORD_PRESENCE) if(ENABLE_DISCORD_PRESENCE)
add_subdirectory(discord-rpc) add_subdirectory(discord-rpc)
endif() endif()

View file

@ -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_include_directories(imgui INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_compile_definitions(imgui PRIVATE "imgui_STATIC") 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()

View file

@ -37,9 +37,6 @@
<ItemGroup> <ItemGroup>
<ClInclude Include="include\imconfig.h" /> <ClInclude Include="include\imconfig.h" />
<ClInclude Include="include\imgui.h" /> <ClInclude Include="include\imgui.h" />
<ClInclude Include="include\imgui_impl_dx11.h" />
<ClInclude Include="include\imgui_impl_opengl3.h" />
<ClInclude Include="include\imgui_impl_vulkan.h" />
<ClInclude Include="include\imgui_stdlib.h" /> <ClInclude Include="include\imgui_stdlib.h" />
<ClInclude Include="src\imgui_internal.h" /> <ClInclude Include="src\imgui_internal.h" />
<ClInclude Include="src\imstb_rectpack.h" /> <ClInclude Include="src\imstb_rectpack.h" />
@ -49,20 +46,9 @@
<ItemGroup> <ItemGroup>
<ClCompile Include="src\imgui.cpp" /> <ClCompile Include="src\imgui.cpp" />
<ClCompile Include="src\imgui_draw.cpp" /> <ClCompile Include="src\imgui_draw.cpp" />
<ClCompile Include="src\imgui_impl_dx11.cpp" />
<ClCompile Include="src\imgui_impl_opengl3.cpp" />
<ClCompile Include="src\imgui_impl_vulkan.cpp" />
<ClCompile Include="src\imgui_stdlib.cpp" /> <ClCompile Include="src\imgui_stdlib.cpp" />
<ClCompile Include="src\imgui_widgets.cpp" /> <ClCompile Include="src\imgui_widgets.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\glad\glad.vcxproj">
<Project>{43540154-9e1e-409c-834f-b84be5621388}</Project>
</ProjectReference>
<ProjectReference Include="..\vulkan-loader\vulkan-loader.vcxproj">
<Project>{9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035}</Project>
</ProjectReference>
</ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>{BB08260F-6FBC-46AF-8924-090EE71360C6}</ProjectGuid> <ProjectGuid>{BB08260F-6FBC-46AF-8924-090EE71360C6}</ProjectGuid>
<Keyword>Win32Proj</Keyword> <Keyword>Win32Proj</Keyword>
@ -211,7 +197,7 @@
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>imgui_STATIC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>imgui_STATIC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
@ -231,7 +217,7 @@
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>imgui_STATIC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>imgui_STATIC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
@ -251,7 +237,7 @@
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>imgui_STATIC;WIN32;_ITERATOR_DEBUG_LEVEL=1;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>imgui_STATIC;WIN32;_ITERATOR_DEBUG_LEVEL=1;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
<SupportJustMyCode>false</SupportJustMyCode> <SupportJustMyCode>false</SupportJustMyCode>
@ -273,7 +259,7 @@
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>imgui_STATIC;WIN32;_ITERATOR_DEBUG_LEVEL=1;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>imgui_STATIC;WIN32;_ITERATOR_DEBUG_LEVEL=1;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
<SupportJustMyCode>false</SupportJustMyCode> <SupportJustMyCode>false</SupportJustMyCode>
@ -295,7 +281,7 @@
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<AdditionalOptions>/Zo /utf-8 %(AdditionalOptions)</AdditionalOptions> <AdditionalOptions>/Zo /utf-8 %(AdditionalOptions)</AdditionalOptions>
<WholeProgramOptimization>false</WholeProgramOptimization> <WholeProgramOptimization>false</WholeProgramOptimization>
@ -318,7 +304,7 @@
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<AdditionalOptions>/Zo /utf-8 %(AdditionalOptions)</AdditionalOptions> <AdditionalOptions>/Zo /utf-8 %(AdditionalOptions)</AdditionalOptions>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
@ -342,7 +328,7 @@
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<AdditionalOptions>/Zo /utf-8 %(AdditionalOptions)</AdditionalOptions> <AdditionalOptions>/Zo /utf-8 %(AdditionalOptions)</AdditionalOptions>
<WholeProgramOptimization>false</WholeProgramOptimization> <WholeProgramOptimization>false</WholeProgramOptimization>
@ -365,7 +351,7 @@
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>imgui_STATIC;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<AdditionalOptions>/Zo /utf-8 %(AdditionalOptions)</AdditionalOptions> <AdditionalOptions>/Zo /utf-8 %(AdditionalOptions)</AdditionalOptions>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>

View file

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<ClInclude Include="include\imgui_impl_dx11.h" />
<ClInclude Include="include\imgui_impl_opengl3.h" />
<ClInclude Include="include\imconfig.h" /> <ClInclude Include="include\imconfig.h" />
<ClInclude Include="include\imgui.h" /> <ClInclude Include="include\imgui.h" />
<ClInclude Include="src\imgui_internal.h" /> <ClInclude Include="src\imgui_internal.h" />
@ -10,15 +8,11 @@
<ClInclude Include="src\imstb_textedit.h" /> <ClInclude Include="src\imstb_textedit.h" />
<ClInclude Include="src\imstb_truetype.h" /> <ClInclude Include="src\imstb_truetype.h" />
<ClInclude Include="include\imgui_stdlib.h" /> <ClInclude Include="include\imgui_stdlib.h" />
<ClInclude Include="include\imgui_impl_vulkan.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="src\imgui_impl_opengl3.cpp" />
<ClCompile Include="src\imgui_widgets.cpp" /> <ClCompile Include="src\imgui_widgets.cpp" />
<ClCompile Include="src\imgui.cpp" /> <ClCompile Include="src\imgui.cpp" />
<ClCompile Include="src\imgui_draw.cpp" /> <ClCompile Include="src\imgui_draw.cpp" />
<ClCompile Include="src\imgui_impl_dx11.cpp" />
<ClCompile Include="src\imgui_stdlib.cpp" /> <ClCompile Include="src\imgui_stdlib.cpp" />
<ClCompile Include="src\imgui_impl_vulkan.cpp" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -7,6 +7,8 @@
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring>
#include <string>
#include "vulkan_loader.h" #include "vulkan_loader.h"
@ -14,6 +16,10 @@
#include <dlfcn.h> #include <dlfcn.h>
#endif #endif
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
#define VULKAN_MODULE_ENTRY_POINT(name, required) PFN_##name name; #define VULKAN_MODULE_ENTRY_POINT(name, required) PFN_##name name;
#define VULKAN_INSTANCE_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; #define VULKAN_DEVICE_ENTRY_POINT(name, required) PFN_##name name;
@ -111,6 +117,25 @@ bool LoadVulkanLibrary()
char* libvulkan_env = getenv("LIBVULKAN_PATH"); char* libvulkan_env = getenv("LIBVULKAN_PATH");
if (libvulkan_env) if (libvulkan_env)
vulkan_module = dlopen(libvulkan_env, RTLD_NOW); 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) if (!vulkan_module)
vulkan_module = dlopen("libvulkan.dylib", RTLD_NOW); vulkan_module = dlopen("libvulkan.dylib", RTLD_NOW);
#else #else

View file

@ -84,8 +84,8 @@ bool AutoStagingTexture::EnsureSize(ID3D11DeviceContext* context, u32 width, u32
if (m_texture && m_width >= width && m_height >= height && m_format == format) if (m_texture && m_width >= width && m_height >= height && m_format == format)
return true; return true;
ID3D11Device* device; ComPtr<ID3D11Device> device;
context->GetDevice(&device); context->GetDevice(device.GetAddressOf());
CD3D11_TEXTURE2D_DESC new_desc(format, width, height, 1, 1, 0, CD3D11_TEXTURE2D_DESC new_desc(format, width, height, 1, 1, 0,
for_uploading ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_STAGING, for_uploading ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_STAGING,

View file

@ -17,7 +17,7 @@
// Force inline in non-debug helper // Force inline in non-debug helper
#ifdef _DEBUG #ifdef _DEBUG
#define ALWAYS_INLINE_RELEASE #define ALWAYS_INLINE_RELEASE inline
#else #else
#define ALWAYS_INLINE_RELEASE ALWAYS_INLINE #define ALWAYS_INLINE_RELEASE ALWAYS_INLINE
#endif #endif

View file

@ -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) 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); Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Upload);
if (!PrepareForAccess())
return;
Assert((src_x + width) <= m_width && (src_y + height) <= m_height); Assert((src_x + width) <= m_width && (src_y + height) <= m_height);
PrepareForAccess();
// Offset pointer to point to start of region being copied out. // Offset pointer to point to start of region being copied out.
const char* current_ptr = m_staging_buffer.GetMapPointer(); 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) void StagingTexture::ReadTexel(u32 x, u32 y, void* out_ptr)
{ {
Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Upload); Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Upload);
if (!PrepareForAccess())
return;
Assert(x < m_width && y < m_height); Assert(x < m_width && y < m_height);
PrepareForAccess();
const char* src_ptr = GetMappedPointer() + y * GetMappedStride() + x * m_texel_size; const char* src_ptr = GetMappedPointer() + y * GetMappedStride() + x * m_texel_size;
std::memcpy(out_ptr, src_ptr, 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) 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); Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Readback);
if (!PrepareForAccess())
return;
Assert((dst_x + width) <= m_width && (dst_y + height) <= m_height); Assert((dst_x + width) <= m_width && (dst_y + height) <= m_height);
PrepareForAccess();
// Offset pointer to point to start of region being copied to. // Offset pointer to point to start of region being copied to.
char* current_ptr = GetMappedPointer(); 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) void StagingTexture::WriteTexel(u32 x, u32 y, const void* in_ptr)
{ {
if (!PrepareForAccess())
return;
Assert(x < m_width && y < m_height); Assert(x < m_width && y < m_height);
PrepareForAccess();
char* dest_ptr = GetMappedPointer() + y * m_map_stride + x * m_texel_size; char* dest_ptr = GetMappedPointer() + y * m_map_stride + x * m_texel_size;
std::memcpy(dest_ptr, in_ptr, 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 (m_needs_flush)
{
if (IsMapped())
Unmap();
Flush(); Flush();
} }
return IsMapped() || Map();
}
} // namespace Vulkan } // namespace Vulkan

View file

@ -73,7 +73,7 @@ public:
void WriteTexel(u32 x, u32 y, const void* in_ptr); void WriteTexel(u32 x, u32 y, const void* in_ptr);
private: private:
bool PrepareForAccess(); void PrepareForAccess();
StagingBuffer m_staging_buffer; StagingBuffer m_staging_buffer;
u64 m_flush_fence_counter = 0; u64 m_flush_fence_counter = 0;

View file

@ -24,10 +24,6 @@ add_library(core
digital_controller.h digital_controller.h
dma.cpp dma.cpp
dma.h dma.h
game_list.cpp
game_list.h
game_settings.cpp
game_settings.h
gpu.cpp gpu.cpp
gpu.h gpu.h
gpu_commands.cpp gpu_commands.cpp
@ -98,7 +94,7 @@ set(RECOMPILER_SRCS
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(core PUBLIC "${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) target_link_libraries(core PRIVATE glad stb)
if(WIN32) if(WIN32)
@ -126,3 +122,8 @@ elseif(${CPU_ARCH} STREQUAL "aarch64")
else() else()
message("Not building recompiler") message("Not building recompiler")
endif() endif()
if(NOT BUILD_LIBRETRO_CORE)
target_link_libraries(core PRIVATE imgui)
target_compile_definitions(core PRIVATE "WITH_IMGUI=1")
endif()

View file

@ -466,25 +466,31 @@ std::optional<s32> AnalogController::StaticGetButtonCodeByName(std::string_view
Controller::AxisList AnalogController::StaticGetAxisNames() Controller::AxisList AnalogController::StaticGetAxisNames()
{ {
#define A(n) \ return {{TRANSLATABLE("AnalogController", "LeftX"), static_cast<s32>(Axis::LeftX), AxisType::Full},
{ \ {TRANSLATABLE("AnalogController", "LeftY"), static_cast<s32>(Axis::LeftY), AxisType::Full},
#n, static_cast < s32>(Axis::n) \ {TRANSLATABLE("AnalogController", "RightX"), static_cast<s32>(Axis::RightX), AxisType::Full},
} {TRANSLATABLE("AnalogController", "RightY"), static_cast<s32>(Axis::RightY), AxisType::Full}};
return {A(LeftX), A(LeftY), A(RightX), A(RightY)};
#undef A
} }
Controller::ButtonList AnalogController::StaticGetButtonNames() Controller::ButtonList AnalogController::StaticGetButtonNames()
{ {
#define B(n) \ return {{TRANSLATABLE("AnalogController", "Up"), static_cast<s32>(Button::Up)},
{ \ {TRANSLATABLE("AnalogController", "Down"), static_cast<s32>(Button::Down)},
#n, static_cast < s32>(Button::n) \ {TRANSLATABLE("AnalogController", "Left"), static_cast<s32>(Button::Left)},
} {TRANSLATABLE("AnalogController", "Right"), static_cast<s32>(Button::Right)},
return {B(Up), B(Down), B(Left), B(Right), B(Select), B(Start), B(Triangle), B(Cross), B(Circle), {TRANSLATABLE("AnalogController", "Select"), static_cast<s32>(Button::Select)},
B(Square), B(L1), B(L2), B(R1), B(R2), B(L3), B(R3), B(Analog)}; {TRANSLATABLE("AnalogController", "Start"), static_cast<s32>(Button::Start)},
#undef B {TRANSLATABLE("AnalogController", "Triangle"), static_cast<s32>(Button::Triangle)},
{TRANSLATABLE("AnalogController", "Cross"), static_cast<s32>(Button::Cross)},
{TRANSLATABLE("AnalogController", "Circle"), static_cast<s32>(Button::Circle)},
{TRANSLATABLE("AnalogController", "Square"), static_cast<s32>(Button::Square)},
{TRANSLATABLE("AnalogController", "L1"), static_cast<s32>(Button::L1)},
{TRANSLATABLE("AnalogController", "L2"), static_cast<s32>(Button::L2)},
{TRANSLATABLE("AnalogController", "R1"), static_cast<s32>(Button::R1)},
{TRANSLATABLE("AnalogController", "R2"), static_cast<s32>(Button::R2)},
{TRANSLATABLE("AnalogController", "L3"), static_cast<s32>(Button::L3)},
{TRANSLATABLE("AnalogController", "R3"), static_cast<s32>(Button::R3)},
{TRANSLATABLE("AnalogController", "Analog"), static_cast<s32>(Button::Analog)}};
} }
u32 AnalogController::StaticGetVibrationMotorCount() u32 AnalogController::StaticGetVibrationMotorCount()
@ -495,11 +501,14 @@ u32 AnalogController::StaticGetVibrationMotorCount()
Controller::SettingList AnalogController::StaticGetSettings() Controller::SettingList AnalogController::StaticGetSettings()
{ {
static constexpr std::array<SettingInfo, 2> settings = { static constexpr std::array<SettingInfo, 2> settings = {
{{SettingInfo::Type::Boolean, "AutoEnableAnalog", "Enable Analog Mode on Reset", {{SettingInfo::Type::Boolean, "AutoEnableAnalog", TRANSLATABLE("AnalogController", "Enable Analog Mode on Reset"),
"Automatically enables analog mode when the console is reset/powered on.", "false"}, TRANSLATABLE("AnalogController", "Automatically enables analog mode when the console is reset/powered on."),
{SettingInfo::Type::Float, "AxisScale", "Analog Axis Scale", "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 " "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.", "controllers, e.g. DualShock 4, Xbox One Controller."),
"1.00f", "0.01f", "1.50f", "0.01f"}}}; "1.00f", "0.01f", "1.50f", "0.01f"}}};
return SettingList(settings.begin(), settings.end()); return SettingList(settings.begin(), settings.end());

View file

@ -742,10 +742,153 @@ ALWAYS_INLINE static TickCount DoDMAAccess(u32 offset, u32& value)
namespace CPU { namespace CPU {
template<bool add_ticks, bool icache_read = false, u32 word_count = 1>
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<u32>(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<u32>(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<u32>(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<true, true, 4>(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) static void WriteCacheControl(u32 value)
{ {
Log_WarningPrintf("Cache control <- 0x%08X", 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<MemoryAccessType type, MemoryAccessSize size> template<MemoryAccessType type, MemoryAccessSize size>
@ -797,8 +940,11 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32&
if constexpr (type == MemoryAccessType::Write) if constexpr (type == MemoryAccessType::Write)
{ {
if (g_state.cop0_regs.sr.Isc) if (g_state.cop0_regs.sr.Isc)
{
WriteICache(address, value);
return 0; return 0;
} }
}
address &= PHYSICAL_MEMORY_ADDRESS_MASK; address &= PHYSICAL_MEMORY_ADDRESS_MASK;
if ((address & DCACHE_LOCATION_MASK) == DCACHE_LOCATION) if ((address & DCACHE_LOCATION_MASK) == DCACHE_LOCATION)
@ -829,7 +975,7 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32&
if (address == 0xFFFE0130) if (address == 0xFFFE0130)
{ {
if constexpr (type == MemoryAccessType::Read) if constexpr (type == MemoryAccessType::Read)
value = g_state.cache_control; value = g_state.cache_control.bits;
else else
WriteCacheControl(value); WriteCacheControl(value);
@ -849,6 +995,10 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32&
{ {
return DoRAMAccess<type, size>(address, value); return DoRAMAccess<type, size>(address, value);
} }
else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE))
{
return DoBIOSAccess<type, size>(static_cast<u32>(address - BIOS_BASE), value);
}
else if (address < EXP1_BASE) else if (address < EXP1_BASE)
{ {
return DoInvalidAccess(type, size, address, value); return DoInvalidAccess(type, size, address, value);
@ -921,14 +1071,6 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32&
{ {
return DoEXP2Access<type, size>(address & EXP2_MASK, value); return DoEXP2Access<type, size>(address & EXP2_MASK, value);
} }
else if (address < BIOS_BASE)
{
return DoInvalidAccess(type, size, address, value);
}
else if (address < (BIOS_BASE + BIOS_SIZE))
{
return DoBIOSAccess<type, size>(static_cast<u32>(address - BIOS_BASE), value);
}
else else
{ {
return DoInvalidAccess(type, size, address, value); return DoInvalidAccess(type, size, address, value);
@ -961,19 +1103,76 @@ static bool DoAlignmentCheck(VirtualMemoryAddress address)
bool FetchInstruction() bool FetchInstruction()
{ {
DebugAssert(Common::IsAlignedPow2(g_state.regs.npc, 4)); DebugAssert(Common::IsAlignedPow2(g_state.regs.npc, 4));
if (DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(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. case 0x00: // KUSEG 0M-512M
RaiseException(g_state.regs.npc, Cop0Registers::CAUSE::MakeValueForException(Exception::IBE, false, false, 0)); 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<true, false, 1>(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; return false;
} }
}
g_state.regs.pc = g_state.regs.npc; g_state.regs.pc = g_state.regs.npc;
g_state.regs.npc += sizeof(g_state.next_instruction.bits); g_state.regs.npc += sizeof(g_state.next_instruction.bits);
return true; 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<false, false, 1>(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) bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value)
{ {
u32 temp = 0; u32 temp = 0;

View file

@ -78,41 +78,6 @@ extern std::bitset<CPU_CODE_CACHE_PAGE_COUNT> m_ram_code_bits;
extern u8 g_ram[RAM_SIZE]; // 2MB RAM extern u8 g_ram[RAM_SIZE]; // 2MB RAM
extern u8 g_bios[BIOS_SIZE]; // 512K BIOS ROM 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. /// 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; } ALWAYS_INLINE void SetRAMCodePage(u32 index) { m_ram_code_bits[index] = true; }

View file

@ -3,12 +3,13 @@
#include "common/log.h" #include "common/log.h"
#include "common/state_wrapper.h" #include "common/state_wrapper.h"
#include "dma.h" #include "dma.h"
#include "game_list.h"
#include "imgui.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "settings.h" #include "settings.h"
#include "spu.h" #include "spu.h"
#include "system.h" #include "system.h"
#ifdef WITH_IMGUI
#include "imgui.h"
#endif
Log_SetChannel(CDROM); Log_SetChannel(CDROM);
struct CommandInfo struct CommandInfo
@ -444,7 +445,7 @@ void CDROM::InsertMedia(std::unique_ptr<CDImage> media)
RemoveMedia(); RemoveMedia();
// set the region from the system area of the disc // 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), Log_InfoPrintf("Inserting new media, disc region: %s, console region: %s", Settings::GetDiscRegionName(m_disc_region),
Settings::GetConsoleRegionName(System::GetRegion())); 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.ClearActiveBits();
m_secondary_status.motor_on = true; m_secondary_status.motor_on = true;
m_secondary_status.playing_cdda = true;
ClearSectorBuffers(); ClearSectorBuffers();
ResetAudioDecoder(); ResetAudioDecoder();
@ -2241,11 +2243,8 @@ void CDROM::ProcessCDDASector(const u8* raw_sector, const CDImage::SubChannelQ&
// For CDDA sectors, the whole sector contains the audio data. // For CDDA sectors, the whole sector contains the audio data.
Log_DevPrintf("Read sector %u as CDDA", m_current_lba); 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. // The reporting doesn't happen if we're reading with the CDDA mode bit set.
if (m_drive_state == DriveState::Playing) 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; const u8 frame_nibble = subq.absolute_frame_bcd >> 4;
if (m_last_cdda_report_frame_nibble != frame_nibble) if (m_last_cdda_report_frame_nibble != frame_nibble)
@ -2278,7 +2277,6 @@ void CDROM::ProcessCDDASector(const u8* raw_sector, const CDImage::SubChannelQ&
SetAsyncInterrupt(Interrupt::DataReady); SetAsyncInterrupt(Interrupt::DataReady);
} }
} }
}
// Apply volume when pushing sectors to SPU. // Apply volume when pushing sectors to SPU.
if (m_muted) if (m_muted)
@ -2344,6 +2342,7 @@ void CDROM::ClearSectorBuffers()
void CDROM::DrawDebugWindow() void CDROM::DrawDebugWindow()
{ {
#ifdef WITH_IMGUI
static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; 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}; static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f};
const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x;
@ -2524,4 +2523,5 @@ void CDROM::DrawDebugWindow()
} }
ImGui::End(); ImGui::End();
#endif
} }

View file

@ -14,8 +14,14 @@ class HostInterface;
class Controller class Controller
{ {
public: public:
enum class AxisType : u8
{
Full,
Half
};
using ButtonList = std::vector<std::pair<std::string, s32>>; using ButtonList = std::vector<std::pair<std::string, s32>>;
using AxisList = std::vector<std::pair<std::string, s32>>; using AxisList = std::vector<std::tuple<std::string, s32, AxisType>>;
using SettingList = std::vector<SettingInfo>; using SettingList = std::vector<SettingInfo>;
Controller(); Controller();

View file

@ -59,8 +59,6 @@
<ClCompile Include="cpu_recompiler_register_cache.cpp" /> <ClCompile Include="cpu_recompiler_register_cache.cpp" />
<ClCompile Include="cpu_types.cpp" /> <ClCompile Include="cpu_types.cpp" />
<ClCompile Include="digital_controller.cpp" /> <ClCompile Include="digital_controller.cpp" />
<ClCompile Include="game_list.cpp" />
<ClCompile Include="game_settings.cpp" />
<ClCompile Include="gpu_commands.cpp" /> <ClCompile Include="gpu_commands.cpp" />
<ClCompile Include="gpu_hw_d3d11.cpp" /> <ClCompile Include="gpu_hw_d3d11.cpp" />
<ClCompile Include="gpu_hw_shadergen.cpp" /> <ClCompile Include="gpu_hw_shadergen.cpp" />
@ -107,8 +105,6 @@
<ClInclude Include="cpu_recompiler_thunks.h" /> <ClInclude Include="cpu_recompiler_thunks.h" />
<ClInclude Include="cpu_recompiler_types.h" /> <ClInclude Include="cpu_recompiler_types.h" />
<ClInclude Include="digital_controller.h" /> <ClInclude Include="digital_controller.h" />
<ClInclude Include="game_list.h" />
<ClInclude Include="game_settings.h" />
<ClInclude Include="gpu_hw_d3d11.h" /> <ClInclude Include="gpu_hw_d3d11.h" />
<ClInclude Include="gpu_hw_shadergen.h" /> <ClInclude Include="gpu_hw_shadergen.h" />
<ClInclude Include="gpu_hw_vulkan.h" /> <ClInclude Include="gpu_hw_vulkan.h" />
@ -147,14 +143,14 @@
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj"> <ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project> <Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\dep\simpleini\simpleini.vcxproj">
<Project>{3773f4cc-614e-4028-8595-22e08ca649e3}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\stb\stb.vcxproj"> <ProjectReference Include="..\..\dep\stb\stb.vcxproj">
<Project>{ed601289-ac1a-46b8-a8ed-17db9eb73423}</Project> <Project>{ed601289-ac1a-46b8-a8ed-17db9eb73423}</Project>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\dep\tinyxml2\tinyxml2.vcxproj"> <ProjectReference Include="..\..\dep\vulkan-loader\vulkan-loader.vcxproj">
<Project>{933118a9-68c5-47b4-b151-b03c93961623}</Project> <Project>{9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\zlib\zlib.vcxproj">
<Project>{7ff9fdb9-d504-47db-a16a-b08071999620}</Project>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\common\common.vcxproj"> <ProjectReference Include="..\common\common.vcxproj">
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project> <Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
@ -304,10 +300,10 @@
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level4</WarningLevel> <WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild> <MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -330,10 +326,10 @@
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level4</WarningLevel> <WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild> <MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -356,10 +352,10 @@
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level4</WarningLevel> <WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks> <BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild> <MinimalRebuild>false</MinimalRebuild>
@ -385,10 +381,10 @@
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level4</WarningLevel> <WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks> <BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild> <MinimalRebuild>false</MinimalRebuild>
@ -415,8 +411,8 @@
</PrecompiledHeader> </PrecompiledHeader>
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization> <WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -442,8 +438,8 @@
</PrecompiledHeader> </PrecompiledHeader>
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -470,8 +466,8 @@
</PrecompiledHeader> </PrecompiledHeader>
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization> <WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -497,8 +493,8 @@
</PrecompiledHeader> </PrecompiledHeader>
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>

View file

@ -31,7 +31,6 @@
<ClCompile Include="cpu_recompiler_code_generator.cpp" /> <ClCompile Include="cpu_recompiler_code_generator.cpp" />
<ClCompile Include="cpu_recompiler_code_generator_generic.cpp" /> <ClCompile Include="cpu_recompiler_code_generator_generic.cpp" />
<ClCompile Include="cpu_types.cpp" /> <ClCompile Include="cpu_types.cpp" />
<ClCompile Include="game_list.cpp" />
<ClCompile Include="cpu_recompiler_code_generator_aarch64.cpp" /> <ClCompile Include="cpu_recompiler_code_generator_aarch64.cpp" />
<ClCompile Include="sio.cpp" /> <ClCompile Include="sio.cpp" />
<ClCompile Include="controller.cpp" /> <ClCompile Include="controller.cpp" />
@ -47,7 +46,6 @@
<ClCompile Include="resources.cpp" /> <ClCompile Include="resources.cpp" />
<ClCompile Include="host_interface_progress_callback.cpp" /> <ClCompile Include="host_interface_progress_callback.cpp" />
<ClCompile Include="pgxp.cpp" /> <ClCompile Include="pgxp.cpp" />
<ClCompile Include="game_settings.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="types.h" /> <ClInclude Include="types.h" />
@ -82,7 +80,6 @@
<ClInclude Include="cpu_recompiler_register_cache.h" /> <ClInclude Include="cpu_recompiler_register_cache.h" />
<ClInclude Include="cpu_recompiler_thunks.h" /> <ClInclude Include="cpu_recompiler_thunks.h" />
<ClInclude Include="cpu_recompiler_code_generator.h" /> <ClInclude Include="cpu_recompiler_code_generator.h" />
<ClInclude Include="game_list.h" />
<ClInclude Include="sio.h" /> <ClInclude Include="sio.h" />
<ClInclude Include="controller.h" /> <ClInclude Include="controller.h" />
<ClInclude Include="analog_controller.h" /> <ClInclude Include="analog_controller.h" />
@ -98,6 +95,5 @@
<ClInclude Include="gte_types.h" /> <ClInclude Include="gte_types.h" />
<ClInclude Include="pgxp.h" /> <ClInclude Include="pgxp.h" />
<ClInclude Include="cpu_core_private.h" /> <ClInclude Include="cpu_core_private.h" />
<ClInclude Include="game_settings.h" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -139,8 +139,7 @@ static void ExecuteImpl()
{ {
if (HasPendingInterrupt()) if (HasPendingInterrupt())
{ {
// TODO: Fill in m_next_instruction... SafeReadInstruction(g_state.regs.pc, &g_state.next_instruction.bits);
SafeReadMemoryWord(g_state.regs.pc, &g_state.next_instruction.bits);
DispatchInterrupt(); DispatchInterrupt();
next_block_key = GetNextBlockKey(); next_block_key = GetNextBlockKey();
} }
@ -165,6 +164,9 @@ static void ExecuteImpl()
LogCurrentState(); LogCurrentState();
#endif #endif
if (g_settings.cpu_recompiler_icache)
CheckAndUpdateICacheTags(block->icache_line_count, block->uncached_fetch_ticks);
InterpretCachedBlock<pgxp_mode>(*block); InterpretCachedBlock<pgxp_mode>(*block);
if (g_state.pending_ticks >= g_state.downcount) if (g_state.pending_ticks >= g_state.downcount)
@ -247,7 +249,7 @@ void ExecuteRecompiler()
{ {
if (HasPendingInterrupt()) if (HasPendingInterrupt())
{ {
SafeReadMemoryWord(g_state.regs.pc, &g_state.next_instruction.bits); SafeReadInstruction(g_state.regs.pc, &g_state.next_instruction.bits);
DispatchInterrupt(); DispatchInterrupt();
} }
@ -351,7 +353,8 @@ bool RevalidateBlock(CodeBlock* block)
{ {
for (const CodeBlockInstruction& cbi : block->instructions) 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) if (cbi.instruction.bits != new_code)
{ {
Log_DebugPrintf("Block 0x%08X changed at PC 0x%08X - %08X to %08X - recompiling.", block->GetPC(), cbi.pc, 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(); __debugbreak();
#endif #endif
u32 last_cache_line = ICACHE_LINES;
for (;;) for (;;)
{ {
CodeBlockInstruction cbi = {}; CodeBlockInstruction cbi = {};
if (!SafeReadInstruction(pc, &cbi.instruction.bits) || !IsInvalidInstruction(cbi.instruction))
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))
break; break;
cbi.pc = pc; cbi.pc = pc;
@ -416,6 +415,18 @@ bool CompileBlock(CodeBlock* block)
cbi.has_load_delay = InstructionHasLoadDelay(cbi.instruction); cbi.has_load_delay = InstructionHasLoadDelay(cbi.instruction);
cbi.can_trap = CanInstructionTrap(cbi.instruction, InUserMode()); 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 // instruction is decoded now
block->instructions.push_back(cbi); block->instructions.push_back(cbi);
pc += sizeof(cbi.instruction.bits); pc += sizeof(cbi.instruction.bits);

View file

@ -61,6 +61,8 @@ struct CodeBlock
std::vector<CodeBlock*> link_predecessors; std::vector<CodeBlock*> link_predecessors;
std::vector<CodeBlock*> link_successors; std::vector<CodeBlock*> link_successors;
TickCount uncached_fetch_ticks = 0;
u32 icache_line_count = 0;
bool invalidated = false; bool invalidated = false;
const u32 GetPC() const { return key.GetPC(); } const u32 GetPC() const { return key.GetPC(); }

View file

@ -61,6 +61,7 @@ void Initialize()
void Shutdown() void Shutdown()
{ {
// GTE::Shutdown(); // GTE::Shutdown();
PGXP::Shutdown();
} }
void Reset() void Reset()
@ -80,6 +81,8 @@ void Reset()
g_state.cop0_regs.sr.bits = 0; g_state.cop0_regs.sr.bits = 0;
g_state.cop0_regs.cause.bits = 0; g_state.cop0_regs.cause.bits = 0;
ClearICache();
GTE::Reset(); GTE::Reset();
SetPC(RESET_VECTOR); SetPC(RESET_VECTOR);
@ -117,14 +120,18 @@ bool DoState(StateWrapper& sw)
sw.Do(&g_state.load_delay_value); sw.Do(&g_state.load_delay_value);
sw.Do(&g_state.next_load_delay_reg); sw.Do(&g_state.next_load_delay_reg);
sw.Do(&g_state.next_load_delay_value); 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()); sw.DoBytes(g_state.dcache.data(), g_state.dcache.size());
if (!GTE::DoState(sw)) if (!GTE::DoState(sw))
return false; return false;
if (sw.IsReading()) if (sw.IsReading())
{
ClearICache();
if (g_settings.gpu_pgxp_enable)
PGXP::Initialize(); PGXP::Initialize();
}
return !sw.HasError(); return !sw.HasError();
} }
@ -1416,7 +1423,6 @@ void InterpretCachedBlock(const CodeBlock& block)
{ {
// set up the state so we've already fetched the instruction // set up the state so we've already fetched the instruction
DebugAssert(g_state.regs.pc == block.GetPC()); DebugAssert(g_state.regs.pc == block.GetPC());
g_state.regs.npc = block.GetPC() + 4; g_state.regs.npc = block.GetPC() + 4;
for (const CodeBlockInstruction& cbi : block.instructions) for (const CodeBlockInstruction& cbi : block.instructions)

View file

@ -19,7 +19,32 @@ enum : PhysicalMemoryAddress
DCACHE_LOCATION = UINT32_C(0x1F800000), DCACHE_LOCATION = UINT32_C(0x1F800000),
DCACHE_LOCATION_MASK = UINT32_C(0xFFFFFC00), DCACHE_LOCATION_MASK = UINT32_C(0xFFFFFC00),
DCACHE_OFFSET_MASK = UINT32_C(0x000003FF), 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<u32, bool, 0, 1> lock_mode;
BitField<u32, bool, 1, 1> invalidate_mode;
BitField<u32, bool, 2, 1> tag_test_mode;
BitField<u32, bool, 3, 1> dcache_scratchpad;
BitField<u32, bool, 7, 1> dcache_enable;
BitField<u32, u8, 8, 2> icache_fill_size; // actually dcache? icache always fills to 16 bytes
BitField<u32, bool, 11, 1> icache_enable;
}; };
struct State struct State
@ -49,13 +74,15 @@ struct State
Reg next_load_delay_reg = Reg::count; Reg next_load_delay_reg = Reg::count;
u32 next_load_delay_value = 0; 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 registers are stored here so we can access them on ARM with a single instruction
GTE::Regs gte_regs = {}; GTE::Regs gte_regs = {};
// data cache (used as scratchpad) // data cache (used as scratchpad)
std::array<u8, DCACHE_SIZE> dcache = {}; std::array<u8, DCACHE_SIZE> dcache = {};
std::array<u32, ICACHE_LINES> icache_tags = {};
std::array<u8, ICACHE_SIZE> icache_data = {};
}; };
extern State g_state; extern State g_state;
@ -64,6 +91,7 @@ void Initialize();
void Shutdown(); void Shutdown();
void Reset(); void Reset();
bool DoState(StateWrapper& sw); bool DoState(StateWrapper& sw);
void ClearICache();
/// Executes interpreter loop. /// Executes interpreter loop.
void Execute(); void Execute();

View file

@ -34,8 +34,38 @@ ALWAYS_INLINE static void DispatchInterrupt()
g_state.regs.pc); 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. // defined in cpu_memory.cpp - memory access functions which return false if an exception was thrown.
bool FetchInstruction(); bool FetchInstruction();
bool SafeReadInstruction(VirtualMemoryAddress addr, u32* value);
bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value); bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value);
bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value); bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value);
bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value); bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value);

View file

@ -34,7 +34,7 @@ bool CodeGenerator::CompileBlock(const CodeBlock* block, CodeBlock::HostCodePoin
const CodeBlockInstruction* cbi = m_block_start; const CodeBlockInstruction* cbi = m_block_start;
while (cbi != m_block_end) while (cbi != m_block_end)
{ {
#ifndef Y_BUILD_CONFIG_RELEASE #ifdef _DEBUG
SmallString disasm; SmallString disasm;
DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits, nullptr); DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits, nullptr);
Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray()); Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray());
@ -840,6 +840,9 @@ void CodeGenerator::BlockPrologue()
{ {
EmitStoreCPUStructField(offsetof(State, exception_raised), Value::FromConstantU8(0)); 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 // 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 // TODO: Pull load delay into register cache
m_current_instruction_in_branch_delay_slot_dirty = true; m_current_instruction_in_branch_delay_slot_dirty = true;

View file

@ -61,6 +61,7 @@ public:
void EmitFlushInterpreterLoadDelay(); void EmitFlushInterpreterLoadDelay();
void EmitMoveNextInterpreterLoadDelay(); void EmitMoveNextInterpreterLoadDelay();
void EmitCancelInterpreterLoadDelayForReg(Reg reg); void EmitCancelInterpreterLoadDelayForReg(Reg reg);
void EmitICacheCheckAndUpdate();
void EmitLoadCPUStructField(HostReg host_reg, RegSize size, u32 offset); void EmitLoadCPUStructField(HostReg host_reg, RegSize size, u32 offset);
void EmitStoreCPUStructField(u32 offset, const Value& value); void EmitStoreCPUStructField(u32 offset, const Value& value);
void EmitAddCPUStructField(u32 offset, const Value& value); void EmitAddCPUStructField(u32 offset, const Value& value);

View file

@ -18,6 +18,7 @@ constexpr HostReg RARG1 = 0;
constexpr HostReg RARG2 = 1; constexpr HostReg RARG2 = 1;
constexpr HostReg RARG3 = 2; constexpr HostReg RARG3 = 2;
constexpr HostReg RARG4 = 3; constexpr HostReg RARG4 = 3;
constexpr HostReg RSCRATCH = 8;
constexpr u64 FUNCTION_CALL_STACK_ALIGNMENT = 16; constexpr u64 FUNCTION_CALL_STACK_ALIGNMENT = 16;
constexpr u64 FUNCTION_CALL_SHADOW_SPACE = 32; constexpr u64 FUNCTION_CALL_SHADOW_SPACE = 32;
constexpr u64 FUNCTION_CALLEE_SAVED_SPACE_RESERVE = 80; // 8 registers 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 // TODO: function calls mess up the parameter registers if we use them.. fix it
// allocate nonvolatile before volatile // allocate nonvolatile before volatile
m_register_cache.SetHostRegAllocationOrder( 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.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.SetCalleeSavedHostRegs({19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30});
m_register_cache.SetCPUPtrHostReg(RCPUPTR); 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); const bool use_blr = !vixl::IsInt26(displacement);
if (use_blr) if (use_blr)
{ {
m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast<uintptr_t>(ptr)); m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast<uintptr_t>(ptr));
m_emit->Blr(GetHostReg64(RRETURN)); m_emit->Blr(GetHostReg64(RSCRATCH));
} }
else else
{ {
@ -1012,8 +1013,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co
const bool use_blr = !vixl::IsInt26(displacement); const bool use_blr = !vixl::IsInt26(displacement);
if (use_blr) if (use_blr)
{ {
m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast<uintptr_t>(ptr)); m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast<uintptr_t>(ptr));
m_emit->Blr(GetHostReg64(RRETURN)); m_emit->Blr(GetHostReg64(RSCRATCH));
} }
else else
{ {
@ -1048,8 +1049,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co
const bool use_blr = !vixl::IsInt26(displacement); const bool use_blr = !vixl::IsInt26(displacement);
if (use_blr) if (use_blr)
{ {
m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast<uintptr_t>(ptr)); m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast<uintptr_t>(ptr));
m_emit->Blr(GetHostReg64(RRETURN)); m_emit->Blr(GetHostReg64(RSCRATCH));
} }
else else
{ {
@ -1086,8 +1087,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co
const bool use_blr = !vixl::IsInt26(displacement); const bool use_blr = !vixl::IsInt26(displacement);
if (use_blr) if (use_blr)
{ {
m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast<uintptr_t>(ptr)); m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast<uintptr_t>(ptr));
m_emit->Blr(GetHostReg64(RRETURN)); m_emit->Blr(GetHostReg64(RSCRATCH));
} }
else else
{ {
@ -1125,8 +1126,8 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co
const bool use_blr = !vixl::IsInt26(displacement); const bool use_blr = !vixl::IsInt26(displacement);
if (use_blr) if (use_blr)
{ {
m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast<uintptr_t>(ptr)); m_emit->Mov(GetHostReg64(RSCRATCH), reinterpret_cast<uintptr_t>(ptr));
m_emit->Blr(GetHostReg64(RRETURN)); m_emit->Blr(GetHostReg64(RSCRATCH));
} }
else else
{ {

View file

@ -1,4 +1,5 @@
#include "cpu_core.h" #include "cpu_core.h"
#include "cpu_core_private.h"
#include "cpu_recompiler_code_generator.h" #include "cpu_recompiler_code_generator.h"
namespace CPU::Recompiler { namespace CPU::Recompiler {
@ -22,4 +23,48 @@ void CodeGenerator::EmitStoreInterpreterLoadDelay(Reg reg, const Value& value)
m_load_delay_dirty = true; 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<u32>(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<u32>(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 } // namespace CPU::Recompiler

View file

@ -2187,6 +2187,52 @@ void CodeGenerator::EmitCancelInterpreterLoadDelayForReg(Reg reg)
m_emit->L(skip_cancel); 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<u32>(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<u32>(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) void CodeGenerator::EmitBranch(const void* address, bool allow_scratch)
{ {
const s64 jump_distance = const s64 jump_distance =

View file

@ -14,6 +14,7 @@ namespace Recompiler::Thunks {
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
bool InterpretInstruction(); bool InterpretInstruction();
bool InterpretInstructionPGXP(); bool InterpretInstructionPGXP();
void CheckAndUpdateICache(u32 pc, u32 line_count);
// Memory access functions for the JIT - MSB is set on exception. // Memory access functions for the JIT - MSB is set on exception.
u64 ReadMemoryByte(u32 address); u64 ReadMemoryByte(u32 address);

View file

@ -1,6 +1,7 @@
#include "digital_controller.h" #include "digital_controller.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/state_wrapper.h" #include "common/state_wrapper.h"
#include "host_interface.h"
DigitalController::DigitalController() = default; DigitalController::DigitalController() = default;
@ -155,13 +156,20 @@ Controller::AxisList DigitalController::StaticGetAxisNames()
Controller::ButtonList DigitalController::StaticGetButtonNames() Controller::ButtonList DigitalController::StaticGetButtonNames()
{ {
#define B(n) \ return {{TRANSLATABLE("DigitalController", "Up"), static_cast<s32>(Button::Up)},
{ \ {TRANSLATABLE("DigitalController", "Down"), static_cast<s32>(Button::Down)},
#n, static_cast < s32>(Button::n) \ {TRANSLATABLE("DigitalController", "Left"), static_cast<s32>(Button::Left)},
} {TRANSLATABLE("DigitalController", "Right"), static_cast<s32>(Button::Right)},
return {B(Up), B(Down), B(Left), B(Right), B(Select), B(Start), B(Triangle), {TRANSLATABLE("DigitalController", "Select"), static_cast<s32>(Button::Select)},
B(Cross), B(Circle), B(Square), B(L1), B(L2), B(R1), B(R2)}; {TRANSLATABLE("DigitalController", "Start"), static_cast<s32>(Button::Start)},
#undef B {TRANSLATABLE("DigitalController", "Triangle"), static_cast<s32>(Button::Triangle)},
{TRANSLATABLE("DigitalController", "Cross"), static_cast<s32>(Button::Cross)},
{TRANSLATABLE("DigitalController", "Circle"), static_cast<s32>(Button::Circle)},
{TRANSLATABLE("DigitalController", "Square"), static_cast<s32>(Button::Square)},
{TRANSLATABLE("DigitalController", "L1"), static_cast<s32>(Button::L1)},
{TRANSLATABLE("DigitalController", "L2"), static_cast<s32>(Button::L2)},
{TRANSLATABLE("DigitalController", "R1"), static_cast<s32>(Button::R1)},
{TRANSLATABLE("DigitalController", "R2"), static_cast<s32>(Button::R2)}};
} }
u32 DigitalController::StaticGetVibrationMotorCount() u32 DigitalController::StaticGetVibrationMotorCount()

View file

@ -10,7 +10,9 @@
#include "system.h" #include "system.h"
#include "timers.h" #include "timers.h"
#include <cmath> #include <cmath>
#include <imgui.h> #ifdef WITH_IMGUI
#include "imgui.h"
#endif
Log_SetChannel(GPU); Log_SetChannel(GPU);
std::unique_ptr<GPU> g_gpu; std::unique_ptr<GPU> g_gpu;
@ -1341,6 +1343,7 @@ bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride
void GPU::DrawDebugStateWindow() void GPU::DrawDebugStateWindow()
{ {
#ifdef WITH_IMGUI
const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x;
ImGui::SetNextWindowSize(ImVec2(450.0f * framebuffer_scale, 550.0f * framebuffer_scale), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(450.0f * framebuffer_scale, 550.0f * framebuffer_scale), ImGuiCond_FirstUseEver);
@ -1451,6 +1454,7 @@ void GPU::DrawDebugStateWindow()
} }
ImGui::End(); ImGui::End();
#endif
} }
void GPU::DrawRendererStats(bool is_idle_frame) {} void GPU::DrawRendererStats(bool is_idle_frame) {}

View file

@ -3,13 +3,15 @@
#include "common/log.h" #include "common/log.h"
#include "common/state_wrapper.h" #include "common/state_wrapper.h"
#include "cpu_core.h" #include "cpu_core.h"
#include "imgui.h"
#include "pgxp.h" #include "pgxp.h"
#include "settings.h" #include "settings.h"
#include "system.h" #include "system.h"
#include <cmath> #include <cmath>
#include <sstream> #include <sstream>
#include <tuple> #include <tuple>
#ifdef WITH_IMGUI
#include "imgui.h"
#endif
Log_SetChannel(GPU_HW); Log_SetChannel(GPU_HW);
template<typename T> template<typename T>
@ -1000,6 +1002,7 @@ void GPU_HW::DrawRendererStats(bool is_idle_frame)
m_renderer_stats = {}; m_renderer_stats = {};
} }
#ifdef WITH_IMGUI
if (ImGui::CollapsingHeader("Renderer Statistics", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Renderer Statistics", ImGuiTreeNodeFlags_DefaultOpen))
{ {
static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; 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); ImGui::Columns(1);
} }
#endif
} }

View file

@ -594,7 +594,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMod
WriteHeader(ss); WriteHeader(ss);
DefineMacro(ss, "TRANSPARENCY", transparency != GPU_HW::BatchRenderMode::TransparencyDisabled); DefineMacro(ss, "TRANSPARENCY", transparency != GPU_HW::BatchRenderMode::TransparencyDisabled);
DefineMacro(ss, "TRANSPARENCY_ONLY_OPAQUE", transparency == GPU_HW::BatchRenderMode::OnlyOpaque); 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, "TEXTURED", textured);
DefineMacro(ss, "PALETTE", DefineMacro(ss, "PALETTE",
actual_texture_mode == GPU::TextureMode::Palette4Bit || actual_texture_mode == GPU::TextureMode::Palette4Bit ||
@ -889,19 +889,24 @@ void BilinearSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits,
} }
else else
{ {
#if TRANSPARENCY_ONLY_TRANSPARENCY #if TRANSPARENCY_ONLY_TRANSPARENT
discard; discard;
#endif #endif
#if TRANSPARENCY_ONLY_OPAQUE #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); o_col0 = float4(color, oalpha);
#elif USE_DUAL_SOURCE #if USE_DUAL_SOURCE
o_col1 = float4(0.0, 0.0, 0.0, 1.0 - ialpha);
#endif
#else
#if USE_DUAL_SOURCE
o_col0 = float4(color, oalpha); o_col0 = float4(color, oalpha);
o_col1 = float4(0.0, 0.0, 0.0, 1.0 - ialpha); o_col1 = float4(0.0, 0.0, 0.0, 1.0 - ialpha);
#else #else
o_col0 = float4(color, 1.0 - ialpha); o_col0 = float4(color, 1.0 - ialpha);
#endif #endif
#endif
o_depth = oalpha * v_pos.z; o_depth = oalpha * v_pos.z;
} }

View file

@ -99,13 +99,6 @@ void GPU_HW_Vulkan::ResetGraphicsAPIState()
GPU_HW::ResetGraphicsAPIState(); GPU_HW::ResetGraphicsAPIState();
EndRenderPass(); 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() 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, !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_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT) || 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)) VRAM_HEIGHT))
{ {
return false; return false;
@ -683,7 +676,9 @@ bool GPU_HW_Vulkan::CompilePipelines()
gpbuilder.SetBlendAttachment( gpbuilder.SetBlendAttachment(
0, true, VK_BLEND_FACTOR_ONE, 0, true, VK_BLEND_FACTOR_ONE,
m_supports_dual_source_blend ? VK_BLEND_FACTOR_SRC1_ALPHA : VK_BLEND_FACTOR_SRC_ALPHA, m_supports_dual_source_blend ? VK_BLEND_FACTOR_SRC1_ALPHA : VK_BLEND_FACTOR_SRC_ALPHA,
(static_cast<TransparencyMode>(transparency_mode) == TransparencyMode::BackgroundMinusForeground) ? (static_cast<TransparencyMode>(transparency_mode) == TransparencyMode::BackgroundMinusForeground &&
static_cast<BatchRenderMode>(render_mode) != BatchRenderMode::TransparencyDisabled &&
static_cast<BatchRenderMode>(render_mode) != BatchRenderMode::OnlyOpaque) ?
VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_REVERSE_SUBTRACT :
VK_BLEND_OP_ADD, VK_BLEND_OP_ADD,
VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, 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() void GPU_HW_Vulkan::UpdateDisplay()
{ {
GPU_HW::UpdateDisplay(); GPU_HW::UpdateDisplay();
EndRenderPass();
if (g_settings.debugging.show_vram) 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_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_vram_texture.GetWidth(), m_vram_texture.GetHeight());
m_host_display->SetDisplayParameters(VRAM_WIDTH, VRAM_HEIGHT, 0, 0, VRAM_WIDTH, VRAM_HEIGHT, 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_x + scaled_display_width) <= m_vram_texture.GetWidth() &&
(scaled_vram_offset_y + scaled_display_height) <= m_vram_texture.GetHeight()) (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(), 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_vram_offset_x, scaled_vram_offset_y, scaled_display_width,
scaled_display_height); 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 // 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... // 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. // Encode the 24-bit texture as 16-bit.
const u32 uniforms[4] = {copy_rect.left, copy_rect.top, copy_rect.GetWidth(), copy_rect.GetHeight()}; const u32 uniforms[4] = {copy_rect.left, copy_rect.top, copy_rect.GetWidth(), copy_rect.GetHeight()};

View file

@ -20,7 +20,6 @@
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <cwchar> #include <cwchar>
#include <imgui.h>
#include <stdlib.h> #include <stdlib.h>
Log_SetChannel(HostInterface); Log_SetChannel(HostInterface);
@ -362,6 +361,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE)); si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE));
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false); si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false);
si.SetBoolValue("CPU", "ICache", false);
si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)); si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER));
si.SetIntValue("GPU", "ResolutionScale", 1); si.SetIntValue("GPU", "ResolutionScale", 1);
@ -452,7 +452,8 @@ void HostInterface::FixIncompatibleSettings(bool display_osd_messages)
{ {
if (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; 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.", AddFormattedOSDMessage(5.0f, "Switching to %s CPU execution mode.",
Settings::GetCPUExecutionModeName(g_settings.cpu_execution_mode)); Settings::GetCPUExecutionModeName(g_settings.cpu_execution_mode));
CPU::CodeCache::SetUseRecompiler(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler); CPU::CodeCache::SetUseRecompiler(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler);
CPU::CodeCache::Flush();
CPU::ClearICache();
} }
if (g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler && if (g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler &&
@ -520,6 +523,15 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
CPU::CodeCache::Flush(); 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); 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 || 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(); CPU::CodeCache::Flush();
} }
if (old_settings.gpu_pgxp_enable)
PGXP::Shutdown();
if (g_settings.gpu_pgxp_enable) if (g_settings.gpu_pgxp_enable)
PGXP::Initialize(); PGXP::Initialize();
} }

View file

@ -5,7 +5,9 @@
#include "dma.h" #include "dma.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "system.h" #include "system.h"
#include <imgui.h> #ifdef WITH_IMGUI
#include "imgui.h"
#endif
Log_SetChannel(MDEC); Log_SetChannel(MDEC);
MDEC g_mdec; MDEC g_mdec;
@ -701,6 +703,7 @@ void MDEC::HandleSetScaleCommand()
void MDEC::DrawDebugStateWindow() void MDEC::DrawDebugStateWindow()
{ {
#ifdef WITH_IMGUI
const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x;
ImGui::SetNextWindowSize(ImVec2(300.0f * framebuffer_scale, 350.0f * framebuffer_scale), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(300.0f * framebuffer_scale, 350.0f * framebuffer_scale), ImGuiCond_FirstUseEver);
@ -738,4 +741,5 @@ void MDEC::DrawDebugStateWindow()
} }
ImGui::End(); ImGui::End();
#endif
} }

View file

@ -209,12 +209,9 @@ Controller::AxisList NamcoGunCon::StaticGetAxisNames()
Controller::ButtonList NamcoGunCon::StaticGetButtonNames() Controller::ButtonList NamcoGunCon::StaticGetButtonNames()
{ {
#define B(n) \ return {{TRANSLATABLE("NamcoGunCon", "Trigger"), static_cast<s32>(Button::Trigger)},
{ \ {TRANSLATABLE("NamcoGunCon", "A"), static_cast<s32>(Button::A)},
#n, static_cast < s32>(Button::n) \ {TRANSLATABLE("NamcoGunCon", "B"), static_cast<s32>(Button::B)}};
}
return {B(Trigger), B(A), B(B)};
#undef B
} }
u32 NamcoGunCon::StaticGetVibrationMotorCount() u32 NamcoGunCon::StaticGetVibrationMotorCount()

View file

@ -219,12 +219,12 @@ std::optional<s32> NeGcon::StaticGetButtonCodeByName(std::string_view button_nam
Controller::AxisList NeGcon::StaticGetAxisNames() Controller::AxisList NeGcon::StaticGetAxisNames()
{ {
#define A(n) \ #define A(n, t) \
{ \ { \
#n, static_cast <s32>(Axis::n) \ #n, static_cast <s32>(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 #undef A
} }

View file

@ -20,8 +20,8 @@
#include "pgxp.h" #include "pgxp.h"
#include "settings.h" #include "settings.h"
#include <cmath>
#include <climits> #include <climits>
#include <cmath>
namespace PGXP { namespace PGXP {
// pgxp_types.h // pgxp_types.h
@ -134,6 +134,26 @@ static void WriteMem(PGXP_value* value, u32 addr);
static void WriteMem16(PGXP_value* src, u32 addr); static void WriteMem16(PGXP_value* src, u32 addr);
// pgxp_gpu.h // 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); void PGXP_CacheVertex(short sx, short sy, const PGXP_value* _pVertex);
// pgxp_gte.h // pgxp_gte.h
@ -142,8 +162,8 @@ static void PGXP_InitGTE();
// pgxp_cpu.h // pgxp_cpu.h
static void PGXP_InitCPU(); static void PGXP_InitCPU();
static PGXP_value CPU_reg_mem[34]; static PGXP_value CPU_reg_mem[34];
#define CPU_Hi CPU_reg[33] #define CPU_Hi CPU_reg[32]
#define CPU_Lo CPU_reg[34] #define CPU_Lo CPU_reg[33]
static PGXP_value CP0_reg_mem[32]; static PGXP_value CP0_reg_mem[32];
static PGXP_value* CPU_reg = CPU_reg_mem; static PGXP_value* CPU_reg = CPU_reg_mem;
@ -195,7 +215,6 @@ double f16Overflow(double in)
// pgxp_mem.c // pgxp_mem.c
static void PGXP_InitMem(); 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 UserMemOffset = 0;
static const u32 ScratchOffset = 2048 * 1024 / 4; static const u32 ScratchOffset = 2048 * 1024 / 4;
static const u32 RegisterOffset = 2 * 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() void PGXP_InitMem()
{ {
memset(Mem, 0, sizeof(Mem)); if (!Mem)
{
Mem = static_cast<PGXP_value*>(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) u32 PGXP_ConvertAddress(u32 addr)
@ -366,8 +397,6 @@ void WriteMem16(PGXP_value* src, u32 addr)
} }
// pgxp_main.c // pgxp_main.c
u32 static gMode = 0;
void Initialize() void Initialize()
{ {
PGXP_InitMem(); PGXP_InitMem();
@ -375,24 +404,19 @@ void Initialize()
PGXP_InitGTE(); PGXP_InitGTE();
} }
void PGXP_SetModes(u32 modes) void Shutdown()
{ {
gMode = modes; cacheMode = mode_init;
if (vertexCache)
{
std::free(vertexCache);
vertexCache = nullptr;
} }
if (Mem)
u32 PGXP_GetModes()
{ {
return gMode; std::free(Mem);
Mem = nullptr;
} }
void PGXP_EnableModes(u32 modes)
{
gMode |= modes;
}
void PGXP_DisableModes(u32 modes)
{
gMode = gMode & ~modes;
} }
// pgxp_gte.c // pgxp_gte.c
@ -584,17 +608,6 @@ void CPU_SWC2(u32 instr, u32 rtVal, u32 addr)
///////////////////////////////// /////////////////////////////////
//// Blade_Arma's Vertex Cache (CatBlade?) //// 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) unsigned int IsSessionID(unsigned int vertID)
{ {
// No wrapping // No wrapping
@ -612,6 +625,23 @@ unsigned int IsSessionID(unsigned int vertID)
return 0; return 0;
} }
static void InitPGXPVertexCache()
{
if (!vertexCache)
{
vertexCache = static_cast<PGXP_value*>(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) void PGXP_CacheVertex(short sx, short sy, const PGXP_value* _pVertex)
{ {
const PGXP_value* pNewVertex = (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; return;
} }
// Initialise cache on first use
if (!vertexCache)
InitPGXPVertexCache();
// if (bGteAccuracy) // if (bGteAccuracy)
{ {
if (cacheMode != mode_write) if (cacheMode != mode_write)
{ {
// Initialise cache on first use
if (cacheMode == mode_init)
memset(vertexCache, 0x00, sizeof(vertexCache));
// First vertex of write session (frame?) // First vertex of write session (frame?)
cacheMode = mode_write; cacheMode = mode_write;
baseID = pNewVertex->count; 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) 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 // To avoid ambiguity there can only be one valid entry per-session
if (0) //(IsSessionID(pOldVertex->count) && (pOldVertex->value == pNewVertex->value)) 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) PGXP_value* PGXP_GetCachedVertex(short sx, short sy)
{ {
// if (bGteAccuracy) if (g_settings.gpu_pgxp_vertex_cache)
{ {
if (cacheMode != mode_read) if (cacheMode != mode_read)
{ {
if (cacheMode == mode_fail) if (cacheMode == mode_fail)
return NULL; return NULL;
// Initialise cache on first use
if (cacheMode == mode_init)
memset(vertexCache, 0x00, sizeof(vertexCache));
// First vertex of read session (frame?) // First vertex of read session (frame?)
cacheMode = mode_read; cacheMode = mode_read;
} }
// Initialise cache on first use
if (!vertexCache)
InitPGXPVertexCache();
if (sx >= -0x800 && sx <= 0x7ff && sy >= -0x800 && sy <= 0x7ff) if (sx >= -0x800 && sx <= 0x7ff && sy >= -0x800 && sy <= 0x7ff)
{ {
// Return pointer to cache entry // Return pointer to cache entry
return &vertexCache[sy + 0x800][sx + 0x800]; return &vertexCache[(sy + 0x800) * VERTEX_CACHE_WIDTH + (sx + 0x800)];
} }
} }

View file

@ -24,6 +24,7 @@
namespace PGXP { namespace PGXP {
void Initialize(); void Initialize();
void Shutdown();
// -- GTE functions // -- GTE functions
// Transforms // Transforms

View file

@ -92,6 +92,7 @@ void Settings::Load(SettingsInterface& si)
si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str()) si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str())
.value_or(DEFAULT_CPU_EXECUTION_MODE); .value_or(DEFAULT_CPU_EXECUTION_MODE);
cpu_recompiler_memory_exceptions = si.GetBoolValue("CPU", "RecompilerMemoryExceptions", false); 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()) gpu_renderer = ParseRendererName(si.GetStringValue("GPU", "Renderer", GetRendererName(DEFAULT_GPU_RENDERER)).c_str())
.value_or(DEFAULT_GPU_RENDERER); .value_or(DEFAULT_GPU_RENDERER);
@ -206,6 +207,7 @@ void Settings::Save(SettingsInterface& si) const
si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode)); si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions); 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", "Renderer", GetRendererName(gpu_renderer));
si.SetStringValue("GPU", "Adapter", gpu_adapter.c_str()); si.SetStringValue("GPU", "Adapter", gpu_adapter.c_str());

View file

@ -69,6 +69,7 @@ struct Settings
CPUExecutionMode cpu_execution_mode = CPUExecutionMode::Interpreter; CPUExecutionMode cpu_execution_mode = CPUExecutionMode::Interpreter;
bool cpu_recompiler_memory_exceptions = false; bool cpu_recompiler_memory_exceptions = false;
bool cpu_recompiler_icache = false;
float emulation_speed = 1.0f; float emulation_speed = 1.0f;
bool speed_limiter_enabled = true; bool speed_limiter_enabled = true;

View file

@ -8,7 +8,9 @@
#include "host_interface.h" #include "host_interface.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "system.h" #include "system.h"
#include <imgui.h> #ifdef WITH_IMGUI
#include "imgui.h"
#endif
Log_SetChannel(SPU); Log_SetChannel(SPU);
SPU g_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() void SPU::DrawDebugStateWindow()
{ {
#ifdef WITH_IMGUI
static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; 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}; static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f};
const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x;
@ -1924,4 +1927,5 @@ void SPU::DrawDebugStateWindow()
} }
ImGui::End(); ImGui::End();
#endif
} }

View file

@ -4,6 +4,7 @@
#include "cdrom.h" #include "cdrom.h"
#include "common/audio_stream.h" #include "common/audio_stream.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/iso_reader.h"
#include "common/log.h" #include "common/log.h"
#include "common/state_wrapper.h" #include "common/state_wrapper.h"
#include "common/string_util.h" #include "common/string_util.h"
@ -11,7 +12,6 @@
#include "cpu_code_cache.h" #include "cpu_code_cache.h"
#include "cpu_core.h" #include "cpu_core.h"
#include "dma.h" #include "dma.h"
#include "game_list.h"
#include "gpu.h" #include "gpu.h"
#include "gte.h" #include "gte.h"
#include "host_display.h" #include "host_display.h"
@ -27,7 +27,8 @@
#include "spu.h" #include "spu.h"
#include "timers.h" #include "timers.h"
#include <cstdio> #include <cstdio>
#include <imgui.h> #include <cctype>
#include <fstream>
#include <limits> #include <limits>
Log_SetChannel(System); Log_SetChannel(System);
@ -204,6 +205,71 @@ float GetThrottleFrequency()
return s_throttle_frequency; 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<std::string> ParseM3UFile(const char* path)
{
std::ifstream ifs(path);
if (!ifs.is_open())
{
Log_ErrorPrintf("Failed to open %s", path);
return {};
}
std::vector<std::string> 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<u32>(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) ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region)
{ {
switch (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<CDImage> 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<u8> system_cnf_data;
if (!iso.ReadFile("SYSTEM.CNF", &system_cnf_data))
return {};
// Parse lines
std::vector<std::pair<std::string, std::string>> lines;
std::pair<std::string, std::string> current_line;
bool reading_value = false;
for (size_t pos = 0; pos < system_cnf_data.size(); pos++)
{
const char ch = static_cast<char>(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<char>(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<char>(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<DiscRegion> GetRegionForPath(const char* image_path)
{
std::unique_ptr<CDImage> cdi = CDImage::Open(image_path);
if (!cdi)
return {};
return GetRegionForImage(cdi.get());
}
bool RecreateGPU(GPURenderer renderer) bool RecreateGPU(GPURenderer renderer)
{ {
g_gpu->RestoreGraphicsAPIState(); g_gpu->RestoreGraphicsAPIState();
@ -298,8 +546,8 @@ bool Boot(const SystemBootParameters& params)
bool psf_boot = false; bool psf_boot = false;
if (!params.filename.empty()) if (!params.filename.empty())
{ {
exe_boot = GameList::IsExeFileName(params.filename.c_str()); exe_boot = IsExeFileName(params.filename.c_str());
psf_boot = (!exe_boot && GameList::IsPsfFileName(params.filename.c_str())); psf_boot = (!exe_boot && IsPsfFileName(params.filename.c_str()));
if (exe_boot || psf_boot) if (exe_boot || psf_boot)
{ {
// TODO: Pull region from PSF // TODO: Pull region from PSF
@ -312,9 +560,9 @@ bool Boot(const SystemBootParameters& params)
else else
{ {
u32 playlist_index; 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; s_media_playlist_filename = params.filename;
if (s_media_playlist.empty()) if (s_media_playlist.empty())
{ {
@ -351,7 +599,7 @@ bool Boot(const SystemBootParameters& params)
if (s_region == ConsoleRegion::Auto) 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) if (disc_region != DiscRegion::Other)
{ {
s_region = GetConsoleRegionForDiscRegion(disc_region); s_region = GetConsoleRegionForDiscRegion(disc_region);
@ -690,7 +938,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer)
return false; return false;
} }
playlist_entries = GameList::ParseM3UFile(playlist_filename.c_str()); playlist_entries = ParseM3UFile(playlist_filename.c_str());
if (playlist_entries.empty()) if (playlist_entries.empty())
{ {
g_host_interface->ReportFormattedError("Failed to load save state playlist entries from '%s'", 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) 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)); card = MemoryCard::Open(g_host_interface->GetGameMemoryCardPath(playlist_title.c_str(), i));
} }
else if (s_running_game_title.empty()) else if (s_running_game_title.empty())

View file

@ -45,9 +45,29 @@ enum class State
Paused 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<std::string> ParseM3UFile(const char* path);
/// Returns the preferred console type for a disc. /// Returns the preferred console type for a disc.
ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region); 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<DiscRegion> GetRegionForPath(const char* image_path);
std::string_view GetTitleForPath(const char* path);
State GetState(); State GetState();
void SetState(State new_state); void SetState(State new_state);
bool IsRunning(); bool IsRunning();

View file

@ -4,7 +4,9 @@
#include "gpu.h" #include "gpu.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "system.h" #include "system.h"
#include <imgui.h> #ifdef WITH_IMGUI
#include "imgui.h"
#endif
Log_SetChannel(Timers); Log_SetChannel(Timers);
Timers g_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<TickCount>(0xFFFF - cs.counter)); min_ticks_for_this_timer = std::min(min_ticks_for_this_timer, static_cast<TickCount>(0xFFFF - cs.counter));
if (cs.external_counting_enabled) // sysclk/8 for timer 2 if (cs.external_counting_enabled) // sysclk/8 for timer 2
min_ticks_for_this_timer = std::max<TickCount>(1, min_ticks_for_this_timer / 8); min_ticks_for_this_timer = std::max<TickCount>(1, min_ticks_for_this_timer * 8);
min_ticks = std::min(min_ticks, min_ticks_for_this_timer); min_ticks = std::min(min_ticks, min_ticks_for_this_timer);
} }
@ -369,6 +371,7 @@ void Timers::UpdateSysClkEvent()
void Timers::DrawDebugStateWindow() void Timers::DrawDebugStateWindow()
{ {
#ifdef WITH_IMGUI
static constexpr u32 NUM_COLUMNS = 10; static constexpr u32 NUM_COLUMNS = 10;
static constexpr std::array<const char*, NUM_COLUMNS> column_names = { static constexpr std::array<const char*, NUM_COLUMNS> column_names = {
{"#", "Value", "Target", "Sync", "Reset", "IRQ", "IRQRepeat", "IRQToggle", "Clock Source", "Reached"}}; {"#", "Value", "Target", "Sync", "Reset", "IRQ", "IRQRepeat", "IRQToggle", "Clock Source", "Reached"}};
@ -437,4 +440,5 @@ void Timers::DrawDebugStateWindow()
ImGui::Columns(1); ImGui::Columns(1);
ImGui::End(); ImGui::End();
#endif
} }

View file

@ -8,11 +8,12 @@ Log_SetChannel(TimingEvents);
namespace TimingEvents { namespace TimingEvents {
static std::vector<TimingEvent*> 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_global_tick_counter = 0;
static u32 s_last_event_run_time = 0; static u32 s_last_event_run_time = 0;
static bool s_running_events = false;
static bool s_events_need_sorting = false;
u32 GetGlobalTickCounter() u32 GetGlobalTickCounter()
{ {
@ -32,7 +33,7 @@ void Reset()
void Shutdown() void Shutdown()
{ {
Assert(s_events.empty()); Assert(s_active_event_count == 0);
} }
std::unique_ptr<TimingEvent> CreateTimingEvent(std::string name, TickCount period, TickCount interval, std::unique_ptr<TimingEvent> CreateTimingEvent(std::string name, TickCount period, TickCount interval,
@ -49,126 +50,249 @@ std::unique_ptr<TimingEvent> CreateTimingEvent(std::string name, TickCount perio
void UpdateCPUDowncount() void UpdateCPUDowncount()
{ {
if (!CPU::g_state.frame_done) 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) static void AddActiveEvent(TimingEvent* event)
{ {
s_events.push_back(event); DebugAssert(!event->prev && !event->next);
if (!s_running_events) 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(); UpdateCPUDowncount();
} }
else 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) static void RemoveActiveEvent(TimingEvent* event)
{ {
auto iter = std::find_if(s_events.begin(), s_events.end(), [event](const auto& it) { return event == it; }); DebugAssert(s_active_event_count > 0);
if (iter == s_events.end())
{
Panic("Attempt to remove inactive event");
return;
}
s_events.erase(iter); if (event->next)
if (!s_running_events)
{ {
std::make_heap(s_events.begin(), s_events.end(), CompareEvents); event->next->prev = event->prev;
if (!s_events.empty())
UpdateCPUDowncount();
} }
else else
{ {
s_events_need_sorting = true; s_active_events_tail = event->prev;
}
} }
static TimingEvent* FindActiveEvent(const char* name) if (event->prev)
{ {
auto iter = event->prev->next = event->next;
std::find_if(s_events.begin(), s_events.end(), [&name](auto& ev) { return ev->GetName().compare(name) == 0; }); }
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() static void SortEvents()
{ {
if (!s_running_events) std::vector<TimingEvent*> 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); TimingEvent* current = next;
UpdateCPUDowncount(); 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)
{ {
s_events_need_sorting = true; for (TimingEvent* event = s_active_events_head; event; event = event->next)
{
if (event->GetName().compare(name) == 0)
return event;
} }
return nullptr;
} }
void RunEvents() void RunEvents()
{ {
DebugAssert(!s_running_events && !s_events.empty()); DebugAssert(!s_current_event);
s_running_events = true;
TickCount pending_ticks = (s_global_tick_counter + CPU::GetPendingTicks()) - s_last_event_run_time; TickCount pending_ticks = (s_global_tick_counter + CPU::GetPendingTicks()) - s_last_event_run_time;
CPU::ResetPendingTicks(); CPU::ResetPendingTicks();
while (pending_ticks > 0) 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<u32>(time); s_global_tick_counter += static_cast<u32>(time);
pending_ticks -= time; pending_ticks -= time;
// Apply downcount to all events. // Apply downcount to all events.
// This will result in a negative downcount for those events which are late. // 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; event->m_downcount -= time;
evt->m_time_since_last_run += time; event->m_time_since_last_run += time;
} }
// Now we can actually run the callbacks. // 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(); // move it to the end, since that'll likely be its new position
std::pop_heap(s_events.begin(), s_events.end(), CompareEvents); TimingEvent* event = s_active_events_head;
s_current_event = event;
// Factor late time into the time for the next invocation. // Factor late time into the time for the next invocation.
const TickCount ticks_late = -evt->m_downcount; const TickCount ticks_late = -event->m_downcount;
const TickCount ticks_to_execute = evt->m_time_since_last_run; const TickCount ticks_to_execute = event->m_time_since_last_run;
evt->m_downcount += evt->m_interval; event->m_downcount += event->m_interval;
evt->m_time_since_last_run = 0; event->m_time_since_last_run = 0;
// The cycles_late is only an indicator, it doesn't modify the cycles to execute. // The cycles_late is only an indicator, it doesn't modify the cycles to execute.
evt->m_callback(ticks_to_execute, ticks_late); event->m_callback(ticks_to_execute, ticks_late);
if (event->m_active)
// Place it in the appropriate position in the queue. SortEvent(event);
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);
}
} }
} }
s_last_event_run_time = s_global_tick_counter; s_last_event_run_time = s_global_tick_counter;
s_running_events = false; s_current_event = nullptr;
UpdateCPUDowncount(); UpdateCPUDowncount();
} }
@ -216,21 +340,21 @@ bool DoState(StateWrapper& sw)
} }
else else
{ {
u32 event_count = static_cast<u32>(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(&event->m_name);
sw.Do(&evt->m_downcount); sw.Do(&event->m_downcount);
sw.Do(&evt->m_time_since_last_run); sw.Do(&event->m_time_since_last_run);
sw.Do(&evt->m_period); sw.Do(&event->m_period);
sw.Do(&evt->m_interval); sw.Do(&event->m_interval);
} }
sw.Do(&s_last_event_run_time); 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(); 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. // 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. // 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_downcount = m_interval;
m_time_since_last_run = 0; m_time_since_last_run = 0;
TimingEvents::SortEvents(); if (TimingEvents::s_current_event != this)
TimingEvents::SortEvent(this);
} }
void TimingEvent::InvokeEarly(bool force /* = false */) void TimingEvent::InvokeEarly(bool force /* = false */)
@ -318,7 +444,8 @@ void TimingEvent::InvokeEarly(bool force /* = false */)
m_callback(ticks_to_execute, 0); m_callback(ticks_to_execute, 0);
// Since we've changed the downcount, we need to re-sort the events. // 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() void TimingEvent::Activate()

View file

@ -56,6 +56,9 @@ public:
void SetInterval(TickCount interval) { m_interval = interval; } void SetInterval(TickCount interval) { m_interval = interval; }
void SetPeriod(TickCount period) { m_period = period; } void SetPeriod(TickCount period) { m_period = period; }
TimingEvent* prev = nullptr;
TimingEvent* next = nullptr;
TickCount m_downcount; TickCount m_downcount;
TickCount m_time_since_last_run; TickCount m_time_since_last_run;
TickCount m_period; TickCount m_period;

View file

@ -21,7 +21,7 @@ if(WIN32)
) )
endif() 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 # no lib prefix
set_target_properties(duckstation_libretro PROPERTIES PREFIX "") set_target_properties(duckstation_libretro PROPERTIES PREFIX "")

View file

@ -7,7 +7,6 @@
#include "core/analog_controller.h" #include "core/analog_controller.h"
#include "core/bus.h" #include "core/bus.h"
#include "core/digital_controller.h" #include "core/digital_controller.h"
#include "core/game_list.h"
#include "core/gpu.h" #include "core/gpu.h"
#include "core/system.h" #include "core/system.h"
#include "libretro_audio_stream.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) 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? // 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(); code->clear();
} }
@ -370,7 +369,7 @@ void LibretroHostInterface::OnSystemDestroyed()
m_using_hardware_renderer = false; m_using_hardware_renderer = false;
} }
static std::array<retro_core_option_definition, 31> s_option_definitions = {{ static std::array<retro_core_option_definition, 32> s_option_definitions = {{
{"duckstation_Console.Region", {"duckstation_Console.Region",
"Console Region", "Console Region",
"Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.", "Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.",
@ -406,6 +405,12 @@ static std::array<retro_core_option_definition, 31> s_option_definitions = {{
"Which mode to use for CPU emulation. Recompiler provides the best performance.", "Which mode to use for CPU emulation. Recompiler provides the best performance.",
{{"Interpreter", "Interpreter"}, {"CachedIntepreter", "Cached Interpreter"}, {"Recompiler", "Recompiler"}}, {{"Interpreter", "Interpreter"}, {"CachedIntepreter", "Cached Interpreter"}, {"Recompiler", "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", {"duckstation_GPU.Renderer",
"GPU Renderer", "GPU Renderer",
"Which renderer to use to emulate the GPU", "Which renderer to use to emulate the GPU",
@ -1152,7 +1157,7 @@ bool LibretroHostInterface::DiskControlGetImageLabel(unsigned index, char* label
if (image_path.empty()) if (image_path.empty())
return false; return false;
const std::string_view title = GameList::GetTitleForPath(label); const std::string_view title = System::GetTitleForPath(label);
StringUtil::Strlcpy(label, title, len); StringUtil::Strlcpy(label, title, len);
Log_DevPrintf("DiskControlGetImagePath(%u) -> %s", index, label); Log_DevPrintf("DiskControlGetImagePath(%u) -> %s", index, label);
return true; return true;

View file

@ -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" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf.win" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qt.conf"
) )
endif() 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 $<TARGET_FILE_DIR:duckstation-qt>/translations
COMMAND cp ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/translations/*.qm $<TARGET_FILE_DIR:duckstation-qt>/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/"
$<TARGET_FILE:duckstation-qt>)
endif()

Binary file not shown.

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>bin</string>
<string>cue</string>
<string>img</string>
<string>chd</string>
<string>m3u</string>
<string>psexe</string>
<string>psf</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>DuckStation.icns</string>
<key>CFBundleTypeName</key>
<string>PlayStation File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>DuckStation</string>
<key>CFBundleIconFile</key>
<string>DuckStation.icns</string>
<key>CFBundleIdentifier</key>
<string>com.github.stenzek.duckstation</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>NSHumanReadableCopyright</key>
<string>Licensed under GPL version 3</string>
<key>LSMinimumSystemVersion</key>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>CSResourcesFileMapped</key>
<true/>
</dict>
</plist>

View file

@ -27,6 +27,8 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface,
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuMaxRunAhead, "Hacks", "GPUMaxRunAhead"); SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuMaxRunAhead, "Hacks", "GPUMaxRunAhead");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU",
"RecompilerMemoryExceptions", false); "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.showDebugMenu, "Main", "ShowDebugMenu");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuUseDebugDevice, "GPU", "UseDebugDevice"); 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"), 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. " tr("Enables the usage of debug devices and shaders for rendering APIs which support them. "
"Should only be used when debugging the emulator.")); "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; AdvancedSettingsWidget::~AdvancedSettingsWidget() = default;

View file

@ -184,6 +184,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0">
<widget class="QCheckBox" name="cpuRecompilerMemoryExceptions">
<property name="text">
<string>Enable Recompiler Memory Exceptions</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="cpuRecompilerICache">
<property name="text">
<string>Enable Recompiler ICache</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2"> <item row="6" column="0" colspan="2">
<widget class="QPushButton" name="resetToDefaultButton"> <widget class="QPushButton" name="resetToDefaultButton">
<property name="text"> <property name="text">
@ -191,13 +205,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="cpuRecompilerMemoryExceptions">
<property name="text">
<string>Enable Recompiler Memory Exceptions</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View file

@ -174,6 +174,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin
QGridLayout* layout = new QGridLayout(ui->bindings_container); QGridLayout* layout = new QGridLayout(ui->bindings_container);
const auto buttons = Controller::GetButtonNames(ctype); const auto buttons = Controller::GetButtonNames(ctype);
const char* cname = Settings::GetControllerTypeName(ctype);
InputBindingWidget* first_button = nullptr; InputBindingWidget* first_button = nullptr;
InputBindingWidget* last_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 section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1);
std::string key_name = StringUtil::StdStringFromFormat("Button%s", button_name.c_str()); 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), InputButtonBindingWidget* button = new InputButtonBindingWidget(m_host_interface, std::move(section_name),
std::move(key_name), ui->bindings_container); std::move(key_name), ui->bindings_container);
layout->addWidget(label, start_row + current_row, current_column); 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<int>(axises.size()) + 1) / 2; const int num_rows = (static_cast<int>(axises.size()) + 1) / 2;
int current_row = 0; int current_row = 0;
int current_column = 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) 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 section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1);
std::string key_name = StringUtil::StdStringFromFormat("Axis%s", axis_name.c_str()); std::string key_name = StringUtil::StdStringFromFormat("Axis%s", axis_name.c_str());
QLabel* label = new QLabel(QString::fromStdString(axis_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), InputAxisBindingWidget* button = new InputAxisBindingWidget(
std::move(key_name), ui->bindings_container); 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(label, start_row + current_row, current_column);
layout->addWidget(button, start_row + current_row, current_column + 1); 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 section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1);
std::string key_name = si.key; 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) switch (si.type)
{ {
case SettingInfo::Type::Boolean: 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); cb->setToolTip(setting_tooltip);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, cb, std::move(section_name), SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, cb, std::move(section_name),
std::move(key_name), si.BooleanDefaultValue()); std::move(key_name), si.BooleanDefaultValue());
@ -306,7 +307,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin
sb->setSingleStep(si.IntegerStepValue()); sb->setSingleStep(si.IntegerStepValue());
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, sb, std::move(section_name), SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, sb, std::move(section_name),
std::move(key_name), si.IntegerDefaultValue()); 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); layout->addWidget(sb, start_row, 1, 1, 3);
start_row++; start_row++;
} }
@ -321,7 +322,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin
sb->setSingleStep(si.FloatStepValue()); sb->setSingleStep(si.FloatStepValue());
SettingWidgetBinder::BindWidgetToFloatSetting(m_host_interface, sb, std::move(section_name), SettingWidgetBinder::BindWidgetToFloatSetting(m_host_interface, sb, std::move(section_name),
std::move(key_name), si.FloatDefaultValue()); 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); layout->addWidget(sb, start_row, 1, 1, 3);
start_row++; start_row++;
} }
@ -333,7 +334,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin
le->setToolTip(setting_tooltip); le->setToolTip(setting_tooltip);
SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, le, std::move(section_name), SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, le, std::move(section_name),
std::move(key_name), si.StringDefaultValue()); 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); layout->addWidget(le, start_row, 1, 1, 3);
start_row++; start_row++;
} }
@ -356,7 +357,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin
hbox->addWidget(le, 1); hbox->addWidget(le, 1);
hbox->addWidget(browse_button); 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); layout->addLayout(hbox, start_row, 1, 1, 3);
start_row++; start_row++;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 111 KiB

View file

@ -1,5 +1,6 @@
#include "gamelistmodel.h" #include "gamelistmodel.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/system.h"
static constexpr std::array<const char*, GameListModel::Column_Count> s_column_names = { static constexpr std::array<const char*, GameListModel::Column_Count> s_column_names = {
{"Type", "Code", "Title", "File Title", "Size", "Region", "Compatibility"}}; {"Type", "Code", "Title", "File Title", "Size", "Region", "Compatibility"}};
@ -69,7 +70,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
case Column_FileTitle: 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<int>(file_title.length())); return QString::fromUtf8(file_title.data(), static_cast<int>(file_title.length()));
} }
@ -96,7 +97,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
case Column_FileTitle: 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<int>(file_title.length())); return QString::fromUtf8(file_title.data(), static_cast<int>(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& left = m_game_list->GetEntries().at(left_row);
const GameListEntry& right = m_game_list->GetEntries().at(right_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, 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) if (left.code == right.code)
return titlesLessThan(left_row, right_row, ascending); 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: case Column_Title:
{ {
if (left.title == right.title)
return titlesLessThan(left_row, right_row, ascending); return titlesLessThan(left_row, right_row, ascending);
return ascending ? (left.title < right.title) : (right.title > left.title);
} }
case Column_FileTitle: case Column_FileTitle:
{ {
const std::string_view file_title_left(GameList::GetTitleForPath(left.path.c_str())); const std::string_view file_title_left(System::GetTitleForPath(left.path.c_str()));
const std::string_view file_title_right(GameList::GetTitleForPath(right.path.c_str())); const std::string_view file_title_right(System::GetTitleForPath(right.path.c_str()));
if (file_title_left == file_title_right) if (file_title_left == file_title_right)
return titlesLessThan(left_row, right_row, ascending); 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: case Column_Region:

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "core/game_list.h"
#include "core/types.h" #include "core/types.h"
#include "frontend-common/game_list.h"
#include <QtCore/QAbstractTableModel> #include <QtCore/QAbstractTableModel>
#include <QtGui/QPixmap> #include <QtGui/QPixmap>
#include <array> #include <array>

View file

@ -2,7 +2,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/minizip_helpers.h" #include "common/minizip_helpers.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/game_list.h" #include "frontend-common/game_list.h"
#include "gamelistsearchdirectoriesmodel.h" #include "gamelistsearchdirectoriesmodel.h"
#include "qthostinterface.h" #include "qthostinterface.h"
#include "qtutils.h" #include "qtutils.h"

View file

@ -1,7 +1,7 @@
#include "gamelistwidget.h" #include "gamelistwidget.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/game_list.h"
#include "core/settings.h" #include "core/settings.h"
#include "frontend-common/game_list.h"
#include "gamelistmodel.h" #include "gamelistmodel.h"
#include "qthostinterface.h" #include "qthostinterface.h"
#include "qtutils.h" #include "qtutils.h"

View file

@ -1,17 +1,21 @@
#include "gamepropertiesdialog.h" #include "gamepropertiesdialog.h"
#include "common/cd_image.h" #include "common/cd_image.h"
#include "common/cd_image_hasher.h" #include "common/cd_image_hasher.h"
#include "core/game_list.h"
#include "core/settings.h" #include "core/settings.h"
#include "frontend-common/game_list.h"
#include "qthostinterface.h" #include "qthostinterface.h"
#include "qtprogresscallback.h" #include "qtprogresscallback.h"
#include "qtutils.h" #include "qtutils.h"
#include "scmversion/scmversion.h" #include "scmversion/scmversion.h"
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QInputDialog> #include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
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 */) GamePropertiesDialog::GamePropertiesDialog(QtHostInterface* host_interface, QWidget* parent /* = nullptr */)
: QDialog(parent), m_host_interface(host_interface) : QDialog(parent), m_host_interface(host_interface)
{ {
@ -127,13 +131,15 @@ void GamePropertiesDialog::setupAdditionalUi()
qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i)))); qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
} }
m_ui.userResolutionScale->addItem(tr("(unchanged)"));
QtUtils::FillComboBoxWithResolutionScales(m_ui.userResolutionScale);
m_ui.userControllerType1->addItem(tr("(unchanged)")); m_ui.userControllerType1->addItem(tr("(unchanged)"));
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++) for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
{ {
m_ui.userControllerType1->addItem( m_ui.userControllerType1->addItem(
qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i)))); qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
} }
m_ui.userControllerType2->addItem(tr("(unchanged)")); m_ui.userControllerType2->addItem(tr("(unchanged)"));
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++) for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
{ {
@ -141,6 +147,19 @@ void GamePropertiesDialog::setupAdditionalUi()
qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i)))); qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
} }
m_ui.userMemoryCard1Type->addItem(tr("(unchanged)"));
for (u32 i = 0; i < static_cast<u32>(MemoryCardType::Count); i++)
{
m_ui.userMemoryCard1Type->addItem(
qApp->translate("MemoryCardType", Settings::GetMemoryCardTypeDisplayName(static_cast<MemoryCardType>(i))));
}
m_ui.userMemoryCard2Type->addItem(tr("(unchanged)"));
for (u32 i = 0; i < static_cast<u32>(MemoryCardType::Count); i++)
{
m_ui.userMemoryCard2Type->addItem(
qApp->translate("MemoryCardType", Settings::GetMemoryCardTypeDisplayName(static_cast<MemoryCardType>(i))));
}
QGridLayout* traits_layout = new QGridLayout(m_ui.compatibilityTraits); QGridLayout* traits_layout = new QGridLayout(m_ui.compatibilityTraits);
for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++) for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++)
{ {
@ -153,9 +172,9 @@ void GamePropertiesDialog::setupAdditionalUi()
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 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->populate(ge);
gpd->show(); gpd->show();
gpd->onResize(); gpd->onResize();
@ -198,6 +217,26 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path)
} }
} }
void GamePropertiesDialog::populateBooleanUserSetting(QCheckBox* cb, const std::optional<bool>& 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<bool>* value)
{
connect(cb, &QCheckBox::stateChanged, [this, value](int state) {
if (state == Qt::PartiallyChecked)
value->reset();
else
*value = (state == Qt::Checked);
saveGameSettings();
});
}
void GamePropertiesDialog::populateGameSettings() void GamePropertiesDialog::populateGameSettings()
{ {
const GameSettings::Entry& gs = m_game_settings; const GameSettings::Entry& gs = m_game_settings;
@ -230,6 +269,27 @@ void GamePropertiesDialog::populateGameSettings()
m_ui.userAspectRatio->setCurrentIndex(static_cast<int>(gs.display_aspect_ratio.value()) + 1); m_ui.userAspectRatio->setCurrentIndex(static_cast<int>(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<int>(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()) if (gs.controller_1_type.has_value())
{ {
QSignalBlocker sb(m_ui.userControllerType1); QSignalBlocker sb(m_ui.userControllerType1);
@ -240,15 +300,26 @@ void GamePropertiesDialog::populateGameSettings()
QSignalBlocker sb(m_ui.userControllerType2); QSignalBlocker sb(m_ui.userControllerType2);
m_ui.userControllerType2->setCurrentIndex(static_cast<int>(gs.controller_2_type.value()) + 1); m_ui.userControllerType2->setCurrentIndex(static_cast<int>(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); QSignalBlocker sb(m_ui.userMemoryCard1Type);
m_ui.userWidescreenHack->setCheckState(Qt::Checked); m_ui.userMemoryCard1Type->setCurrentIndex(static_cast<int>(gs.memory_card_1_type.value()) + 1);
} }
else if (gs.memory_card_2_type.has_value())
{ {
QSignalBlocker sb(m_ui.userControllerType2); QSignalBlocker sb(m_ui.userMemoryCard2Type);
m_ui.userWidescreenHack->setCheckState(Qt::PartiallyChecked); m_ui.userMemoryCard2Type->setCurrentIndex(static_cast<int>(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(); 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<int>::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<u32>(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<int>::of(&QComboBox::currentIndexChanged), [this](int index) { connect(m_ui.userControllerType1, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
if (index <= 0) if (index <= 0)
m_game_settings.controller_1_type.reset(); m_game_settings.controller_1_type.reset();
@ -322,13 +411,50 @@ void GamePropertiesDialog::connectUi()
saveGameSettings(); saveGameSettings();
}); });
connect(m_ui.userWidescreenHack, &QCheckBox::stateChanged, [this](int state) { connect(m_ui.userMemoryCard1Type, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
if (state == Qt::PartiallyChecked) if (index <= 0)
m_game_settings.gpu_widescreen_hack.reset(); m_game_settings.memory_card_1_type.reset();
else else
m_game_settings.gpu_widescreen_hack = (state == Qt::Checked); m_game_settings.memory_card_1_type = static_cast<MemoryCardType>(index - 1);
saveGameSettings(); 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<int>::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<MemoryCardType>(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<u32>(GameSettings::Trait::Count); i++) for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++)
{ {

View file

@ -1,5 +1,5 @@
#pragma once #pragma once
#include "core/game_settings.h" #include "frontend-common/game_settings.h"
#include "ui_gamepropertiesdialog.h" #include "ui_gamepropertiesdialog.h"
#include <QtWidgets/QDialog> #include <QtWidgets/QDialog>
#include <array> #include <array>
@ -17,7 +17,7 @@ public:
GamePropertiesDialog(QtHostInterface* host_interface, QWidget* parent = nullptr); GamePropertiesDialog(QtHostInterface* host_interface, QWidget* parent = nullptr);
~GamePropertiesDialog(); ~GamePropertiesDialog();
static void showForEntry(QtHostInterface* host_interface, const GameListEntry* ge); static void showForEntry(QtHostInterface* host_interface, const GameListEntry* ge, QWidget* parent);
public Q_SLOTS: public Q_SLOTS:
void clear(); void clear();
@ -43,6 +43,8 @@ private:
void populateCompatibilityInfo(const std::string& game_code); void populateCompatibilityInfo(const std::string& game_code);
void populateTracksInfo(const std::string& image_path); void populateTracksInfo(const std::string& image_path);
void populateGameSettings(); void populateGameSettings();
void populateBooleanUserSetting(QCheckBox* cb, const std::optional<bool>& value);
void connectBooleanUserSetting(QCheckBox* cb, std::optional<bool>* value);
void saveGameSettings(); void saveGameSettings();
void fillEntryFromUi(GameListCompatibilityEntry* entry); void fillEntryFromUi(GameListCompatibilityEntry* entry);
void computeTrackHashes(); void computeTrackHashes();

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>793</width> <width>793</width>
<height>647</height> <height>600</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -21,7 +21,7 @@
<item> <item>
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>1</number>
</property> </property>
<widget class="QWidget" name="tab"> <widget class="QWidget" name="tab">
<attribute name="title"> <attribute name="title">
@ -190,30 +190,91 @@
<item> <item>
<widget class="QGroupBox" name="groupBox_4"> <widget class="QGroupBox" name="groupBox_4">
<property name="title"> <property name="title">
<string>GPU Settings</string> <string>GPU Screen Display</string>
</property> </property>
<layout class="QFormLayout" name="formLayout_5"> <layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Crop Mode:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="userCropMode"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_18"> <widget class="QLabel" name="label_18">
<property name="text"> <property name="text">
<string>Aspect Ratio:</string> <string>Aspect Ratio:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="userAspectRatio"/> <widget class="QComboBox" name="userAspectRatio"/>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Crop Mode:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="userCropMode"/>
</item>
<item row="2" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="userLinearUpscaling">
<property name="text">
<string>Linear Upscaling</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="userIntegerUpscaling">
<property name="text">
<string>Integer Upscaling</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>GPU Enhancements</string>
</property>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Resolution Scale:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="userResolutionScale"/>
</item>
<item row="1" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="userTrueColor">
<property name="text">
<string>True Color Rendering (24-bit, disables dithering)</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="userScaledDithering">
<property name="text">
<string>Scaled Dithering (scale dither pattern to resolution)</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="userWidescreenHack"> <widget class="QCheckBox" name="userWidescreenHack">
<property name="text"> <property name="text">
<string>Widescreen Hack</string> <string>Widescreen Hack</string>
@ -223,6 +284,38 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QCheckBox" name="userForceNTSCTimings">
<property name="text">
<string>Force NTSC Timings (60hz-on-PAL)</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="userBilinearTextureFiltering">
<property name="text">
<string>Bilinear Texture Filtering</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="userPGXP">
<property name="text">
<string>PGXP Geometry Correction</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -256,7 +349,78 @@
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="verticalSpacer_3"> <widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Memory Card Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Memory Card 1 Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="userMemoryCard1Type"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Memory Card 1 Shared Path:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="userMemoryCard1SharedPath"/>
</item>
<item>
<widget class="QPushButton" name="userMemoryCard1SharedPathBrowse">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Memory Card 2 Type:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="userMemoryCard2Type"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Memory Card 2 Shared Path:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="userMemoryCard2SharedPath"/>
</item>
<item>
<widget class="QPushButton" name="userMemoryCard2SharedPathBrowse">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>

View file

@ -1,6 +1,7 @@
#include "gpusettingswidget.h" #include "gpusettingswidget.h"
#include "core/gpu.h" #include "core/gpu.h"
#include "core/settings.h" #include "core/settings.h"
#include "qtutils.h"
#include "settingsdialog.h" #include "settingsdialog.h"
#include "settingwidgetbinder.h" #include "settingwidgetbinder.h"
@ -62,14 +63,15 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.renderer, tr("Renderer"), Settings::GetRendererDisplayName(Settings::DEFAULT_GPU_RENDERER), m_ui.renderer, tr("Renderer"), Settings::GetRendererDisplayName(Settings::DEFAULT_GPU_RENDERER),
tr( tr("Chooses the backend to use for rendering the console/game visuals. <br>Depending on your system and hardware, "
"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 "
"Direct3D 11 and OpenGL hardware backends may be available. <br>The software renderer offers the best compatibility, " "compatibility, "
"but is the slowest and does not offer any enhancements.")); "but is the slowest and does not offer any enhancements."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.adapter, tr("Adapter"), tr("(Default)"), 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 " tr("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.")); "renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default "
"device."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.displayAspectRatio, tr("Aspect Ratio"), QStringLiteral("4:3"), 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 " tr("Changes the aspect ratio used to display the console's output to the screen. The default "
@ -82,13 +84,15 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
"compromise between stability and hiding black borders.")); "compromise between stability and hiding black borders."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.disableInterlacing, tr("Disable Interlacing (force progressive render/scan)"), tr("Unchecked"), m_ui.disableInterlacing, tr("Disable Interlacing (force progressive render/scan)"), tr("Unchecked"),
tr("Forces the rendering and display of frames to progressive mode. <br>This removes the \"combing\" effect seen in " tr(
"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> " "480i games by rendering them in 480p. Usually safe to enable.<br> "
"<b><u>May not be compatible with all games.</u></b>")); "<b><u>May not be compatible with all games.</u></b>"));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(m_ui.displayLinearFiltering, tr("Linear Upscaling"), tr("Checked"),
m_ui.displayLinearFiltering, tr("Linear Upscaling"), tr("Checked"), tr("Uses bilinear texture filtering when displaying the console's framebuffer to the "
tr("Uses bilinear texture filtering when displaying the console's framebuffer to the screen. <br>Disabling filtering " "screen. <br>Disabling filtering "
"will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. <br>The option will be less " "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.")); "noticable the higher the resolution scale."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.displayIntegerScaling, tr("Integer Upscaling"), tr("Unchecked"), m_ui.displayIntegerScaling, tr("Integer Upscaling"), tr("Unchecked"),
@ -114,11 +118,11 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
m_ui.scaledDithering, tr("Scaled Dithering (scale dither pattern to resolution)"), tr("Checked"), 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 " tr("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.")); "obvious at higher resolutions. <br>Usually safe to enable, and only supported by the hardware renderers."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(m_ui.forceNTSCTimings, tr("Force NTSC Timings (60hz-on-PAL)"), tr("Unchecked"),
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 "
tr( "60hz. <br>For most games which "
"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 "
"have a speed tied to the framerate, this will result in the game running approximately 17% faster. <br>For variable " "approximately 17% faster. <br>For variable "
"frame rate games, it may not affect the speed.")); "frame rate games, it may not affect the speed."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.linearTextureFiltering, tr("Bilinear Texture Filtering"), tr("Unchecked"), m_ui.linearTextureFiltering, tr("Bilinear Texture Filtering"), tr("Unchecked"),
@ -128,7 +132,8 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"), m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"),
tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially " 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. <br>For 2D games, or games which " "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>")); "use pre-rendered backgrounds, this enhancement will not work as expected. <br><b><u>May not be compatible with "
"all games.</u></b>"));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"), m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"),
tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games. <br>Only " tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games. <br>Only "
@ -178,29 +183,7 @@ void GPUSettingsWidget::setupAdditionalUi()
qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i)))); qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
} }
std::array<QString, GPU::MAX_RESOLUTION_SCALE + 1> resolution_suffixes = {{ QtUtils::FillComboBoxWithResolutionScales(m_ui.resolutionScale);
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]));
} }
void GPUSettingsWidget::populateGPUAdapters() void GPUSettingsWidget::populateGPUAdapters()

View file

@ -36,8 +36,32 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
{ {
const QEvent::Type event_type = event->type(); const QEvent::Type event_type = event->type();
if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || // if the key is being released, set the input
event_type == QEvent::MouseButtonDblClick) 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<QKeyEvent*>(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<u32>(static_cast<const QMouseEvent*>(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; return true;
} }
@ -103,6 +127,27 @@ void InputBindingDialog::addNewBinding(std::string new_binding)
saveListToSettings(); saveListToSettings();
} }
void InputBindingDialog::bindToControllerAxis(int controller_index, int axis_index, std::optional<bool> 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() void InputBindingDialog::onAddBindingButtonClicked()
{ {
if (isListeningForInput()) if (isListeningForInput())
@ -159,38 +204,6 @@ InputButtonBindingDialog::~InputButtonBindingDialog()
InputButtonBindingDialog::stopListeningForInput(); 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<QKeyEvent*>(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<u32>(static_cast<const QMouseEvent*>(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() void InputButtonBindingDialog::hookControllerInput()
{ {
ControllerInterface* controller_interface = m_host_interface->getControllerInterface(); ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
@ -206,7 +219,7 @@ void InputButtonBindingDialog::hookControllerInput()
// TODO: this probably should consider the "last value" // TODO: this probably should consider the "last value"
QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), 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<bool>, ei.value > 0));
return ControllerInterface::Hook::CallbackResult::StopMonitoring; return ControllerInterface::Hook::CallbackResult::StopMonitoring;
} }
else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f) else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f)
@ -229,21 +242,6 @@ void InputButtonBindingDialog::unhookControllerInput()
controller_interface->ClearHook(); 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) void InputButtonBindingDialog::startListeningForInput(u32 timeout_in_seconds)
{ {
InputBindingDialog::startListeningForInput(timeout_in_seconds); InputBindingDialog::startListeningForInput(timeout_in_seconds);
@ -257,8 +255,10 @@ void InputButtonBindingDialog::stopListeningForInput()
} }
InputAxisBindingDialog::InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, InputAxisBindingDialog::InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name,
std::string key_name, std::vector<std::string> bindings, QWidget* parent) std::string key_name, std::vector<std::string> bindings,
: InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent) 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; return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional<bool>, 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)); Q_ARG(int, ei.button_or_axis_number));
return ControllerInterface::Hook::CallbackResult::StopMonitoring; return ControllerInterface::Hook::CallbackResult::StopMonitoring;
} }
@ -299,11 +306,19 @@ void InputAxisBindingDialog::unhookControllerInput()
controller_interface->ClearHook(); 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); if (m_axis_type != Controller::AxisType::Half)
addNewBinding(std::move(binding)); {
stopListeningForInput(); 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) void InputAxisBindingDialog::startListeningForInput(u32 timeout_in_seconds)

View file

@ -1,7 +1,9 @@
#pragma once #pragma once
#include "common/types.h" #include "common/types.h"
#include "core/controller.h"
#include "ui_inputbindingdialog.h" #include "ui_inputbindingdialog.h"
#include <QtWidgets/QDialog> #include <QtWidgets/QDialog>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -17,6 +19,8 @@ public:
~InputBindingDialog(); ~InputBindingDialog();
protected Q_SLOTS: protected Q_SLOTS:
void bindToControllerAxis(int controller_index, int axis_index, std::optional<bool> positive);
void bindToControllerButton(int controller_index, int button_index);
void onAddBindingButtonClicked(); void onAddBindingButtonClicked();
void onRemoveBindingButtonClicked(); void onRemoveBindingButtonClicked();
void onClearBindingsButtonClicked(); void onClearBindingsButtonClicked();
@ -52,7 +56,7 @@ protected:
u32 m_input_listen_remaining_seconds = 0; u32 m_input_listen_remaining_seconds = 0;
}; };
class InputButtonBindingDialog : public InputBindingDialog class InputButtonBindingDialog final : public InputBindingDialog
{ {
Q_OBJECT Q_OBJECT
@ -61,13 +65,6 @@ public:
std::vector<std::string> bindings, QWidget* parent); std::vector<std::string> bindings, QWidget* parent);
~InputButtonBindingDialog(); ~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: protected:
void startListeningForInput(u32 timeout_in_seconds) override; void startListeningForInput(u32 timeout_in_seconds) override;
void stopListeningForInput() override; void stopListeningForInput() override;
@ -75,21 +72,22 @@ protected:
void unhookControllerInput(); void unhookControllerInput();
}; };
class InputAxisBindingDialog : public InputBindingDialog class InputAxisBindingDialog final : public InputBindingDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name, InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
std::vector<std::string> bindings, QWidget* parent); std::vector<std::string> bindings, Controller::AxisType axis_type, QWidget* parent);
~InputAxisBindingDialog(); ~InputAxisBindingDialog();
private Q_SLOTS:
void bindToControllerAxis(int controller_index, int axis_index);
protected: protected:
bool eventFilter(QObject* watched, QEvent* event) override;
void startListeningForInput(u32 timeout_in_seconds) override; void startListeningForInput(u32 timeout_in_seconds) override;
void stopListeningForInput() override; void stopListeningForInput() override;
void hookControllerInput(); void hookControllerInput();
void unhookControllerInput(); void unhookControllerInput();
private:
Controller::AxisType m_axis_type;
}; };

View file

@ -40,6 +40,27 @@ void InputBindingWidget::updateText()
setText(QString::fromStdString(m_bindings[0])); setText(QString::fromStdString(m_bindings[0]));
} }
void InputBindingWidget::bindToControllerAxis(int controller_index, int axis_index, std::optional<bool> 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() void InputBindingWidget::beginRebindAll()
{ {
m_is_binding_all = true; m_is_binding_all = true;
@ -53,8 +74,32 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
{ {
const QEvent::Type event_type = event->type(); const QEvent::Type event_type = event->type();
if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || // if the key is being released, set the input
event_type == QEvent::MouseButtonDblClick) if (event_type == QEvent::KeyRelease)
{
setNewBinding();
stopListeningForInput();
return true;
}
else if (event_type == QEvent::KeyPress)
{
QString binding = QtUtils::KeyEventToString(static_cast<QKeyEvent*>(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<u32>(static_cast<const QMouseEvent*>(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; return true;
} }
@ -185,38 +230,6 @@ InputButtonBindingWidget::~InputButtonBindingWidget()
InputButtonBindingWidget::stopListeningForInput(); 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<QKeyEvent*>(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<u32>(static_cast<const QMouseEvent*>(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() void InputButtonBindingWidget::hookControllerInput()
{ {
ControllerInterface* controller_interface = m_host_interface->getControllerInterface(); ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
@ -232,7 +245,7 @@ void InputButtonBindingWidget::hookControllerInput()
// TODO: this probably should consider the "last value" // TODO: this probably should consider the "last value"
QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), 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<bool>, ei.value > 0));
return ControllerInterface::Hook::CallbackResult::StopMonitoring; return ControllerInterface::Hook::CallbackResult::StopMonitoring;
} }
else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f) else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f)
@ -255,21 +268,6 @@ void InputButtonBindingWidget::unhookControllerInput()
controller_interface->ClearHook(); 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) void InputButtonBindingWidget::startListeningForInput(u32 timeout_in_seconds)
{ {
InputBindingWidget::startListeningForInput(timeout_in_seconds); InputBindingWidget::startListeningForInput(timeout_in_seconds);
@ -291,8 +289,8 @@ void InputButtonBindingWidget::openDialog()
} }
InputAxisBindingWidget::InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, InputAxisBindingWidget::InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name,
std::string key_name, QWidget* parent) std::string key_name, Controller::AxisType axis_type, QWidget* parent)
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), 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; return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional<bool>, 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)); Q_ARG(int, ei.button_or_axis_number));
return ControllerInterface::Hook::CallbackResult::StopMonitoring; return ControllerInterface::Hook::CallbackResult::StopMonitoring;
} }
@ -333,11 +338,19 @@ void InputAxisBindingWidget::unhookControllerInput()
controller_interface->ClearHook(); 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); if (m_axis_type != Controller::AxisType::Half)
setNewBinding(); {
stopListeningForInput(); 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) void InputAxisBindingWidget::startListeningForInput(u32 timeout_in_seconds)
@ -354,7 +367,7 @@ void InputAxisBindingWidget::stopListeningForInput()
void InputAxisBindingWidget::openDialog() 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)); QtUtils::GetRootWidget(this));
binding_dialog.exec(); binding_dialog.exec();
reloadBinding(); reloadBinding();

Some files were not shown because too many files have changed in this diff Show more