Compare commits

...

30 Commits

Author SHA1 Message Date
Liam
73a87edc45 audio_core: fix -Wuninitialized when compiling with ASan 2022-07-29 23:30:01 -04:00
Narr the Reg
250c3d555e Merge pull request #8665 from liamwhite/github-is-dumb
ci: use dep5 for GitHub issue template files
2022-07-29 13:14:31 -05:00
Liam
f490c15b0f ci: use dep5 for GitHub issue template files 2022-07-28 19:57:27 -04:00
bunnei
1dc4ff0974 Update bug-report-feature-request.md 2022-07-28 16:06:12 -07:00
bunnei
34f75023b2 Update config.yml 2022-07-28 16:00:20 -07:00
bunnei
880006c5ca Merge pull request #8656 from german77/audio-step
yuzu: Add incremental steps to volume hotkeys
2022-07-28 15:41:37 -07:00
bunnei
52dd68cfff Merge pull request #8349 from yuzu-emu/revert-8256-ci-vs-2022
Revert "ci: Enable building with Visual Studio 2022"
2022-07-28 12:50:07 -07:00
liamwhite
c2895df336 Merge pull request #8659 from lat9nq/drpc-yuzu
externals: Use yuzu-emu/discord-rpc to provide Discord integration
2022-07-28 15:24:36 -04:00
lat9nq
58bc6125d7 externals: Use yuzu-emu/discord-rpc to provide Discord integration
Our fork includes a commit to disable clang-format, preventing the dirty
flag from being set when building yuzu on CI.
2022-07-28 12:57:05 -04:00
liamwhite
e5615c4704 Merge pull request #8657 from Kelebek1/depop
[Audio] Avoid an out-of-bounds span write in depop prepare
2022-07-28 11:02:41 -04:00
liamwhite
1e3afd5d80 Merge pull request #8642 from lat9nq/appimg-more-control
ci/linux: Exclude libwayland-client from AppImage
2022-07-28 11:02:23 -04:00
liamwhite
5c332b90c7 Merge pull request #8641 from Docteh/vcpkg_dll
build: Ship vcpkg dlls with MSVC pr-verify builds
2022-07-27 20:04:22 -04:00
Maide
2e46110379 Revert Coretiming PRs 8531 and 7454 (#8591) 2022-07-27 19:47:06 -04:00
snek
b7642cff36 implement pause on system suspend (#8585) 2022-07-27 19:46:03 -04:00
Kelebek1
6c47b43a60 Avoid depop out of bounds 2022-07-27 23:17:18 +01:00
liamwhite
ea861cc1c4 Merge pull request #8542 from Morph1984/gpu-use-old-q
gpu_thread: Use the previous MPSCQueue implementation
2022-07-27 15:55:28 -04:00
liamwhite
64fd9f41a7 Merge pull request #8636 from german77/irs_cluster_release
service: irs: Implement clustering processor
2022-07-27 15:54:28 -04:00
liamwhite
a761d020c6 Merge pull request #8633 from Morph1984/optional-keys
applet/swkbd: Implement optional symbol keys
2022-07-27 15:53:32 -04:00
snek
9479940a1f Merge pull request #8592 from devsnek/sig-handlers
exit gracefully on sigint/sigterm
2022-07-27 15:51:26 -04:00
liamwhite
1779795d81 Merge pull request #8338 from Tachi107/spdx-fixes
chore: make yuzu REUSE compliant
2022-07-27 13:00:59 -04:00
Narr the Reg
2fdefd7064 yuzu: Add incremental steps to volume hotkeys 2022-07-27 11:34:46 -05:00
Kyle Kienapfel
81f66eec0c build: Ship vcpkg dlls with MSVC pr-verify builds
With our recent switchover from conan to vcpkg, we're shipping a few
more dll files, these need to be in the full zip.

    cp .\build\bin\*.dll .\artifacts\

also tacking on the fix where we're shipping scm_rev.cpp accidentally
2022-07-26 22:57:23 -07:00
lat9nq
5e27d37edc ci/linux: Delete libwayland-client from AppDir
This library causes issues in Vulkan driver detection.
libQt5MultimediaGstTools's dependencies seem to be the issue.
2022-07-26 02:25:39 -04:00
Narr the Reg
ceb70b2139 Address comments 2022-07-25 11:17:48 -05:00
german77
21b1e9c21a fix compiler errors 2022-07-24 16:39:32 -05:00
german77
3ac4f3a252 service: irs: Implement clustering processor 2022-07-24 15:01:43 -05:00
Morph
d7d09355e7 qt_software_keyboard: Fix infinite loop when moving between buttons
There was a bug where, when using the numeric keyboard, moving between buttons resulted in an infinite loop, resulting in a stuck state.
This was due to prev_button being the only one enabled in that row or column, causing the condition in the while loop to always be true.
To fix this, detect whether we have returned to that initial row/column and break out of the loop.
2022-07-24 07:27:41 -04:00
Morph
bee823db3a applet/swkbd: Implement optional symbol keys
These are only used in the numeric keyboard, and correspond to the keys to the left and right of the "0" key on the numeric keyboard.
2022-07-24 07:21:02 -04:00
bunnei
3f37e228a3 Revert "ci: Enable building with Visual Studio 2022" 2022-07-22 17:59:12 -07:00
Morph
fa09f7aa6c gpu_thread: Use the previous MPSCQueue implementation
The bounded MPSCQueue implementation causes crashes in Fire Emblem Three Houses, use the previous implementation for now.
2022-07-06 01:33:17 -04:00
33 changed files with 632 additions and 159 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -50,7 +50,7 @@ stages:
timeoutInMinutes: 120
displayName: 'msvc'
pool:
vmImage: windows-2022
vmImage: windows-2019
steps:
- template: ./templates/sync-source.yml
parameters:

View File

@@ -15,7 +15,7 @@ stages:
timeoutInMinutes: 120
displayName: 'windows-msvc'
pool:
vmImage: windows-2022
vmImage: windows-2019
steps:
- template: ./templates/sync-source.yml
parameters:

View File

@@ -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:

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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]) {

View File

@@ -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

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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},

View File

@@ -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);

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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{};

View File

@@ -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];
}

View File

@@ -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};

View File

@@ -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"/>

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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