Compare commits

..

3 Commits

Author SHA1 Message Date
lizzie c9d5ddab57 2026-09-15 19:47:11
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-15 19:47:11 +00:00
lizzie 1eb44e3ea6 2026-09-15 19:39:00
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-15 19:39:00 +00:00
lizzie 6c96d9f43d 2026-09-15 07:59:31
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-15 07:59:31 +00:00
8 changed files with 136 additions and 170 deletions
-2
View File
@@ -115,8 +115,6 @@ add_library(
stb.h
steady_clock.cpp
steady_clock.h
stream.cpp
stream.h
string_util.cpp
string_util.h
swap.h
-46
View File
@@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdexcept>
#include "common/common_types.h"
#include "common/stream.h"
namespace Common {
Stream::Stream() = default;
Stream::~Stream() = default;
void Stream::Seek(s32 offset, SeekOrigin origin) {
if (origin == SeekOrigin::SetOrigin) {
if (offset < 0) {
position = 0;
} else if (position >= buffer.size()) {
position = buffer.size();
} else {
position = offset;
}
} else if (origin == SeekOrigin::FromCurrentPos) {
Seek(static_cast<s32>(position) + offset, SeekOrigin::SetOrigin);
} else if (origin == SeekOrigin::FromEnd) {
Seek(static_cast<s32>(buffer.size()) - offset, SeekOrigin::SetOrigin);
}
}
u8 Stream::ReadByte() {
if (position < buffer.size()) {
return buffer[position++];
} else {
throw std::out_of_range("Attempting to read a byte not within the buffer range");
}
}
void Stream::WriteByte(u8 byte) {
if (position == buffer.size()) {
buffer.push_back(byte);
position++;
} else {
buffer.insert(buffer.begin() + position, byte);
}
}
} // namespace Common
-55
View File
@@ -1,55 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include "common/common_types.h"
namespace Common {
enum class SeekOrigin {
SetOrigin,
FromCurrentPos,
FromEnd,
};
class Stream {
public:
/// Stream creates a bitstream and provides common functionality on the stream.
explicit Stream();
~Stream();
Stream(const Stream&) = delete;
Stream& operator=(const Stream&) = delete;
Stream(Stream&&) = default;
Stream& operator=(Stream&&) = default;
/// Reposition bitstream "cursor" to the specified offset from origin
void Seek(s32 offset, SeekOrigin origin);
/// Reads next byte in the stream buffer and increments position
u8 ReadByte();
/// Writes byte at current position
void WriteByte(u8 byte);
[[nodiscard]] std::size_t GetPosition() const {
return position;
}
[[nodiscard]] std::vector<u8>& GetBuffer() {
return buffer;
}
[[nodiscard]] const std::vector<u8>& GetBuffer() const {
return buffer;
}
private:
std::vector<u8> buffer;
std::size_t position{0};
};
} // namespace Common
+106 -39
View File
@@ -28,10 +28,8 @@ JoyconDriver::~JoyconDriver() {
}
void JoyconDriver::Stop() {
if (input_thread.joinable()) {
input_thread.request_stop();
input_thread.join();
}
is_connected = false;
input_thread = {};
}
Common::Input::DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
@@ -60,6 +58,7 @@ Common::Input::DriverResult JoyconDriver::InitializeDevice() {
return Common::Input::DriverResult::InvalidHandle;
}
std::scoped_lock lock{mutex};
disable_input_thread = true;
// Reset Counters
error_counter = 0;
@@ -127,44 +126,62 @@ Common::Input::DriverResult JoyconDriver::InitializeDevice() {
right_stick_calibration, motion_calibration);
// Start polling for data
if (!input_thread.joinable()) {
input_thread = std::jthread([this](std::stop_token stop_token) {
InputThread(stop_token);
});
is_connected = true;
if (!input_thread_running) {
input_thread =
std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
}
disable_input_thread = false;
return Common::Input::DriverResult::Success;
}
void JoyconDriver::InputThread(std::stop_token stop_token) {
LOG_INFO(Input, "Joycon Adapter input thread started");
Common::SetCurrentThreadName("JoyconInput");
input_thread_running = true;
// Max update rate is 5ms, ensure we are always able to read a bit faster
constexpr int ThreadDelay = 3;
std::vector<u8> buffer(MaxBufferSize);
while (!stop_token.stop_requested()) {
// Max update rate is 5ms, so just (timeout) at 300ms
constexpr int READ_TIMEOUT_MS = 300;
constexpr size_t MAX_VIBRATIONS = 4;
std::array<u8, MaxBufferSize> buffer; // Filled by SDL, don't zero-init
int status = 0;
if (IsInputThreadValid()) {
// By disabling the input thread we can ensure custom commands will succeed as no package is
// skipped
status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(), READ_TIMEOUT_MS);
if (IsPayloadCorrect(status, buffer)) {
OnNewData(buffer);
}
if (!vibration_queue.Empty()) {
VibrationValue vibration_value;
vibration_queue.Pop(vibration_value);
last_vibration_result = rumble_protocol->SendVibration(vibration_value);
}
// We can't keep up with vibrations. Start skipping.
while (vibration_queue.Size() >= MAX_VIBRATIONS) {
vibration_queue.Pop();
}
} else {
if (!IsInputThreadValid()) {
input_thread.request_stop();
continue;
}
// By disabling the input thread we can ensure custom commands will succeed as no package is
// skipped
if (!disable_input_thread) {
status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
ThreadDelay);
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
}
if (IsPayloadCorrect(status, buffer)) {
OnNewData(buffer);
}
if (!vibration_queue.Empty()) {
VibrationValue vibration_value;
vibration_queue.Pop(vibration_value);
last_vibration_result = rumble_protocol->SendVibration(vibration_value);
}
// We can't keep up with vibrations. Start skipping.
while (vibration_queue.Size() > 6) {
vibration_queue.Pop();
}
std::this_thread::yield();
}
is_connected = false;
input_thread_running = false;
LOG_INFO(Input, "Joycon Adapter input thread stopped");
}
@@ -254,6 +271,11 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
}
Common::Input::DriverResult JoyconDriver::SetPollingMode() {
SCOPE_EXIT {
disable_input_thread = false;
};
disable_input_thread = true;
rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
if (motion_enabled && supported_features.motion) {
@@ -360,12 +382,17 @@ JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
}
bool JoyconDriver::IsInputThreadValid() const {
if (hidapi_handle == nullptr || hidapi_handle->handle == nullptr)
if (!is_connected.load()) {
return false;
}
if (hidapi_handle->handle == nullptr) {
return false;
}
// Controller is not responding. Terminate connection
if (error_counter > MaxErrorCount)
if (error_counter > MaxErrorCount) {
return false;
return input_thread.joinable();
}
return true;
}
bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
@@ -388,18 +415,30 @@ bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
Common::Input::DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
std::scoped_lock lock{mutex};
if (disable_input_thread) {
return Common::Input::DriverResult::HandleInUse;
}
vibration_queue.Push(vibration);
return last_vibration_result;
}
Common::Input::DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
std::scoped_lock lock{mutex};
if (disable_input_thread) {
return Common::Input::DriverResult::HandleInUse;
}
return generic_protocol->SetLedPattern(led_pattern);
}
Common::Input::DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
std::scoped_lock lock{mutex};
return irs_protocol->SetIrsConfig(mode_, format_);
if (disable_input_thread) {
return Common::Input::DriverResult::HandleInUse;
}
disable_input_thread = true;
const auto result = irs_protocol->SetIrsConfig(mode_, format_);
disable_input_thread = false;
return result;
}
Common::Input::DriverResult JoyconDriver::SetPassiveMode() {
@@ -493,7 +532,12 @@ Common::Input::DriverResult JoyconDriver::StartNfcPolling() {
if (!nfc_protocol->IsEnabled()) {
return Common::Input::DriverResult::Disabled;
}
return nfc_protocol->StartNFCPollingMode();
disable_input_thread = true;
const auto result = nfc_protocol->StartNFCPollingMode();
disable_input_thread = false;
return result;
}
Common::Input::DriverResult JoyconDriver::StopNfcPolling() {
@@ -506,7 +550,10 @@ Common::Input::DriverResult JoyconDriver::StopNfcPolling() {
return Common::Input::DriverResult::Disabled;
}
disable_input_thread = true;
const auto result = nfc_protocol->StopNFCPollingMode();
disable_input_thread = false;
if (amiibo_detected) {
amiibo_detected = false;
joycon_poller->UpdateAmiibo({});
@@ -529,7 +576,11 @@ Common::Input::DriverResult JoyconDriver::ReadAmiiboData(std::vector<u8>& out_da
}
out_data.resize(0x21C);
return nfc_protocol->ReadAmiibo(out_data);
disable_input_thread = true;
const auto result = nfc_protocol->ReadAmiibo(out_data);
disable_input_thread = false;
return result;
}
Common::Input::DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) {
@@ -544,7 +595,12 @@ Common::Input::DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data)
if (!amiibo_detected) {
return Common::Input::DriverResult::ErrorWritingData;
}
return nfc_protocol->WriteAmiibo(data);
disable_input_thread = true;
const auto result = nfc_protocol->WriteAmiibo(data);
disable_input_thread = false;
return result;
}
Common::Input::DriverResult JoyconDriver::ReadMifareData(std::span<const MifareReadChunk> data,
@@ -560,7 +616,12 @@ Common::Input::DriverResult JoyconDriver::ReadMifareData(std::span<const MifareR
if (!amiibo_detected) {
return Common::Input::DriverResult::ErrorWritingData;
}
return nfc_protocol->ReadMifare(data, out_data);
disable_input_thread = true;
const auto result = nfc_protocol->ReadMifare(data, out_data);
disable_input_thread = false;
return result;
}
Common::Input::DriverResult JoyconDriver::WriteMifareData(std::span<const MifareWriteChunk> data) {
@@ -575,11 +636,17 @@ Common::Input::DriverResult JoyconDriver::WriteMifareData(std::span<const Mifare
if (!amiibo_detected) {
return Common::Input::DriverResult::ErrorWritingData;
}
return nfc_protocol->WriteMifare(data);
disable_input_thread = true;
const auto result = nfc_protocol->WriteMifare(data);
disable_input_thread = false;
return result;
}
bool JoyconDriver::IsConnected() const {
return input_thread.joinable();
std::scoped_lock lock{mutex};
return is_connected.load();
}
bool JoyconDriver::IsVibrationEnabled() const {
+2 -3
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -163,6 +160,8 @@ private:
// Thread related
mutable std::mutex mutex;
std::jthread input_thread;
bool input_thread_running{};
bool disable_input_thread{};
};
} // namespace InputCommon::Joycon
@@ -22,7 +22,7 @@ void JoyconPoller::SetCallbacks(const JoyconCallbacks& callbacks_) {
void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
const RingStatus& ring_status) {
InputReportActive data{};
std::memcpy(&data, buffer.data(), sizeof(InputReportActive));
memcpy(&data, buffer.data(), sizeof(InputReportActive));
switch (device_type) {
case ControllerType::Left:
@@ -47,7 +47,7 @@ void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& moti
void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
InputReportPassive data{};
std::memcpy(&data, buffer.data(), sizeof(InputReportPassive));
memcpy(&data, buffer.data(), sizeof(InputReportPassive));
switch (device_type) {
case ControllerType::Left:
+20 -19
View File
@@ -892,20 +892,28 @@ void VpxRangeEncoder::Write(bool bit, s32 probability) {
count += shift;
if (count >= 0) {
const s32 offset = shift - count;
if (((low_value << (offset - 1)) >> 31) != 0) {
const s32 current_pos = static_cast<s32>(base_stream.GetPosition());
base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
while (PeekByte() == 0xff) {
base_stream.WriteByte(0);
base_stream.Seek(-2, Common::SeekOrigin::FromCurrentPos);
auto const write_byte = [&](u8 byte) {
if (stream_pos == stream_data.size()) {
stream_data.push_back(byte);
++stream_pos;
} else {
stream_data.insert(stream_data.begin() + stream_pos, byte);
}
base_stream.WriteByte(static_cast<u8>((PeekByte() + 1)));
base_stream.Seek(current_pos, Common::SeekOrigin::SetOrigin);
};
const s32 offset = shift - count;
if (((low_value << (offset - 1)) >> 31) != 0) {
auto const current_pos = stream_pos;
--stream_pos;
while (stream_data[stream_pos] == 0xff) {
write_byte(0);
stream_pos -= 2;
}
write_byte(stream_data[stream_pos] + 1);
stream_pos = current_pos;
}
base_stream.WriteByte(static_cast<u8>((low_value >> (24 - offset))));
write_byte(u8((low_value >> (24 - offset))));
++stream_pos;
low_value <<= offset;
shift = count;
@@ -923,13 +931,6 @@ void VpxRangeEncoder::End() {
}
}
u8 VpxRangeEncoder::PeekByte() {
const u8 value = base_stream.ReadByte();
base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
return value;
}
VpxBitStreamWriter::VpxBitStreamWriter() = default;
VpxBitStreamWriter::~VpxBitStreamWriter() = default;
+6 -4
View File
@@ -12,7 +12,6 @@
#include "common/common_types.h"
#include "common/scratch_buffer.h"
#include "common/stream.h"
#include "video_core/host1x/codecs/decoder.h"
#include "video_core/host1x/codec_types.h"
#include "video_core/host1x/nvdec_common.h"
@@ -52,16 +51,19 @@ public:
void End();
[[nodiscard]] std::vector<u8>& GetBuffer() {
return base_stream.GetBuffer();
return stream_data;
}
[[nodiscard]] const std::vector<u8>& GetBuffer() const {
return base_stream.GetBuffer();
return stream_data;
}
private:
u8 PeekByte();
Common::Stream base_stream{};
std::vector<u8> stream_data{};
size_t stream_pos;
u32 low_value{};
u32 range{0xff};
s32 count{-24};