Merge pull request #13197 from jordan-woyak/vrr-mapping-indicators

DolphinQt: Update mapping indicators at screen refresh rate.
This commit is contained in:
JMC47 2024-12-03 12:51:18 -05:00 committed by GitHub
commit 26ba8f5481
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 103 additions and 59 deletions

View File

@ -5,7 +5,6 @@
#include <array>
#include <cmath>
#include <numeric>
#include <fmt/format.h>
@ -19,12 +18,10 @@
#include "Core/HW/WiimoteEmu/Camera.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/Force.h"
#include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"
#include "DolphinQt/Config/Mapping/MappingWidget.h"
@ -289,7 +286,14 @@ void GenerateFibonacciSphere(int point_count, F&& callback)
void MappingIndicator::paintEvent(QPaintEvent*)
{
constexpr float max_elapsed_seconds = 0.1f;
const auto now = Clock::now();
const float elapsed_seconds = std::chrono::duration_cast<DT_s>(now - m_last_update).count();
m_last_update = now;
const auto lock = ControllerEmu::EmulatedController::GetStateLock();
Update(std::min(elapsed_seconds, max_elapsed_seconds));
Draw();
}
@ -321,7 +325,7 @@ void SquareIndicator::TransformPainter(QPainter& p)
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
p.translate(width() / 2, height() / 2);
p.translate(width() / 2.0, height() / 2.0);
const auto scale = GetContentsScale();
p.scale(scale, scale);
}
@ -413,10 +417,13 @@ void AnalogStickIndicator::Draw()
(adj_coord.x || adj_coord.y) ? std::make_optional(adj_coord) : std::nullopt);
}
void TiltIndicator::Update(float elapsed_seconds)
{
WiimoteEmu::EmulateTilt(&m_motion_state, &m_group, elapsed_seconds);
}
void TiltIndicator::Draw()
{
WiimoteEmu::EmulateTilt(&m_motion_state, &m_group, 1.f / INDICATOR_UPDATE_FREQ);
auto adj_coord = Common::DVec2{-m_motion_state.angle.y, m_motion_state.angle.x} / MathUtil::PI;
// Angle values after dividing by pi.
@ -564,28 +571,54 @@ void SwingIndicator::DrawUnderGate(QPainter& p)
}
}
void SwingIndicator::Update(float elapsed_seconds)
{
WiimoteEmu::EmulateSwing(&m_motion_state, &m_swing_group, elapsed_seconds);
}
void SwingIndicator::Draw()
{
auto& force = m_swing_group;
WiimoteEmu::EmulateSwing(&m_motion_state, &force, 1.f / INDICATOR_UPDATE_FREQ);
DrawReshapableInput(force, SWING_GATE_COLOR,
DrawReshapableInput(m_swing_group, SWING_GATE_COLOR,
Common::DVec2{-m_motion_state.position.x, m_motion_state.position.z});
}
void ShakeMappingIndicator::Update(float elapsed_seconds)
{
WiimoteEmu::EmulateShake(&m_motion_state, &m_shake_group, elapsed_seconds);
for (auto& sample : m_position_samples)
sample.age += elapsed_seconds;
m_position_samples.erase(
std::ranges::find_if(m_position_samples,
[](const ShakeSample& sample) { return sample.age > 1.f; }),
m_position_samples.end());
constexpr float MAX_DISTANCE = 0.5f;
m_position_samples.push_front(ShakeSample{m_motion_state.position / MAX_DISTANCE});
const bool any_non_zero_samples =
std::any_of(m_position_samples.begin(), m_position_samples.end(),
[](const ShakeSample& s) { return s.state.LengthSquared() != 0.0; });
// Only start moving the line if there's non-zero data.
if (m_grid_line_position || any_non_zero_samples)
{
m_grid_line_position += elapsed_seconds;
if (m_grid_line_position > 1.f)
{
if (any_non_zero_samples)
m_grid_line_position = std::fmod(m_grid_line_position, 1.f);
else
m_grid_line_position = 0;
}
}
}
void ShakeMappingIndicator::Draw()
{
constexpr std::size_t HISTORY_COUNT = INDICATOR_UPDATE_FREQ;
WiimoteEmu::EmulateShake(&m_motion_state, &m_shake_group, 1.f / INDICATOR_UPDATE_FREQ);
constexpr float MAX_DISTANCE = 0.5f;
m_position_samples.push_front(m_motion_state.position / MAX_DISTANCE);
// This also holds the current state so +1.
if (m_position_samples.size() > HISTORY_COUNT + 1)
m_position_samples.pop_back();
QPainter p(this);
DrawBoundingBox(p);
TransformPainter(p);
@ -610,15 +643,7 @@ void ShakeMappingIndicator::Draw()
p.drawPoint(QPointF{-0.5 + c * 0.5, raw_coord.data[c]});
}
// Grid line.
if (m_grid_line_position ||
std::any_of(m_position_samples.begin(), m_position_samples.end(),
[](const Common::Vec3& v) { return v.LengthSquared() != 0.0; }))
{
// Only start moving the line if there's non-zero data.
m_grid_line_position = (m_grid_line_position + 1) % HISTORY_COUNT;
}
const double grid_line_x = 1.0 - m_grid_line_position * 2.0 / HISTORY_COUNT;
const double grid_line_x = 1.0 - m_grid_line_position * 2.0;
p.setPen(QPen(GetRawInputColor(), 0));
p.drawLine(QPointF{grid_line_x, -1.0}, QPointF{grid_line_x, 1.0});
@ -629,12 +654,8 @@ void ShakeMappingIndicator::Draw()
{
QPolygonF polyline;
int i = 0;
for (auto& sample : m_position_samples)
{
polyline.append(QPointF{1.0 - i * 2.0 / HISTORY_COUNT, sample.data[c]});
++i;
}
polyline.append(QPointF{1.0 - sample.age * 2.0, sample.state.data[c]});
p.setPen(QPen(component_colors[c], 0));
p.drawPolyline(polyline);
@ -692,7 +713,7 @@ void AccelerometerMappingIndicator::Draw()
p.setBrush(Qt::NoBrush);
p.resetTransform();
p.translate(width() / 2, height() / 2);
p.translate(width() / 2.0, height() / 2.0);
// Red dot upright target.
p.setPen(GetAdjustedInputColor());
@ -717,6 +738,28 @@ void AccelerometerMappingIndicator::Draw()
fmt::format("{:.2f} g", state.Length() / WiimoteEmu::GRAVITY_ACCELERATION)));
}
void GyroMappingIndicator::Update(float elapsed_seconds)
{
const auto gyro_state = m_gyro_group.GetState();
const auto angular_velocity = gyro_state.value_or(Common::Vec3{});
m_state *= WiimoteEmu::GetRotationFromGyroscope(angular_velocity * Common::Vec3(-1, +1, -1) *
elapsed_seconds);
m_state = m_state.Normalized();
// Reset orientation when stable for a bit:
constexpr float STABLE_RESET_SECONDS = 1.f;
// Consider device stable when data (with deadzone applied) is zero.
const bool is_stable = !angular_velocity.LengthSquared();
if (!is_stable)
m_stable_time = 0;
else if (m_stable_time < STABLE_RESET_SECONDS)
m_stable_time += elapsed_seconds;
if (m_stable_time >= STABLE_RESET_SECONDS)
m_state = Common::Quaternion::Identity();
}
void GyroMappingIndicator::Draw()
{
const auto gyro_state = m_gyro_group.GetState();
@ -725,23 +768,9 @@ void GyroMappingIndicator::Draw()
const auto jitter = raw_gyro_state - m_previous_velocity;
m_previous_velocity = raw_gyro_state;
m_state *= WiimoteEmu::GetRotationFromGyroscope(angular_velocity * Common::Vec3(-1, +1, -1) /
INDICATOR_UPDATE_FREQ);
m_state = m_state.Normalized();
// Reset orientation when stable for a bit:
constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ;
// Consider device stable when data (with deadzone applied) is zero.
const bool is_stable = !angular_velocity.LengthSquared();
if (!is_stable)
m_stable_steps = 0;
else if (m_stable_steps != STABLE_RESET_STEPS)
++m_stable_steps;
if (STABLE_RESET_STEPS == m_stable_steps)
m_state = Common::Quaternion::Identity();
// Use an empty rotation matrix if gyroscope data is not present.
const auto rotation =
(gyro_state.has_value() ? Common::Matrix33::FromQuaternion(m_state) : Common::Matrix33{});
@ -814,7 +843,7 @@ void GyroMappingIndicator::Draw()
p.setBrush(Qt::NoBrush);
p.resetTransform();
p.translate(width() / 2, height() / 2);
p.translate(width() / 2.0, height() / 2.0);
// Red dot upright target.
p.setPen(GetAdjustedInputColor());

View File

@ -45,9 +45,12 @@ public:
protected:
virtual void Draw() {}
virtual void Update(float elapsed_seconds) {}
private:
void paintEvent(QPaintEvent*) override;
Clock::time_point m_last_update = Clock::now();
};
class SquareIndicator : public MappingIndicator
@ -98,6 +101,7 @@ public:
private:
void Draw() override;
void Update(float elapsed_seconds) override;
ControllerEmu::Tilt& m_group;
WiimoteEmu::MotionState m_motion_state{};
@ -132,6 +136,7 @@ public:
private:
void Draw() override;
void Update(float elapsed_seconds) override;
void DrawUnderGate(QPainter& p) override;
@ -146,11 +151,19 @@ public:
private:
void Draw() override;
void Update(float elapsed_seconds) override;
ControllerEmu::Shake& m_shake_group;
WiimoteEmu::MotionState m_motion_state{};
std::deque<ControllerEmu::Shake::StateData> m_position_samples;
int m_grid_line_position = 0;
struct ShakeSample
{
ControllerEmu::Shake::StateData state;
float age = 0.f;
};
std::deque<ShakeSample> m_position_samples;
float m_grid_line_position = 0;
};
class AccelerometerMappingIndicator : public SquareIndicator
@ -174,11 +187,12 @@ public:
private:
void Draw() override;
void Update(float elapsed_seconds) override;
ControllerEmu::IMUGyroscope& m_gyro_group;
Common::Quaternion m_state = Common::Quaternion::Identity();
Common::Vec3 m_previous_velocity = {};
u32 m_stable_steps = 0;
float m_stable_time = 0;
};
class IRPassthroughMappingIndicator : public SquareIndicator

View File

@ -27,8 +27,6 @@ class NumericSettingBase;
enum class SettingVisibility;
} // namespace ControllerEmu
constexpr int INDICATOR_UPDATE_FREQ = 30;
class MappingWidget : public QWidget
{
Q_OBJECT

View File

@ -10,12 +10,12 @@
#include <QGroupBox>
#include <QHBoxLayout>
#include <QPushButton>
#include <QScreen>
#include <QTabWidget>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
#include "Core/Core.h"
#include "Core/HotkeyManager.h"
#include "Common/CommonPaths.h"
@ -73,12 +73,15 @@ MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
SetMappingType(type);
const auto timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [this] {
connect(timer, &QTimer::timeout, this, [this, timer] {
const double refresh_rate = screen()->refreshRate();
timer->setInterval(1000 / refresh_rate);
const auto lock = GetController()->GetStateLock();
emit Update();
});
timer->start(1000 / INDICATOR_UPDATE_FREQ);
timer->start(100);
const auto lock = GetController()->GetStateLock();
emit ConfigChanged();