Compare commits
30 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73a87edc45 | ||
|
|
250c3d555e | ||
|
|
f490c15b0f | ||
|
|
1dc4ff0974 | ||
|
|
34f75023b2 | ||
|
|
880006c5ca | ||
|
|
52dd68cfff | ||
|
|
c2895df336 | ||
|
|
58bc6125d7 | ||
|
|
e5615c4704 | ||
|
|
1e3afd5d80 | ||
|
|
5c332b90c7 | ||
|
|
2e46110379 | ||
|
|
b7642cff36 | ||
|
|
6c47b43a60 | ||
|
|
ea861cc1c4 | ||
|
|
64fd9f41a7 | ||
|
|
a761d020c6 | ||
|
|
9479940a1f | ||
|
|
1779795d81 | ||
|
|
2fdefd7064 | ||
|
|
81f66eec0c | ||
|
|
5e27d37edc | ||
|
|
ceb70b2139 | ||
|
|
21b1e9c21a | ||
|
|
3ac4f3a252 | ||
|
|
d7d09355e7 | ||
|
|
bee823db3a | ||
|
|
3f37e228a3 | ||
|
|
fa09f7aa6c |
@@ -54,6 +54,9 @@ mkdir -p AppDir/usr/optional/libgcc_s
|
||||
# Deploy yuzu's needed dependencies
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt
|
||||
|
||||
# Workaround for libQt5MultimediaGstTools indirectly requiring libwayland-client and breaking Vulkan usage on end-user systems
|
||||
find AppDir -type f -regex '.*libwayland-client\.so.*' -delete -print
|
||||
|
||||
# Workaround for building yuzu with GCC 10 but also trying to distribute it to Ubuntu 18.04 et al.
|
||||
# See https://github.com/darealshinji/AppImageKit-checkrt
|
||||
cp exec-x86_64.so AppDir/usr/optional/exec.so
|
||||
|
||||
@@ -57,6 +57,10 @@ Copy-Item .\CMakeModules -Recurse -Destination $MSVC_SOURCE
|
||||
if ("$env:GITHUB_ACTIONS" -eq "true") {
|
||||
echo "Hello GitHub Actions"
|
||||
|
||||
# With vcpkg we now have a few more dll files
|
||||
ls .\build\bin\*.dll
|
||||
cp .\build\bin\*.dll .\artifacts\
|
||||
|
||||
# Hopefully there is an exe in either .\build\bin or .\build\bin\Release
|
||||
cp .\build\bin\yuzu*.exe .\artifacts\
|
||||
Copy-Item "$BUILD_DIR\*" -Destination "artifacts" -Recurse
|
||||
@@ -112,6 +116,3 @@ Get-ChildItem . -Filter "*.zip" | Copy-Item -destination "artifacts"
|
||||
Get-ChildItem . -Filter "*.7z" | Copy-Item -destination "artifacts"
|
||||
Get-ChildItem . -Filter "*.tar.xz" | Copy-Item -destination "artifacts"
|
||||
}
|
||||
# Extra items
|
||||
git status
|
||||
cp .\build\src\common\scm_rev.cpp .\artifacts
|
||||
|
||||
@@ -9,7 +9,7 @@ parameters:
|
||||
steps:
|
||||
- script: choco install vulkan-sdk
|
||||
displayName: 'Install vulkan-sdk'
|
||||
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_TESTS=OFF -DYUZU_USE_BUNDLED_VCPKG=ON .. && cd ..
|
||||
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_TESTS=OFF -DYUZU_USE_BUNDLED_VCPKG=ON .. && cd ..
|
||||
displayName: 'Configure CMake'
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build'
|
||||
|
||||
@@ -50,7 +50,7 @@ stages:
|
||||
timeoutInMinutes: 120
|
||||
displayName: 'msvc'
|
||||
pool:
|
||||
vmImage: windows-2022
|
||||
vmImage: windows-2019
|
||||
steps:
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
|
||||
@@ -15,7 +15,7 @@ stages:
|
||||
timeoutInMinutes: 120
|
||||
displayName: 'windows-msvc'
|
||||
pool:
|
||||
vmImage: windows-2022
|
||||
vmImage: windows-2019
|
||||
steps:
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2016 MerryMage
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
---
|
||||
name: Bug Report / Feature Request
|
||||
about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu or you are requesting a feature you believe would make yuzu better.
|
||||
@@ -42,4 +37,3 @@ When submitting an issue, please check the following:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,6 +1,3 @@
|
||||
# SPDX-FileCopyrightText: 2020 tgsm <doodrabbit@hotmail.com>
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: yuzu Discord
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -21,7 +21,7 @@
|
||||
url = https://github.com/libusb/libusb.git
|
||||
[submodule "discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/discord/discord-rpc.git
|
||||
url = https://github.com/yuzu-emu/discord-rpc.git
|
||||
[submodule "Vulkan-Headers"]
|
||||
path = externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
|
||||
@@ -104,3 +104,11 @@ License: GPL-2.0-or-later
|
||||
Files: vcpkg.json
|
||||
Copyright: 2022 yuzu Emulator Project
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: .github/ISSUE_TEMPLATE/config.yml
|
||||
Copyright: 2020 tgsm <doodrabbit@hotmail.com>
|
||||
License: GPL-2.0-or-later
|
||||
|
||||
Files: .github/ISSUE_TEMPLATE/bug-report-feature-request.md
|
||||
Copyright: 2016 MerryMage
|
||||
License: GPL-2.0-or-later
|
||||
|
||||
2
externals/discord-rpc
vendored
2
externals/discord-rpc
vendored
Submodule externals/discord-rpc updated: 963aa9f3e5...20cc99aeff
@@ -339,7 +339,7 @@ void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceSt
|
||||
cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()),
|
||||
MaxMixBuffers * sizeof(s32));
|
||||
cmd.buffer_count = buffer_count;
|
||||
cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32));
|
||||
cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer.size_bytes());
|
||||
|
||||
GenerateEnd<DepopPrepareCommand>(cmd);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor
|
||||
|
||||
void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto samples{reinterpret_cast<s32*>(previous_samples)};
|
||||
auto buffer{std::span(reinterpret_cast<s32*>(depop_buffer), buffer_count)};
|
||||
auto buffer{reinterpret_cast<s32*>(depop_buffer)};
|
||||
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
if (samples[i]) {
|
||||
|
||||
@@ -419,13 +419,13 @@ protected:
|
||||
/// Workbuffers assigned to this effect
|
||||
std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)};
|
||||
/// Aux/Capture buffer info for reading
|
||||
CpuAddr send_buffer_info;
|
||||
CpuAddr send_buffer_info{};
|
||||
/// Aux/Capture buffer for reading
|
||||
CpuAddr send_buffer;
|
||||
CpuAddr send_buffer{};
|
||||
/// Aux/Capture buffer info for writing
|
||||
CpuAddr return_buffer_info;
|
||||
CpuAddr return_buffer_info{};
|
||||
/// Aux/Capture buffer for writing
|
||||
CpuAddr return_buffer;
|
||||
CpuAddr return_buffer{};
|
||||
/// Parameters of this effect
|
||||
std::array<u8, sizeof(InParameterVersion2)> parameter{};
|
||||
/// State of this effect used by the AudioRenderer across calls
|
||||
|
||||
@@ -30,10 +30,6 @@ namespace Common {
|
||||
#else
|
||||
return _udiv128(r[1], r[0], d, &remainder);
|
||||
#endif
|
||||
#else
|
||||
#ifdef __SIZEOF_INT128__
|
||||
const auto product = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
|
||||
return static_cast<u64>(product / d);
|
||||
#else
|
||||
const u64 diva = a / d;
|
||||
const u64 moda = a % d;
|
||||
@@ -41,7 +37,6 @@ namespace Common {
|
||||
const u64 modb = b % d;
|
||||
return diva * b + moda * divb + moda * modb / d;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// This function multiplies 2 u64 values and produces a u128 value;
|
||||
|
||||
@@ -75,8 +75,8 @@ NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequen
|
||||
}
|
||||
|
||||
u64 NativeClock::GetRTSC() {
|
||||
TimePoint current_time_point{};
|
||||
TimePoint new_time_point{};
|
||||
TimePoint current_time_point{};
|
||||
|
||||
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
|
||||
do {
|
||||
|
||||
@@ -451,6 +451,7 @@ add_library(core STATIC
|
||||
hle/service/hid/hidbus.h
|
||||
hle/service/hid/irs.cpp
|
||||
hle/service/hid/irs.h
|
||||
hle/service/hid/irs_ring_lifo.h
|
||||
hle/service/hid/ring_lifo.h
|
||||
hle/service/hid/xcd.cpp
|
||||
hle/service/hid/xcd.h
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hardware_properties.h"
|
||||
@@ -44,10 +42,10 @@ CoreTiming::CoreTiming()
|
||||
|
||||
CoreTiming::~CoreTiming() = default;
|
||||
|
||||
void CoreTiming::ThreadEntry(CoreTiming& instance, size_t id) {
|
||||
const std::string name = "yuzu:HostTiming_" + std::to_string(id);
|
||||
MicroProfileOnThreadCreate(name.c_str());
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
void CoreTiming::ThreadEntry(CoreTiming& instance) {
|
||||
constexpr char name[] = "yuzu:HostTiming";
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
instance.on_thread_init();
|
||||
instance.ThreadLoop();
|
||||
@@ -63,127 +61,100 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
|
||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
||||
if (is_multicore) {
|
||||
worker_threads.emplace_back(ThreadEntry, std::ref(*this), 0);
|
||||
timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::Shutdown() {
|
||||
is_paused = true;
|
||||
paused = true;
|
||||
shutting_down = true;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
event_cv.notify_all();
|
||||
wait_pause_cv.notify_all();
|
||||
for (auto& thread : worker_threads) {
|
||||
thread.join();
|
||||
pause_event.Set();
|
||||
event.Set();
|
||||
if (timer_thread) {
|
||||
timer_thread->join();
|
||||
}
|
||||
worker_threads.clear();
|
||||
pause_callbacks.clear();
|
||||
ClearPendingEvents();
|
||||
timer_thread.reset();
|
||||
has_started = false;
|
||||
}
|
||||
|
||||
void CoreTiming::Pause(bool is_paused_) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
if (is_paused_ == paused_state.load(std::memory_order_relaxed)) {
|
||||
return;
|
||||
}
|
||||
if (is_multicore) {
|
||||
is_paused = is_paused_;
|
||||
event_cv.notify_all();
|
||||
if (!is_paused_) {
|
||||
wait_pause_cv.notify_all();
|
||||
}
|
||||
}
|
||||
paused_state.store(is_paused_, std::memory_order_relaxed);
|
||||
void CoreTiming::Pause(bool is_paused) {
|
||||
paused = is_paused;
|
||||
pause_event.Set();
|
||||
|
||||
if (!is_paused_) {
|
||||
if (!is_paused) {
|
||||
pause_end_time = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
for (auto& cb : pause_callbacks) {
|
||||
cb(is_paused_);
|
||||
cb(is_paused);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::SyncPause(bool is_paused_) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
if (is_paused_ == paused_state.load(std::memory_order_relaxed)) {
|
||||
void CoreTiming::SyncPause(bool is_paused) {
|
||||
if (is_paused == paused && paused_set == paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_multicore) {
|
||||
is_paused = is_paused_;
|
||||
event_cv.notify_all();
|
||||
if (!is_paused_) {
|
||||
wait_pause_cv.notify_all();
|
||||
}
|
||||
}
|
||||
paused_state.store(is_paused_, std::memory_order_relaxed);
|
||||
if (is_multicore) {
|
||||
if (is_paused_) {
|
||||
wait_signal_cv.wait(main_lock, [this] { return pause_count == worker_threads.size(); });
|
||||
} else {
|
||||
wait_signal_cv.wait(main_lock, [this] { return pause_count == 0; });
|
||||
Pause(is_paused);
|
||||
if (timer_thread) {
|
||||
if (!is_paused) {
|
||||
pause_event.Set();
|
||||
}
|
||||
event.Set();
|
||||
while (paused_set != is_paused)
|
||||
;
|
||||
}
|
||||
|
||||
if (!is_paused_) {
|
||||
if (!is_paused) {
|
||||
pause_end_time = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
for (auto& cb : pause_callbacks) {
|
||||
cb(is_paused_);
|
||||
cb(is_paused);
|
||||
}
|
||||
}
|
||||
|
||||
bool CoreTiming::IsRunning() const {
|
||||
return !paused_state.load(std::memory_order_acquire);
|
||||
return !paused_set;
|
||||
}
|
||||
|
||||
bool CoreTiming::HasPendingEvents() const {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
return !event_queue.empty() || pending_events.load(std::memory_order_relaxed) != 0;
|
||||
return !(wait_set && event_queue.empty());
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data, bool absolute_time) {
|
||||
{
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
|
||||
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
|
||||
|
||||
event_queue.emplace_back(Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
|
||||
pending_events.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
|
||||
if (is_multicore) {
|
||||
event_cv.notify_one();
|
||||
event_queue.emplace_back(
|
||||
Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
|
||||
event.Set();
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||
std::chrono::nanoseconds resched_time,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data, bool absolute_time) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
|
||||
|
||||
event_queue.emplace_back(
|
||||
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
|
||||
pending_events.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
|
||||
if (is_multicore) {
|
||||
event_cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get() && e.user_data == user_data;
|
||||
});
|
||||
@@ -192,7 +163,6 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||
if (itr != event_queue.end()) {
|
||||
event_queue.erase(itr, event_queue.end());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
pending_events.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,12 +202,11 @@ u64 CoreTiming::GetClockTicks() const {
|
||||
}
|
||||
|
||||
void CoreTiming::ClearPendingEvents() {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
event_queue.clear();
|
||||
}
|
||||
|
||||
void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
std::scoped_lock lock{basic_lock};
|
||||
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get();
|
||||
@@ -251,28 +220,27 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
||||
}
|
||||
|
||||
void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
std::scoped_lock lock{basic_lock};
|
||||
pause_callbacks.emplace_back(std::move(callback));
|
||||
}
|
||||
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
std::scoped_lock lock{advance_lock, basic_lock};
|
||||
global_timer = GetGlobalTimeNs().count();
|
||||
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
while (!event_queue.empty() && event_queue.front().time <= global_timer) {
|
||||
Event evt = std::move(event_queue.front());
|
||||
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
event_queue.pop_back();
|
||||
|
||||
if (const auto event_type{evt.type.lock()}) {
|
||||
event_mutex.unlock();
|
||||
basic_lock.unlock();
|
||||
|
||||
const auto new_schedule_time{event_type->callback(
|
||||
evt.user_data, evt.time,
|
||||
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
|
||||
|
||||
event_mutex.lock();
|
||||
pending_events.fetch_sub(1, std::memory_order_relaxed);
|
||||
basic_lock.lock();
|
||||
|
||||
if (evt.reschedule_time != 0) {
|
||||
// If this event was scheduled into a pause, its time now is going to be way behind.
|
||||
@@ -285,9 +253,9 @@ std::optional<s64> CoreTiming::Advance() {
|
||||
const auto next_schedule_time{new_schedule_time.has_value()
|
||||
? new_schedule_time.value().count()
|
||||
: evt.reschedule_time};
|
||||
|
||||
event_queue.emplace_back(
|
||||
Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
|
||||
pending_events.fetch_add(1, std::memory_order_relaxed);
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
@@ -304,34 +272,27 @@ std::optional<s64> CoreTiming::Advance() {
|
||||
}
|
||||
|
||||
void CoreTiming::ThreadLoop() {
|
||||
const auto predicate = [this] { return !event_queue.empty() || is_paused; };
|
||||
has_started = true;
|
||||
while (!shutting_down) {
|
||||
while (!is_paused && !shutting_down) {
|
||||
while (!paused) {
|
||||
paused_set = false;
|
||||
const auto next_time = Advance();
|
||||
if (next_time) {
|
||||
if (*next_time > 0) {
|
||||
std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
event_cv.wait_for(main_lock, next_time_ns, predicate);
|
||||
event.WaitFor(next_time_ns);
|
||||
}
|
||||
} else {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
event_cv.wait(main_lock, predicate);
|
||||
wait_set = true;
|
||||
event.Wait();
|
||||
}
|
||||
wait_set = false;
|
||||
}
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
pause_count++;
|
||||
if (pause_count == worker_threads.size()) {
|
||||
clock->Pause(true);
|
||||
wait_signal_cv.notify_all();
|
||||
}
|
||||
wait_pause_cv.wait(main_lock, [this] { return !is_paused || shutting_down; });
|
||||
pause_count--;
|
||||
if (pause_count == 0) {
|
||||
clock->Pause(false);
|
||||
wait_signal_cv.notify_all();
|
||||
}
|
||||
|
||||
paused_set = true;
|
||||
clock->Pause(true);
|
||||
pause_event.Wait();
|
||||
clock->Pause(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -15,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
@@ -143,7 +143,7 @@ private:
|
||||
/// Clear all pending events. This should ONLY be done on exit.
|
||||
void ClearPendingEvents();
|
||||
|
||||
static void ThreadEntry(CoreTiming& instance, size_t id);
|
||||
static void ThreadEntry(CoreTiming& instance);
|
||||
void ThreadLoop();
|
||||
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
@@ -156,24 +156,21 @@ private:
|
||||
// accomodated by the standard adaptor class.
|
||||
std::vector<Event> event_queue;
|
||||
u64 event_fifo_id = 0;
|
||||
std::atomic<size_t> pending_events{};
|
||||
|
||||
std::shared_ptr<EventType> ev_lost;
|
||||
Common::Event event{};
|
||||
Common::Event pause_event{};
|
||||
std::mutex basic_lock;
|
||||
std::mutex advance_lock;
|
||||
std::unique_ptr<std::thread> timer_thread;
|
||||
std::atomic<bool> paused{};
|
||||
std::atomic<bool> paused_set{};
|
||||
std::atomic<bool> wait_set{};
|
||||
std::atomic<bool> shutting_down{};
|
||||
std::atomic<bool> has_started{};
|
||||
std::function<void()> on_thread_init{};
|
||||
|
||||
std::vector<std::thread> worker_threads;
|
||||
|
||||
std::condition_variable event_cv;
|
||||
std::condition_variable wait_pause_cv;
|
||||
std::condition_variable wait_signal_cv;
|
||||
mutable std::mutex event_mutex;
|
||||
|
||||
std::atomic<bool> paused_state{};
|
||||
bool is_paused{};
|
||||
bool shutting_down{};
|
||||
bool is_multicore{};
|
||||
size_t pause_count{};
|
||||
s64 pause_end_time{};
|
||||
|
||||
/// Cycle timing
|
||||
|
||||
@@ -17,6 +17,8 @@ struct KeyboardInitializeParameters {
|
||||
std::u16string sub_text;
|
||||
std::u16string guide_text;
|
||||
std::u16string initial_text;
|
||||
char16_t left_optional_symbol_key;
|
||||
char16_t right_optional_symbol_key;
|
||||
u32 max_text_length;
|
||||
u32 min_text_length;
|
||||
s32 initial_cursor_position;
|
||||
|
||||
@@ -536,6 +536,8 @@ void SoftwareKeyboard::InitializeFrontendNormalKeyboard() {
|
||||
.sub_text{std::move(sub_text)},
|
||||
.guide_text{std::move(guide_text)},
|
||||
.initial_text{initial_text},
|
||||
.left_optional_symbol_key{swkbd_config_common.left_optional_symbol_key},
|
||||
.right_optional_symbol_key{swkbd_config_common.right_optional_symbol_key},
|
||||
.max_text_length{max_text_length},
|
||||
.min_text_length{min_text_length},
|
||||
.initial_cursor_position{initial_cursor_position},
|
||||
@@ -591,6 +593,8 @@ void SoftwareKeyboard::InitializeFrontendInlineKeyboardOld() {
|
||||
.sub_text{},
|
||||
.guide_text{},
|
||||
.initial_text{current_text},
|
||||
.left_optional_symbol_key{appear_arg.left_optional_symbol_key},
|
||||
.right_optional_symbol_key{appear_arg.right_optional_symbol_key},
|
||||
.max_text_length{max_text_length},
|
||||
.min_text_length{min_text_length},
|
||||
.initial_cursor_position{initial_cursor_position},
|
||||
@@ -632,6 +636,8 @@ void SoftwareKeyboard::InitializeFrontendInlineKeyboardNew() {
|
||||
.sub_text{},
|
||||
.guide_text{},
|
||||
.initial_text{current_text},
|
||||
.left_optional_symbol_key{appear_arg.left_optional_symbol_key},
|
||||
.right_optional_symbol_key{appear_arg.right_optional_symbol_key},
|
||||
.max_text_length{max_text_length},
|
||||
.min_text_length{min_text_length},
|
||||
.initial_cursor_position{initial_cursor_position},
|
||||
|
||||
@@ -166,7 +166,7 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
|
||||
MakeProcessor<ClusteringProcessor>(parameters.camera_handle, device);
|
||||
MakeProcessorWithCoreContext<ClusteringProcessor>(parameters.camera_handle, device);
|
||||
auto& image_transfer_processor =
|
||||
GetProcessor<ClusteringProcessor>(parameters.camera_handle);
|
||||
image_transfer_processor.SetConfig(parameters.processor_config);
|
||||
|
||||
47
src/core/hle/service/hid/irs_ring_lifo.h
Normal file
47
src/core/hle/service/hid/irs_ring_lifo.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::IRS {
|
||||
|
||||
template <typename State, std::size_t max_buffer_size>
|
||||
struct Lifo {
|
||||
s64 sampling_number{};
|
||||
s64 buffer_count{};
|
||||
std::array<State, max_buffer_size> entries{};
|
||||
|
||||
const State& ReadCurrentEntry() const {
|
||||
return entries[GetBufferTail()];
|
||||
}
|
||||
|
||||
const State& ReadPreviousEntry() const {
|
||||
return entries[GetPreviousEntryIndex()];
|
||||
}
|
||||
|
||||
s64 GetBufferTail() const {
|
||||
return sampling_number % max_buffer_size;
|
||||
}
|
||||
|
||||
std::size_t GetPreviousEntryIndex() const {
|
||||
return static_cast<size_t>((GetBufferTail() + max_buffer_size - 1) % max_buffer_size);
|
||||
}
|
||||
|
||||
std::size_t GetNextEntryIndex() const {
|
||||
return static_cast<size_t>((GetBufferTail() + 1) % max_buffer_size);
|
||||
}
|
||||
|
||||
void WriteNextEntry(const State& new_state) {
|
||||
if (buffer_count < static_cast<s64>(max_buffer_size)) {
|
||||
buffer_count++;
|
||||
}
|
||||
sampling_number++;
|
||||
entries[GetBufferTail()] = new_state;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Service::IRS
|
||||
@@ -1,34 +1,265 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/service/hid/irsensor/clustering_processor.h"
|
||||
|
||||
namespace Service::IRS {
|
||||
ClusteringProcessor::ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format)
|
||||
: device(device_format) {
|
||||
ClusteringProcessor::ClusteringProcessor(Core::HID::HIDCore& hid_core_,
|
||||
Core::IrSensor::DeviceFormat& device_format,
|
||||
std::size_t npad_index)
|
||||
: device{device_format} {
|
||||
npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index);
|
||||
|
||||
device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor;
|
||||
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
|
||||
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
|
||||
SetDefaultConfig();
|
||||
|
||||
shared_memory = std::construct_at(
|
||||
reinterpret_cast<ClusteringSharedMemory*>(&device_format.state.processor_raw_data));
|
||||
|
||||
Core::HID::ControllerUpdateCallback engine_callback{
|
||||
.on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
|
||||
.is_npad_service = true,
|
||||
};
|
||||
callback_key = npad_device->SetCallback(engine_callback);
|
||||
}
|
||||
|
||||
ClusteringProcessor::~ClusteringProcessor() = default;
|
||||
ClusteringProcessor::~ClusteringProcessor() {
|
||||
npad_device->DeleteCallback(callback_key);
|
||||
};
|
||||
|
||||
void ClusteringProcessor::StartProcessor() {}
|
||||
void ClusteringProcessor::StartProcessor() {
|
||||
device.camera_status = Core::IrSensor::IrCameraStatus::Available;
|
||||
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
|
||||
}
|
||||
|
||||
void ClusteringProcessor::SuspendProcessor() {}
|
||||
|
||||
void ClusteringProcessor::StopProcessor() {}
|
||||
|
||||
void ClusteringProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
|
||||
if (type != Core::HID::ControllerTriggerType::IrSensor) {
|
||||
return;
|
||||
}
|
||||
|
||||
next_state = {};
|
||||
const auto camera_data = npad_device->GetCamera();
|
||||
auto filtered_image = camera_data.data;
|
||||
|
||||
RemoveLowIntensityData(filtered_image);
|
||||
|
||||
const auto window_start_x = static_cast<std::size_t>(current_config.window_of_interest.x);
|
||||
const auto window_start_y = static_cast<std::size_t>(current_config.window_of_interest.y);
|
||||
const auto window_end_x =
|
||||
window_start_x + static_cast<std::size_t>(current_config.window_of_interest.width);
|
||||
const auto window_end_y =
|
||||
window_start_y + static_cast<std::size_t>(current_config.window_of_interest.height);
|
||||
|
||||
for (std::size_t y = window_start_y; y < window_end_y; y++) {
|
||||
for (std::size_t x = window_start_x; x < window_end_x; x++) {
|
||||
u8 pixel = GetPixel(filtered_image, x, y);
|
||||
if (pixel == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto cluster = GetClusterProperties(filtered_image, x, y);
|
||||
if (cluster.pixel_count > current_config.pixel_count_max) {
|
||||
continue;
|
||||
}
|
||||
if (cluster.pixel_count < current_config.pixel_count_min) {
|
||||
continue;
|
||||
}
|
||||
// Cluster object limit reached
|
||||
if (next_state.object_count >= next_state.data.size()) {
|
||||
continue;
|
||||
}
|
||||
next_state.data[next_state.object_count] = cluster;
|
||||
next_state.object_count++;
|
||||
}
|
||||
}
|
||||
|
||||
next_state.sampling_number = camera_data.sample;
|
||||
next_state.timestamp = next_state.timestamp + 131;
|
||||
next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
|
||||
shared_memory->clustering_lifo.WriteNextEntry(next_state);
|
||||
|
||||
if (!IsProcessorActive()) {
|
||||
StartProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
void ClusteringProcessor::RemoveLowIntensityData(std::vector<u8>& data) {
|
||||
for (u8& pixel : data) {
|
||||
if (pixel < current_config.pixel_count_min) {
|
||||
pixel = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClusteringProcessor::ClusteringData ClusteringProcessor::GetClusterProperties(std::vector<u8>& data,
|
||||
std::size_t x,
|
||||
std::size_t y) {
|
||||
using DataPoint = Common::Point<std::size_t>;
|
||||
std::queue<DataPoint> search_points{};
|
||||
ClusteringData current_cluster = GetPixelProperties(data, x, y);
|
||||
SetPixel(data, x, y, 0);
|
||||
search_points.emplace<DataPoint>({x, y});
|
||||
|
||||
while (!search_points.empty()) {
|
||||
const auto point = search_points.front();
|
||||
search_points.pop();
|
||||
|
||||
// Avoid negative numbers
|
||||
if (point.x == 0 || point.y == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::array<DataPoint, 4> new_points{
|
||||
DataPoint{point.x - 1, point.y},
|
||||
{point.x, point.y - 1},
|
||||
{point.x + 1, point.y},
|
||||
{point.x, point.y + 1},
|
||||
};
|
||||
|
||||
for (const auto new_point : new_points) {
|
||||
if (new_point.x >= width) {
|
||||
continue;
|
||||
}
|
||||
if (new_point.y >= height) {
|
||||
continue;
|
||||
}
|
||||
if (GetPixel(data, new_point.x, new_point.y) < current_config.object_intensity_min) {
|
||||
continue;
|
||||
}
|
||||
const ClusteringData cluster = GetPixelProperties(data, new_point.x, new_point.y);
|
||||
current_cluster = MergeCluster(current_cluster, cluster);
|
||||
SetPixel(data, new_point.x, new_point.y, 0);
|
||||
search_points.emplace<DataPoint>({new_point.x, new_point.y});
|
||||
}
|
||||
}
|
||||
|
||||
return current_cluster;
|
||||
}
|
||||
|
||||
ClusteringProcessor::ClusteringData ClusteringProcessor::GetPixelProperties(
|
||||
const std::vector<u8>& data, std::size_t x, std::size_t y) const {
|
||||
return {
|
||||
.average_intensity = GetPixel(data, x, y) / 255.0f,
|
||||
.centroid =
|
||||
{
|
||||
.x = static_cast<f32>(x),
|
||||
.y = static_cast<f32>(y),
|
||||
|
||||
},
|
||||
.pixel_count = 1,
|
||||
.bound =
|
||||
{
|
||||
.x = static_cast<s16>(x),
|
||||
.y = static_cast<s16>(y),
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ClusteringProcessor::ClusteringData ClusteringProcessor::MergeCluster(
|
||||
const ClusteringData a, const ClusteringData b) const {
|
||||
const f32 a_pixel_count = static_cast<f32>(a.pixel_count);
|
||||
const f32 b_pixel_count = static_cast<f32>(b.pixel_count);
|
||||
const f32 pixel_count = a_pixel_count + b_pixel_count;
|
||||
const f32 average_intensity =
|
||||
(a.average_intensity * a_pixel_count + b.average_intensity * b_pixel_count) / pixel_count;
|
||||
const Core::IrSensor::IrsCentroid centroid = {
|
||||
.x = (a.centroid.x * a_pixel_count + b.centroid.x * b_pixel_count) / pixel_count,
|
||||
.y = (a.centroid.y * a_pixel_count + b.centroid.y * b_pixel_count) / pixel_count,
|
||||
};
|
||||
s16 bound_start_x = a.bound.x < b.bound.x ? a.bound.x : b.bound.x;
|
||||
s16 bound_start_y = a.bound.y < b.bound.y ? a.bound.y : b.bound.y;
|
||||
s16 a_bound_end_x = a.bound.x + a.bound.width;
|
||||
s16 a_bound_end_y = a.bound.y + a.bound.height;
|
||||
s16 b_bound_end_x = b.bound.x + b.bound.width;
|
||||
s16 b_bound_end_y = b.bound.y + b.bound.height;
|
||||
|
||||
const Core::IrSensor::IrsRect bound = {
|
||||
.x = bound_start_x,
|
||||
.y = bound_start_y,
|
||||
.width = a_bound_end_x > b_bound_end_x ? static_cast<s16>(a_bound_end_x - bound_start_x)
|
||||
: static_cast<s16>(b_bound_end_x - bound_start_x),
|
||||
.height = a_bound_end_y > b_bound_end_y ? static_cast<s16>(a_bound_end_y - bound_start_y)
|
||||
: static_cast<s16>(b_bound_end_y - bound_start_y),
|
||||
};
|
||||
|
||||
return {
|
||||
.average_intensity = average_intensity,
|
||||
.centroid = centroid,
|
||||
.pixel_count = static_cast<u32>(pixel_count),
|
||||
.bound = bound,
|
||||
};
|
||||
}
|
||||
|
||||
u8 ClusteringProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const {
|
||||
if ((y * width) + x > data.size()) {
|
||||
return 0;
|
||||
}
|
||||
return data[(y * width) + x];
|
||||
}
|
||||
|
||||
void ClusteringProcessor::SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value) {
|
||||
if ((y * width) + x > data.size()) {
|
||||
return;
|
||||
}
|
||||
data[(y * width) + x] = value;
|
||||
}
|
||||
|
||||
void ClusteringProcessor::SetDefaultConfig() {
|
||||
using namespace std::literals::chrono_literals;
|
||||
current_config.camera_config.exposure_time = std::chrono::microseconds(200ms).count();
|
||||
current_config.camera_config.gain = 2;
|
||||
current_config.camera_config.is_negative_used = false;
|
||||
current_config.camera_config.light_target = Core::IrSensor::CameraLightTarget::BrightLeds;
|
||||
current_config.window_of_interest = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
current_config.pixel_count_min = 3;
|
||||
current_config.pixel_count_max = static_cast<u32>(GetDataSize(format));
|
||||
current_config.is_external_light_filter_enabled = true;
|
||||
current_config.object_intensity_min = 150;
|
||||
|
||||
npad_device->SetCameraFormat(format);
|
||||
}
|
||||
|
||||
void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) {
|
||||
current_config.camera_config.exposure_time = config.camera_config.exposure_time;
|
||||
current_config.camera_config.gain = config.camera_config.gain;
|
||||
current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
|
||||
current_config.camera_config.light_target =
|
||||
static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
|
||||
current_config.window_of_interest = config.window_of_interest;
|
||||
current_config.pixel_count_min = config.pixel_count_min;
|
||||
current_config.pixel_count_max = config.pixel_count_max;
|
||||
current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled;
|
||||
current_config.object_intensity_min = config.object_intensity_min;
|
||||
|
||||
LOG_INFO(Service_IRS,
|
||||
"Processor config, exposure_time={}, gain={}, is_negative_used={}, "
|
||||
"light_target={}, window_of_interest=({}, {}, {}, {}), pixel_count_min={}, "
|
||||
"pixel_count_max={}, is_external_light_filter_enabled={}, object_intensity_min={}",
|
||||
current_config.camera_config.exposure_time, current_config.camera_config.gain,
|
||||
current_config.camera_config.is_negative_used,
|
||||
current_config.camera_config.light_target, current_config.window_of_interest.x,
|
||||
current_config.window_of_interest.y, current_config.window_of_interest.width,
|
||||
current_config.window_of_interest.height, current_config.pixel_count_min,
|
||||
current_config.pixel_count_max, current_config.is_external_light_filter_enabled,
|
||||
current_config.object_intensity_min);
|
||||
|
||||
npad_device->SetCameraFormat(format);
|
||||
}
|
||||
|
||||
} // namespace Service::IRS
|
||||
|
||||
@@ -5,12 +5,19 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hid/irs_types.h"
|
||||
#include "core/hle/service/hid/irs_ring_lifo.h"
|
||||
#include "core/hle/service/hid/irsensor/processor_base.h"
|
||||
|
||||
namespace Core::HID {
|
||||
class EmulatedController;
|
||||
} // namespace Core::HID
|
||||
|
||||
namespace Service::IRS {
|
||||
class ClusteringProcessor final : public ProcessorBase {
|
||||
public:
|
||||
explicit ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format);
|
||||
explicit ClusteringProcessor(Core::HID::HIDCore& hid_core_,
|
||||
Core::IrSensor::DeviceFormat& device_format,
|
||||
std::size_t npad_index);
|
||||
~ClusteringProcessor() override;
|
||||
|
||||
// Called when the processor is initialized
|
||||
@@ -26,6 +33,10 @@ public:
|
||||
void SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config);
|
||||
|
||||
private:
|
||||
static constexpr auto format = Core::IrSensor::ImageTransferProcessorFormat::Size320x240;
|
||||
static constexpr std::size_t width = 320;
|
||||
static constexpr std::size_t height = 240;
|
||||
|
||||
// This is nn::irsensor::ClusteringProcessorConfig
|
||||
struct ClusteringProcessorConfig {
|
||||
Core::IrSensor::CameraConfig camera_config;
|
||||
@@ -68,7 +79,32 @@ private:
|
||||
static_assert(sizeof(ClusteringProcessorState) == 0x198,
|
||||
"ClusteringProcessorState is an invalid size");
|
||||
|
||||
struct ClusteringSharedMemory {
|
||||
Service::IRS::Lifo<ClusteringProcessorState, 6> clustering_lifo;
|
||||
static_assert(sizeof(clustering_lifo) == 0x9A0, "clustering_lifo is an invalid size");
|
||||
INSERT_PADDING_WORDS(0x11F);
|
||||
};
|
||||
static_assert(sizeof(ClusteringSharedMemory) == 0xE20,
|
||||
"ClusteringSharedMemory is an invalid size");
|
||||
|
||||
void OnControllerUpdate(Core::HID::ControllerTriggerType type);
|
||||
void RemoveLowIntensityData(std::vector<u8>& data);
|
||||
ClusteringData GetClusterProperties(std::vector<u8>& data, std::size_t x, std::size_t y);
|
||||
ClusteringData GetPixelProperties(const std::vector<u8>& data, std::size_t x,
|
||||
std::size_t y) const;
|
||||
ClusteringData MergeCluster(const ClusteringData a, const ClusteringData b) const;
|
||||
u8 GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const;
|
||||
void SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value);
|
||||
|
||||
// Sets config parameters of the camera
|
||||
void SetDefaultConfig();
|
||||
|
||||
ClusteringSharedMemory* shared_memory = nullptr;
|
||||
ClusteringProcessorState next_state{};
|
||||
|
||||
ClusteringProcessorConfig current_config{};
|
||||
Core::IrSensor::DeviceFormat& device;
|
||||
Core::HID::EmulatedController* npad_device;
|
||||
int callback_key{};
|
||||
};
|
||||
} // namespace Service::IRS
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
@@ -23,15 +22,14 @@ std::array<s64, 5> delays{};
|
||||
|
||||
std::bitset<CB_IDS.size()> callbacks_ran_flags;
|
||||
u64 expected_callback = 0;
|
||||
std::mutex control_mutex;
|
||||
|
||||
template <unsigned int IDX>
|
||||
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) {
|
||||
std::unique_lock<std::mutex> lk(control_mutex);
|
||||
static_assert(IDX < CB_IDS.size(), "IDX out of range");
|
||||
callbacks_ran_flags.set(IDX);
|
||||
REQUIRE(CB_IDS[IDX] == user_data);
|
||||
REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
|
||||
delays[IDX] = ns_late.count();
|
||||
++expected_callback;
|
||||
return std::nullopt;
|
||||
|
||||
@@ -31,8 +31,7 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
|
||||
VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
CommandDataContainer next;
|
||||
state.queue.Pop(next, stop_token);
|
||||
CommandDataContainer next = state.queue.PopWait(stop_token);
|
||||
if (stop_token.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
|
||||
#include "common/bounded_threadsafe_queue.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "video_core/framebuffer_config.h"
|
||||
|
||||
namespace Tegra {
|
||||
@@ -96,7 +96,7 @@ struct CommandDataContainer {
|
||||
|
||||
/// Struct used to synchronize the GPU thread
|
||||
struct SynchState final {
|
||||
using CommandQueue = Common::MPSCQueue<CommandDataContainer>;
|
||||
using CommandQueue = Common::MPSCQueue<CommandDataContainer, true>;
|
||||
std::mutex write_lock;
|
||||
CommandQueue queue;
|
||||
u64 last_fence{};
|
||||
|
||||
@@ -213,9 +213,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
|
||||
ui->button_ok_num,
|
||||
},
|
||||
{
|
||||
nullptr,
|
||||
ui->button_left_optional_num,
|
||||
ui->button_0_num,
|
||||
nullptr,
|
||||
ui->button_right_optional_num,
|
||||
ui->button_ok_num,
|
||||
},
|
||||
}};
|
||||
@@ -330,7 +330,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
|
||||
ui->button_7_num,
|
||||
ui->button_8_num,
|
||||
ui->button_9_num,
|
||||
ui->button_left_optional_num,
|
||||
ui->button_0_num,
|
||||
ui->button_right_optional_num,
|
||||
};
|
||||
|
||||
SetupMouseHover();
|
||||
@@ -342,6 +344,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
|
||||
ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text));
|
||||
ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text));
|
||||
|
||||
ui->button_left_optional_num->setText(QChar{initialize_parameters.left_optional_symbol_key});
|
||||
ui->button_right_optional_num->setText(QChar{initialize_parameters.right_optional_symbol_key});
|
||||
|
||||
current_text = initialize_parameters.initial_text;
|
||||
cursor_position = initialize_parameters.initial_cursor_position;
|
||||
|
||||
@@ -932,6 +937,15 @@ void QtSoftwareKeyboardDialog::DisableKeyboardButtons() {
|
||||
button->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
const auto enable_left_optional = initialize_parameters.left_optional_symbol_key != '\0';
|
||||
const auto enable_right_optional = initialize_parameters.right_optional_symbol_key != '\0';
|
||||
|
||||
ui->button_left_optional_num->setEnabled(enable_left_optional);
|
||||
ui->button_left_optional_num->setVisible(enable_left_optional);
|
||||
|
||||
ui->button_right_optional_num->setEnabled(enable_right_optional);
|
||||
ui->button_right_optional_num->setVisible(enable_right_optional);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1019,7 +1033,10 @@ bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) {
|
||||
}
|
||||
|
||||
if (bottom_osk_index == BottomOSKIndex::NumberPad &&
|
||||
std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) {
|
||||
std::any_of(input_text.begin(), input_text.end(), [this](QChar c) {
|
||||
return !c.isDigit() && c != QChar{initialize_parameters.left_optional_symbol_key} &&
|
||||
c != QChar{initialize_parameters.right_optional_symbol_key};
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1384,6 +1401,10 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
|
||||
}
|
||||
};
|
||||
|
||||
// Store the initial row and column.
|
||||
const auto initial_row = row;
|
||||
const auto initial_column = column;
|
||||
|
||||
switch (bottom_osk_index) {
|
||||
case BottomOSKIndex::LowerCase:
|
||||
case BottomOSKIndex::UpperCase: {
|
||||
@@ -1394,6 +1415,11 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
|
||||
auto* curr_button = keyboard_buttons[index][row][column];
|
||||
|
||||
while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
|
||||
// If we returned back to where we started from, break the loop.
|
||||
if (row == initial_row && column == initial_column) {
|
||||
break;
|
||||
}
|
||||
|
||||
move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL);
|
||||
curr_button = keyboard_buttons[index][row][column];
|
||||
}
|
||||
@@ -1408,6 +1434,11 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
|
||||
auto* curr_button = numberpad_buttons[row][column];
|
||||
|
||||
while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
|
||||
// If we returned back to where we started from, break the loop.
|
||||
if (row == initial_row && column == initial_column) {
|
||||
break;
|
||||
}
|
||||
|
||||
move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD);
|
||||
curr_button = numberpad_buttons[row][column];
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ private:
|
||||
std::array<std::array<QPushButton*, NUM_COLUMNS_NUMPAD>, NUM_ROWS_NUMPAD> numberpad_buttons;
|
||||
|
||||
// Contains a set of all buttons used in keyboard_buttons and numberpad_buttons.
|
||||
std::array<QPushButton*, 110> all_buttons;
|
||||
std::array<QPushButton*, 112> all_buttons;
|
||||
|
||||
std::size_t row{0};
|
||||
std::size_t column{0};
|
||||
|
||||
@@ -3298,6 +3298,24 @@ p, li { white-space: pre-wrap; }
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QPushButton" name="button_left_optional_num">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>28</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QPushButton" name="button_0_num">
|
||||
<property name="sizePolicy">
|
||||
@@ -3316,6 +3334,24 @@ p, li { white-space: pre-wrap; }
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="4">
|
||||
<widget class="QPushButton" name="button_right_optional_num">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>28</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QPushButton" name="button_3_num">
|
||||
<property name="sizePolicy">
|
||||
@@ -3494,7 +3530,9 @@ p, li { white-space: pre-wrap; }
|
||||
<tabstop>button_7_num</tabstop>
|
||||
<tabstop>button_8_num</tabstop>
|
||||
<tabstop>button_9_num</tabstop>
|
||||
<tabstop>button_left_optional_num</tabstop>
|
||||
<tabstop>button_0_num</tabstop>
|
||||
<tabstop>button_right_optional_num</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../../dist/icons/overlay/overlay.qrc"/>
|
||||
|
||||
@@ -804,6 +804,7 @@ void GRenderWindow::TouchEndEvent() {
|
||||
}
|
||||
|
||||
void GRenderWindow::InitializeCamera() {
|
||||
constexpr auto camera_update_ms = std::chrono::milliseconds{50}; // (50ms, 20Hz)
|
||||
if (!Settings::values.enable_ir_sensor) {
|
||||
return;
|
||||
}
|
||||
@@ -837,7 +838,7 @@ void GRenderWindow::InitializeCamera() {
|
||||
camera_timer = std::make_unique<QTimer>();
|
||||
connect(camera_timer.get(), &QTimer::timeout, [this] { RequestCameraCapture(); });
|
||||
// This timer should be dependent of camera resolution 5ms for every 100 pixels
|
||||
camera_timer->start(100);
|
||||
camera_timer->start(camera_update_ms);
|
||||
}
|
||||
|
||||
void GRenderWindow::FinalizeCamera() {
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h> // for chdir
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
#include <csignal>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
|
||||
#include "applets/qt_controller.h"
|
||||
@@ -259,6 +263,10 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||
config{std::make_unique<Config>(*system)},
|
||||
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
|
||||
provider{std::make_unique<FileSys::ManualContentProvider>()} {
|
||||
#ifdef __linux__
|
||||
SetupSigInterrupts();
|
||||
#endif
|
||||
|
||||
Common::Log::Initialize();
|
||||
LoadTranslation();
|
||||
|
||||
@@ -378,6 +386,8 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||
SDL_EnableScreenSaver();
|
||||
#endif
|
||||
|
||||
SetupPrepareForSleep();
|
||||
|
||||
Common::Log::Start();
|
||||
|
||||
QStringList args = QApplication::arguments();
|
||||
@@ -462,7 +472,13 @@ GMainWindow::~GMainWindow() {
|
||||
if (render_window->parent() == nullptr) {
|
||||
delete render_window;
|
||||
}
|
||||
|
||||
system->GetRoomNetwork().Shutdown();
|
||||
|
||||
#ifdef __linux__
|
||||
::close(sig_interrupt_fds[0]);
|
||||
::close(sig_interrupt_fds[1]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GMainWindow::RegisterMetaTypes() {
|
||||
@@ -1060,12 +1076,26 @@ void GMainWindow::InitializeHotkeys() {
|
||||
[] { Settings::values.audio_muted = !Settings::values.audio_muted; });
|
||||
connect_shortcut(QStringLiteral("Audio Volume Down"), [] {
|
||||
const auto current_volume = static_cast<int>(Settings::values.volume.GetValue());
|
||||
const auto new_volume = std::max(current_volume - 5, 0);
|
||||
int step = 5;
|
||||
if (current_volume <= 30) {
|
||||
step = 2;
|
||||
}
|
||||
if (current_volume <= 6) {
|
||||
step = 1;
|
||||
}
|
||||
const auto new_volume = std::max(current_volume - step, 0);
|
||||
Settings::values.volume.SetValue(static_cast<u8>(new_volume));
|
||||
});
|
||||
connect_shortcut(QStringLiteral("Audio Volume Up"), [] {
|
||||
const auto current_volume = static_cast<int>(Settings::values.volume.GetValue());
|
||||
const auto new_volume = std::min(current_volume + 5, 100);
|
||||
int step = 5;
|
||||
if (current_volume < 30) {
|
||||
step = 2;
|
||||
}
|
||||
if (current_volume < 6) {
|
||||
step = 1;
|
||||
}
|
||||
const auto new_volume = std::min(current_volume + step, 100);
|
||||
Settings::values.volume.SetValue(static_cast<u8>(new_volume));
|
||||
});
|
||||
connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
|
||||
@@ -1313,6 +1343,43 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::SetupPrepareForSleep() {
|
||||
#ifdef __linux__
|
||||
auto bus = QDBusConnection::systemBus();
|
||||
if (bus.isConnected()) {
|
||||
const bool success = bus.connect(
|
||||
QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"),
|
||||
QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("PrepareForSleep"),
|
||||
QStringLiteral("b"), this, SLOT(OnPrepareForSleep(bool)));
|
||||
|
||||
if (!success) {
|
||||
LOG_WARNING(Frontend, "Couldn't register PrepareForSleep signal");
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Frontend, "QDBusConnection system bus is not connected");
|
||||
}
|
||||
#endif // __linux__
|
||||
}
|
||||
|
||||
void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
|
||||
if (emu_thread == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prepare_sleep) {
|
||||
if (emu_thread->IsRunning()) {
|
||||
auto_paused = true;
|
||||
OnPauseGame();
|
||||
}
|
||||
} else {
|
||||
if (!emu_thread->IsRunning() && auto_paused) {
|
||||
auto_paused = false;
|
||||
RequestGameResume();
|
||||
OnStartGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
|
||||
if (!QDBusConnection::sessionBus().isConnected()) {
|
||||
@@ -1352,6 +1419,52 @@ static void ReleaseWakeLockLinux(QDBusObjectPath lock) {
|
||||
QString::fromLatin1("org.freedesktop.portal.Request"));
|
||||
unlocker.call(QString::fromLatin1("Close"));
|
||||
}
|
||||
|
||||
std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0};
|
||||
|
||||
void GMainWindow::SetupSigInterrupts() {
|
||||
if (sig_interrupt_fds[2] == 1) {
|
||||
return;
|
||||
}
|
||||
socketpair(AF_UNIX, SOCK_STREAM, 0, sig_interrupt_fds.data());
|
||||
sig_interrupt_fds[2] = 1;
|
||||
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = &GMainWindow::HandleSigInterrupt;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESETHAND;
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
|
||||
sig_interrupt_notifier = new QSocketNotifier(sig_interrupt_fds[1], QSocketNotifier::Read, this);
|
||||
connect(sig_interrupt_notifier, &QSocketNotifier::activated, this,
|
||||
&GMainWindow::OnSigInterruptNotifierActivated);
|
||||
connect(this, &GMainWindow::SigInterrupt, this, &GMainWindow::close);
|
||||
}
|
||||
|
||||
void GMainWindow::HandleSigInterrupt(int sig) {
|
||||
if (sig == SIGINT) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Calling into Qt directly from a signal handler is not safe,
|
||||
// so wake up a QSocketNotifier with this hacky write call instead.
|
||||
char a = 1;
|
||||
int ret = write(sig_interrupt_fds[0], &a, sizeof(a));
|
||||
(void)ret;
|
||||
}
|
||||
|
||||
void GMainWindow::OnSigInterruptNotifierActivated() {
|
||||
sig_interrupt_notifier->setEnabled(false);
|
||||
|
||||
char a;
|
||||
int ret = read(sig_interrupt_fds[1], &a, sizeof(a));
|
||||
(void)ret;
|
||||
|
||||
sig_interrupt_notifier->setEnabled(true);
|
||||
|
||||
emit SigInterrupt();
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
void GMainWindow::PreventOSSleep() {
|
||||
|
||||
@@ -163,6 +163,8 @@ signals:
|
||||
void WebBrowserExtractOfflineRomFS();
|
||||
void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url);
|
||||
|
||||
void SigInterrupt();
|
||||
|
||||
public slots:
|
||||
void OnLoadComplete();
|
||||
void OnExecuteProgram(std::size_t program_index);
|
||||
@@ -202,7 +204,7 @@ private:
|
||||
void ConnectMenuEvents();
|
||||
void UpdateMenuState();
|
||||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
void SetupPrepareForSleep();
|
||||
|
||||
void PreventOSSleep();
|
||||
void AllowOSSleep();
|
||||
@@ -251,12 +253,19 @@ private:
|
||||
void RequestGameResume();
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
#ifdef __linux__
|
||||
void SetupSigInterrupts();
|
||||
static void HandleSigInterrupt(int);
|
||||
void OnSigInterruptNotifierActivated();
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
void OnStartGame();
|
||||
void OnRestartGame();
|
||||
void OnPauseGame();
|
||||
void OnPauseContinueGame();
|
||||
void OnStopGame();
|
||||
void OnPrepareForSleep(bool prepare_sleep);
|
||||
void OnMenuReportCompatibility();
|
||||
void OnOpenModsPage();
|
||||
void OnOpenQuickstartGuide();
|
||||
@@ -347,6 +356,8 @@ private:
|
||||
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
|
||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
|
||||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
|
||||
GRenderWindow* render_window;
|
||||
GameList* game_list;
|
||||
LoadingScreen* loading_screen;
|
||||
@@ -419,6 +430,9 @@ private:
|
||||
bool is_tas_recording_dialog_active{};
|
||||
|
||||
#ifdef __linux__
|
||||
QSocketNotifier* sig_interrupt_notifier;
|
||||
static std::array<int, 3> sig_interrupt_fds;
|
||||
|
||||
QDBusObjectPath wake_lock{};
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user