Android: Multiple fixes

- Fix possible crash when applying settings worker thread (no JNIEnv).
 - Fix settings not applying until restarting the app.
 - Support analog controller - auto-binding of axixes. Currently no
   touchscreen controller for the joysticks.
 - Add option to auto-hide the touchscreen controller.
This commit is contained in:
Connor McLaughlin
2020-07-29 02:24:25 +10:00
parent c7b457de9e
commit 24ffe6f67e
10 changed files with 183 additions and 75 deletions

View File

@ -3,7 +3,6 @@ set(SRCS
android_host_interface.h
android_settings_interface.cpp
android_settings_interface.h
main.cpp
)
add_library(duckstation-native SHARED ${SRCS})

View File

@ -11,6 +11,7 @@
#include "core/system.h"
#include "frontend-common/opengl_host_display.h"
#include "frontend-common/vulkan_host_display.h"
#include "frontend-common/imgui_styles.h"
#include <android/native_window_jni.h>
#include <cmath>
#include <imgui.h>
@ -195,8 +196,18 @@ void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function,
void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params)
{
JNIEnv* thread_env;
if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK)
{
Log_ErrorPrintf("Failed to attach JNI to thread");
m_emulation_thread_start_result.store(false);
m_emulation_thread_started.Signal();
return;
}
CreateImGuiContext();
m_surface = initial_surface;
ApplySettings();
// Boot system.
if (!BootSystem(boot_params))
@ -205,6 +216,7 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf
DestroyImGuiContext();
m_emulation_thread_start_result.store(false);
m_emulation_thread_started.Signal();
s_jvm->DetachCurrentThread();
return;
}
@ -256,6 +268,7 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf
DestroySystem();
DestroyImGuiContext();
s_jvm->DetachCurrentThread();
}
bool AndroidHostInterface::AcquireHostDisplay()
@ -297,31 +310,6 @@ void AndroidHostInterface::ReleaseHostDisplay()
m_display.reset();
}
std::unique_ptr<AudioStream> AndroidHostInterface::CreateAudioStream(AudioBackend backend)
{
std::unique_ptr<AudioStream> stream;
switch (m_settings.audio_backend)
{
case AudioBackend::Cubeb:
stream = AudioStream::CreateCubebAudioStream();
break;
default:
stream = AudioStream::CreateNullAudioStream();
break;
}
if (!stream)
{
ReportFormattedError("Failed to create %s audio stream, falling back to null",
Settings::GetAudioBackendName(m_settings.audio_backend));
stream = AudioStream::CreateNullAudioStream();
}
return stream;
}
void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, int width, int height)
{
Log_InfoPrintf("SurfaceChanged %p %d %d %d", surface, format, width, height);
@ -351,9 +339,16 @@ void AndroidHostInterface::CreateImGuiContext()
{
ImGui::CreateContext();
ImGui::GetIO().IniFilename = nullptr;
// ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
// ImGui::GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad;
const float framebuffer_scale = 2.0f;
auto& io = ImGui::GetIO();
io.IniFilename = nullptr;
io.DisplayFramebufferScale.x = framebuffer_scale;
io.DisplayFramebufferScale.y = framebuffer_scale;
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
ImGui::StyleColorsDarker();
ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale);
}
void AndroidHostInterface::DestroyImGuiContext()
@ -397,6 +392,22 @@ void AndroidHostInterface::SetControllerButtonState(u32 index, s32 button_code,
false);
}
void AndroidHostInterface::SetControllerAxisState(u32 index, s32 button_code, float value)
{
if (!IsEmulationThreadRunning())
return;
RunOnEmulationThread(
[this, index, button_code, value]() {
Controller* controller = m_system->GetController(index);
if (!controller)
return;
controller->SetAxisState(button_code, value);
},
false);
}
void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidate_database)
{
m_game_list->SetSearchDirectoriesFromSettings(m_settings_interface);
@ -544,6 +555,25 @@ DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobje
return code.value_or(-1);
}
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerAxisState, jobject obj, jint index, jint button_code,
jfloat value)
{
AndroidHelpers::GetNativeClass(env, obj)->SetControllerAxisState(index, button_code, value);
}
DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerAxisCode, jobject unused, jstring controller_type,
jstring axis_name)
{
std::optional<ControllerType> type =
Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str());
if (!type)
return -1;
std::optional<s32> code =
Controller::GetAxisCodeByName(type.value(), AndroidHelpers::JStringToString(env, axis_name));
return code.value_or(-1);
}
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_refreshGameList, jobject obj, jboolean invalidate_cache, jboolean invalidate_database)
{
AndroidHelpers::GetNativeClass(env, obj)->RefreshGameList(invalidate_cache, invalidate_database);

View File

@ -43,6 +43,7 @@ public:
void SetControllerType(u32 index, std::string_view type_name);
void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
void SetControllerAxisState(u32 index, s32 button_code, float value);
void RefreshGameList(bool invalidate_cache, bool invalidate_database);
void ApplySettings();
@ -54,7 +55,6 @@ protected:
bool AcquireHostDisplay() override;
void ReleaseHostDisplay() override;
std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override;
private:
void EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params);

View File

@ -1,17 +0,0 @@
#include "core/host_interface.h"
#include <jni.h>
#define DEFINE_JNI_METHOD(return_type, name, ...) \
extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(__VA_ARGS__)
DEFINE_JNI_METHOD(bool, createSystem)
{
return false;
}
DEFINE_JNI_METHOD(bool, bootSystem, const char* filename, const char* state_filename)
{
return false;
}
DEFINE_JNI_METHOD(void, runFrame) {}