Skip to content

Commit

Permalink
Implement Rumble Support for Controllers and Device Vibrators
Browse files Browse the repository at this point in the history
  • Loading branch information
PixelyIon committed Sep 6, 2020
1 parent d8ccdd7 commit 1a58a2e
Show file tree
Hide file tree
Showing 26 changed files with 397 additions and 51 deletions.
1 change: 1 addition & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/services/am/applet/ILibraryAppletAccessor.cpp
${source_DIR}/skyline/services/hid/IHidServer.cpp
${source_DIR}/skyline/services/hid/IAppletResource.cpp
${source_DIR}/skyline/services/hid/IActiveVibrationDeviceList.cpp
${source_DIR}/skyline/services/timesrv/IStaticService.cpp
${source_DIR}/skyline/services/timesrv/ISystemClock.cpp
${source_DIR}/skyline/services/timesrv/ISteadyClock.cpp
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/cpp/emu_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonSt
auto device = input->npad.controllers[index].device;
if (device)
device->SetButtonState(skyline::input::NpadButton{.raw = static_cast<skyline::u64>(mask)}, pressed);
} catch (const std::bad_weak_ptr&) {
} catch (const std::bad_weak_ptr &) {
// We don't mind if we miss button updates while input hasn't been initialized
}
}
Expand All @@ -127,7 +127,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValu
auto device = input->npad.controllers[index].device;
if (device)
device->SetAxisValue(static_cast<skyline::input::NpadAxisId>(axis), value);
} catch (const std::bad_weak_ptr&) {
} catch (const std::bad_weak_ptr &) {
// We don't mind if we miss axis updates while input hasn't been initialized
}
}
1 change: 1 addition & 0 deletions app/src/main/cpp/skyline/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <map>
#include <unordered_map>
#include <span>
#include <vector>
#include <fstream>
#include <syslog.h>
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/cpp/skyline/gpu/gpfifo.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#pragma once

#include <common.h>
#include <span>
#include <queue>
#include "engines/engine.h"
#include "engines/gpfifo.h"
Expand Down Expand Up @@ -177,4 +176,4 @@ namespace skyline::gpu {
void Push(std::span<GpEntry> entries);
};
}
}
}
1 change: 0 additions & 1 deletion app/src/main/cpp/skyline/gpu/memory_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#pragma once

#include <span>
#include <common.h>

namespace skyline {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/cpp/skyline/input/npad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ namespace skyline::input {
if (style.raw) {
if (style.proController || style.joyconHandheld || style.joyconLeft || style.joyconRight) {
device.Connect(controller.type);
device.index = static_cast<size_t>(&controller - controllers.data());
device.partnerIndex = -1;
controller.device = &device;
} else if (style.joyconDual && orientation == NpadJoyOrientation::Vertical && device.GetAssignment() == NpadJoyAssignment::Dual) {
device.Connect(NpadControllerType::JoyconDual);
device.index = static_cast<size_t>(&controller - controllers.data());
device.partnerIndex = controller.partnerIndex;
controller.device = &device;
controllers.at(controller.partnerIndex).device = &device;
} else {
Expand Down
91 changes: 91 additions & 0 deletions app/src/main/cpp/skyline/input/npad_device.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)

#include <jvm.h>
#include "npad_device.h"
#include "npad.h"

Expand Down Expand Up @@ -155,6 +156,9 @@ namespace skyline::input {
section = {};
globalTimestamp = 0;

index = -1;
partnerIndex = -1;

type = NpadControllerType::None;
controllerInfo = nullptr;

Expand Down Expand Up @@ -347,4 +351,91 @@ namespace skyline::input {

globalTimestamp++;
}

void NpadDevice::VibrateDevice(i8 vibrateIndex, const NpadVibrationValue &value) {
std::array<jlong, 3> timings;
std::array<jint, 3> amplitudes;

jlong periodLow = 1000 / value.frequencyLow;
jlong periodHigh = 1000 / value.frequencyHigh;

jint amplitudeLow = value.amplitudeLow * 127;
jint amplitudeHigh = value.amplitudeHigh * 127;

if (amplitudeLow + amplitudeHigh == 0 || periodLow + periodHigh == 0) {
manager.state.jvm->ClearVibrationDevice(vibrateIndex);
return;
}

if (periodLow == periodHigh) {
timings = {periodLow, periodHigh, 0};
amplitudes = {std::min(amplitudeLow + amplitudeHigh, 255), 0, 0};
} else if (periodLow < periodHigh) {
timings = {periodLow, periodHigh - periodLow, periodHigh};
amplitudes = {std::min(amplitudeLow + amplitudeHigh, 255), amplitudeHigh, 0};
} else if (periodHigh < periodLow) {
timings = {periodHigh, periodLow - periodHigh, periodLow};
amplitudes = {std::min(amplitudeHigh + amplitudeLow, 255), amplitudeLow, 0};
}

manager.state.jvm->VibrateDevice(vibrateIndex, timings, amplitudes);
}

void NpadDevice::Vibrate(bool isRight, const NpadVibrationValue &value) {
if (isRight)
vibrationRight = value;
else
vibrationLeft = value;

if (vibrationRight)
Vibrate(vibrationLeft, *vibrationRight);
else
VibrateDevice(index, value);
}

void NpadDevice::Vibrate(const NpadVibrationValue &left, const NpadVibrationValue &right) {
if (partnerIndex == -1) {
std::array<jlong, 5> timings;
std::array<jint, 5> amplitudes;

std::array<std::pair<jlong, jint>, 4> vibrations{std::pair<jlong, jint>{1000 / left.frequencyLow, left.amplitudeLow * 64},
{1000 / left.frequencyHigh, left.amplitudeHigh * 64},
{1000 / right.frequencyLow, right.amplitudeLow * 64},
{1000 / right.frequencyHigh, right.amplitudeHigh * 64},
};

jlong totalTime{};
std::sort(vibrations.begin(), vibrations.end(), [](const std::pair<jlong, jint> &a, const std::pair<jlong, jint> &b) {
return a.first < b.first;
});

jint totalAmplitude{};
for (const auto &vibration : vibrations)
totalAmplitude += vibration.second;

if (totalAmplitude == 0 || vibrations[3].first == 0) {
manager.state.jvm->ClearVibrationDevice(index);
return;
}

for (u8 i{0}; i < vibrations.size(); i++) {
const auto &vibration = vibrations[i];

auto time = vibration.first - totalTime;
timings[i] = time;
totalTime += time;

amplitudes[i] = std::min(totalAmplitude, 255);
totalAmplitude -= vibration.second;
}

timings[4] = totalTime;
amplitudes[4] = 0;

manager.state.jvm->VibrateDevice(index, timings, amplitudes);
} else {
VibrateDevice(index, left);
VibrateDevice(partnerIndex, right);
}
}
}
54 changes: 54 additions & 0 deletions app/src/main/cpp/skyline/input/npad_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,50 @@ namespace skyline::input {
Handheld = 0x20,
};

/**
* @brief A handle to a specific device addressed by it's ID and type
* @note This is used by both Six-Axis and Vibration
*/
union __attribute__((__packed__)) NpadDeviceHandle {
u32 raw;
struct {
u8 type;
NpadId id : 8;
bool isRight : 1; //!< If this is a right Joy-Con (Both) or right LRA in the Pro-Controller (Vibration)
bool isSixAxisSingle : 1; //!< If the Six-Axis device is a single unit, either Handheld or Pro-Controller
};

constexpr NpadControllerType GetType() {
switch (type) {
case 3:
return NpadControllerType::ProController;
case 4:
return NpadControllerType::Handheld;
case 5:
return NpadControllerType::JoyconDual;
case 6:
return NpadControllerType::JoyconLeft;
case 7:
return NpadControllerType::JoyconRight;
}
return NpadControllerType::None;
}
};

/**
* @brief The parameters to produce a vibration using an LRA
* @note The vibration is broken into a frequency band with the lower and high range supplied
* @note Amplitude is in arbitrary units from 0f to 1f
* @note Frequency is in Hertz
*/
struct NpadVibrationValue {
float amplitudeLow;
float frequencyLow;
float amplitudeHigh;
float frequencyHigh;
};
static_assert(sizeof(NpadVibrationValue) == 0x10);

class NpadManager;

/**
Expand All @@ -85,8 +129,14 @@ namespace skyline::input {
*/
NpadControllerInfo &GetControllerInfo();

void VibrateDevice(i8 index, const NpadVibrationValue &value);

public:
NpadId id;
i8 index{-1}; //!< The index of the device assigned to this player
i8 partnerIndex{-1}; //!< The index of a partner device, if present
NpadVibrationValue vibrationLeft; //!< Vibration for the left Joy-Con (Handheld/Pair), left LRA in a Pro-Controller or individual Joy-Cons
std::optional<NpadVibrationValue> vibrationRight; //!< Vibration for the right Joy-Con (Handheld/Pair) or right LRA in a Pro-Controller
NpadControllerType type{};
NpadConnectionState connectionState{};
std::shared_ptr<kernel::type::KEvent> updateEvent; //!< This event is triggered on the controller's style changing
Expand Down Expand Up @@ -132,5 +182,9 @@ namespace skyline::input {
* @param value The value to set
*/
void SetAxisValue(NpadAxisId axis, i32 value);

void Vibrate(bool isRight, const NpadVibrationValue &value);

void Vibrate(const NpadVibrationValue &left, const NpadVibrationValue &right);
};
}
38 changes: 19 additions & 19 deletions app/src/main/cpp/skyline/input/sections/Npad.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,38 +147,38 @@ namespace skyline::input {
/**
* @brief This structure is used to hold a single sample of 3D data from the IMU
*/
struct SixaxisVector {
struct SixAxisVector {
float x; //!< The data in the X-axis
float y; //!< The data in the Y-axis
float z; //!< The data in the Z-axis
};
static_assert(sizeof(SixaxisVector) == 0xC);
static_assert(sizeof(SixAxisVector) == 0xC);

/**
* @brief This structure contains data about the state of the controller's IMU (Sixaxis) (https://switchbrew.org/wiki/HID_Shared_Memory#NpadSixAxisSensorHandheldState)
* @brief This structure contains data about the state of the controller's IMU (Six-Axis) (https://switchbrew.org/wiki/HID_Shared_Memory#NpadSixAxisSensorHandheldState)
*/
struct NpadSixaxisState {
struct NpadSixAxisState {
u64 globalTimestamp; //!< The global timestamp in samples
u64 _unk0_;
u64 localTimestamp; //!< The local timestamp in samples

SixaxisVector accelerometer;
SixaxisVector gyroscope;
SixaxisVector rotation;
std::array<SixaxisVector, 3> orientation; //!< The orientation basis data as a matrix
SixAxisVector accelerometer;
SixAxisVector gyroscope;
SixAxisVector rotation;
std::array<SixAxisVector, 3> orientation; //!< The orientation basis data as a matrix

u64 _unk2_; //!< This is always 1
};
static_assert(sizeof(NpadSixaxisState) == 0x68);
static_assert(sizeof(NpadSixAxisState) == 0x68);

/**
* @brief This structure contains header and entries for the IMU (Sixaxis) data
* @brief This structure contains header and entries for the IMU (Six-Axis) data
*/
struct NpadSixaxisInfo {
struct NpadSixAxisInfo {
CommonHeader header;
std::array<NpadSixaxisState, constant::HidEntryCount> state;
std::array<NpadSixAxisState, constant::HidEntryCount> state;
};
static_assert(sizeof(NpadSixaxisInfo) == 0x708);
static_assert(sizeof(NpadSixAxisInfo) == 0x708);

/**
* @brief This is a bit-field of all the device types (https://switchbrew.org/wiki/HID_services#DeviceType)
Expand Down Expand Up @@ -268,12 +268,12 @@ namespace skyline::input {
NpadControllerInfo palmaController; //!< The Poké Ball Plus controller data
NpadControllerInfo defaultController; //!< The Default controller data (Inputs are rotated based on orientation and SL/SR are mapped to L/R incase it is a single JC)

NpadSixaxisInfo fullKeySixaxis; //!< The Pro/GC IMU data
NpadSixaxisInfo handheldSixaxis; //!< The Handheld IMU data
NpadSixaxisInfo dualLeftSixaxis; //!< The Left Joy-Con in dual mode's IMU data
NpadSixaxisInfo dualRightSixaxis; //!< The Left Joy-Con in dual mode's IMU data
NpadSixaxisInfo leftSixaxis; //!< The Left Joy-Con IMU data
NpadSixaxisInfo rightSixaxis; //!< The Right Joy-Con IMU data
NpadSixAxisInfo fullKeySixAxis; //!< The Pro/GC IMU data
NpadSixAxisInfo handheldSixAxis; //!< The Handheld IMU data
NpadSixAxisInfo dualLeftSixAxis; //!< The Left Joy-Con in dual mode's IMU data
NpadSixAxisInfo dualRightSixAxis; //!< The Left Joy-Con in dual mode's IMU data
NpadSixAxisInfo leftSixAxis; //!< The Left Joy-Con IMU data
NpadSixAxisInfo rightSixAxis; //!< The Right Joy-Con IMU data

NpadDeviceType deviceType;

Expand Down
23 changes: 22 additions & 1 deletion app/src/main/cpp/skyline/jvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
thread_local JNIEnv *env;

namespace skyline {
JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(instance), instanceClass(reinterpret_cast<jclass>(environ->NewGlobalRef(environ->GetObjectClass(instance)))), initializeControllersId(environ->GetMethodID(instanceClass, "initializeControllers", "()V")) {
JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(environ->NewGlobalRef(instance)), instanceClass(reinterpret_cast<jclass>(environ->NewGlobalRef(environ->GetObjectClass(instance)))), initializeControllersId(environ->GetMethodID(instanceClass, "initializeControllers", "()V")), vibrateDeviceId(environ->GetMethodID(instanceClass, "vibrateDevice", "(I[J[I)V")), clearVibrationDeviceId(environ->GetMethodID(instanceClass, "clearVibrationDevice", "(I)V")) {
env = environ;
if (env->GetJavaVM(&vm) < 0)
throw exception("Cannot get JavaVM from environment");
}

JvmManager::~JvmManager() {
env->DeleteGlobalRef(instanceClass);
env->DeleteGlobalRef(instance);
}

void JvmManager::AttachThread() {
if (!env)
vm->AttachCurrentThread(&env, nullptr);
Expand Down Expand Up @@ -41,4 +46,20 @@ namespace skyline {
void JvmManager::InitializeControllers() {
env->CallVoidMethod(instance, initializeControllersId);
}

void JvmManager::VibrateDevice(jint index, const std::span<jlong> &timings, const std::span<jint> &amplitudes) {
auto jTimings = env->NewLongArray(timings.size());
env->SetLongArrayRegion(jTimings, 0, timings.size(), timings.data());
auto jAmplitudes = env->NewIntArray(amplitudes.size());
env->SetIntArrayRegion(jAmplitudes, 0, amplitudes.size(), amplitudes.data());

env->CallVoidMethod(instance, vibrateDeviceId, index, jTimings, jAmplitudes);

env->DeleteLocalRef(jTimings);
env->DeleteLocalRef(jAmplitudes);
}

void JvmManager::ClearVibrationDevice(jint index) {
env->CallVoidMethod(instance, clearVibrationDeviceId, index);
}
}
14 changes: 14 additions & 0 deletions app/src/main/cpp/skyline/jvm.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ namespace skyline {
*/
JvmManager(JNIEnv *env, jobject instance);

~JvmManager();

/**
* @brief Attach the current thread to the Java VM
*/
Expand Down Expand Up @@ -92,7 +94,19 @@ namespace skyline {
*/
void InitializeControllers();

/**
* @brief A call to EmulationActivity.vibrateDevice in Kotlin
*/
void VibrateDevice(jint index, const std::span<jlong> &timings, const std::span<jint> &amplitudes);

/**
* @brief A call to EmulationActivity.clearVibrationDevice in Kotlin
*/
void ClearVibrationDevice(jint index);

private:
jmethodID initializeControllersId;
jmethodID vibrateDeviceId;
jmethodID clearVibrationDeviceId;
};
}
Loading

0 comments on commit 1a58a2e

Please sign in to comment.