Merge branch 'master' into patch-3

This commit is contained in:
qurious-pixel 2026-02-14 22:13:40 -08:00 committed by GitHub
commit 0323397dee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 721 additions and 193 deletions

View file

@ -134,7 +134,7 @@ jobs:
runs-on: macos-14
env:
CCACHE_DIR: /tmp/ccache_dir
QT_VER: '6.10.1'
QT_VER: '6.10.2'
QT_VER_MAIN: '6'
LLVM_COMPILER_VER: '21'
RELEASE_MESSAGE: ../GitHubReleaseMessage.txt
@ -213,9 +213,9 @@ jobs:
env:
COMPILER: msvc
QT_VER_MAIN: '6'
QT_VER: '6.10.1'
QT_VER: '6.10.2'
QT_VER_MSVC: 'msvc2022'
QT_DATE: '202511161843'
QT_DATE: '202601261212'
LLVM_VER: '19.1.7'
VULKAN_VER: '1.3.268.0'
VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5'

@ -1 +1 @@
Subproject commit 4e3f57d50f552841550a36eabbb3fbcecacb7750
Subproject commit 02f2b4f4699f0ef9111a6534f093b53732df4452

View file

@ -20,26 +20,26 @@ The following tools are required to build RPCS3 on Windows 10 or later:
with standalone **CMake** tool.
- [Python 3.6+](https://www.python.org/downloads/) (add to PATH)
- [Qt 6.10.1](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt)
- [Qt 6.10.2](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt)
- [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0.
The `sln` solution available only on **Visual Studio** is the preferred building solution. It easily allows to build the **RPCS3** application in `Release` and `Debug` mode.
In order to build **RPCS3** with the `sln` solution (with **Visual Studio**), **Qt** libs need to be detected. To detect the libs:
- add and set the `QTDIR` environment variable, e.g. `<QtInstallFolder>\6.10.1\msvc2022_64\`
- add and set the `QTDIR` environment variable, e.g. `<QtInstallFolder>\6.10.2\msvc2022_64\`
- or use the [Visual Studio Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022)
**NOTE:** If you have issues with the **Visual Studio Qt Plugin**, you may want to uninstall it and install the [Legacy Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.LEGACYQtVisualStudioTools2022) instead.
In order to build **RPCS3** with the `CMake` solution (with both **Visual Studio** and standalone **CMake** tool):
- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `<QtInstallFolder>\6.10.1\msvc2022_64\`
- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `<QtInstallFolder>\6.10.2\msvc2022_64\`
### Linux
These are the essentials tools to build RPCS3 on Linux. Some of them can be installed through your favorite package manager:
- Clang 17+ or GCC 13+
- [CMake 3.28.0+](https://www.cmake.org/download/)
- [Qt 6.10.1](https://www.qt.io/download-qt-installer)
- [Qt 6.10.2](https://www.qt.io/download-qt-installer)
- [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0.
- [SDL3](https://github.com/libsdl-org/SDL/releases) (for the FAudio backend)
@ -123,7 +123,7 @@ Start **Visual Studio**, click on `Open a project or solution` and select the `r
##### Configuring the Qt Plugin (if used)
1) go to `Extensions->Qt VS Tools->Qt Versions`
2) add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\6.10.1\msvc2022_64`, version will fill in automatically
2) add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\6.10.2\msvc2022_64`, version will fill in automatically
3) go to `Extensions->Qt VS Tools->Options->Legacy Project Format`. (Only available in the **Legacy Qt Plugin**)
4) set `Build: Run pre-build setup` to `true`. (Only available in the **Legacy Qt Plugin**)

View file

@ -688,6 +688,30 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
mem = std::make_unique<MemoryManager1>(std::move(symbols_cement));
}
std::vector<std::string> attributes;
#if defined(ARCH_ARM64)
if (utils::has_sha3())
attributes.push_back("+sha3");
else
attributes.push_back("-sha3");
if (utils::has_dotprod())
attributes.push_back("+dotprod");
else
attributes.push_back("-dotprod");
if (utils::has_sve())
attributes.push_back("+sve");
else
attributes.push_back("-sve");
if (utils::has_sve2())
attributes.push_back("+sve2");
else
attributes.push_back("-sve2");
#endif
{
m_engine.reset(llvm::EngineBuilder(std::move(null_mod))
.setErrorStr(&result)
@ -699,6 +723,7 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
//.setCodeModel(llvm::CodeModel::Large)
#endif
.setRelocationModel(llvm::Reloc::Model::PIC_)
.setMAttrs(attributes)
.setMCPU(m_cpu)
.create());
}

View file

@ -634,7 +634,7 @@ u32 microphone_device::capture_audio()
if (ALCenum err = alcGetError(micdevice.device); err != ALC_NO_ERROR)
{
cellMic.error("Error getting number of captured samples of device '%s' (error=%s)", micdevice.name, fmt::alc_error{micdevice.device, err});
return CELL_MICIN_ERROR_FATAL;
return 0;
}
num_samples = std::min<u32>(num_samples, samples_in);

View file

@ -238,7 +238,7 @@ public:
if (over_size > Size)
{
m_tail += (over_size - Size);
if (m_tail > Size)
if (m_tail >= Size)
m_tail -= Size;
m_used = Size;

View file

@ -556,6 +556,8 @@ usb_handler_thread::usb_handler_thread()
switch (g_cfg.audio.microphone_type)
{
case microphone_handler::null:
break;
case microphone_handler::standard:
usb_devices.push_back(std::make_shared<usb_device_mic>(0, get_new_location(), MicType::Logitech));
break;

View file

@ -33,12 +33,12 @@ void MouseHandlerBase::save(utils::serial& ar)
bool MouseHandlerBase::is_time_for_update(double elapsed_time_ms)
{
steady_clock::time_point now = steady_clock::now();
const double elapsed_ms = (now - last_update).count() / 1'000'000.;
const steady_clock::time_point now = steady_clock::now();
const double elapsed_ms = (now - m_last_update).count() / 1'000'000.;
if (elapsed_ms > elapsed_time_ms)
{
last_update = now;
m_last_update = now;
return true;
}
return false;

View file

@ -128,7 +128,7 @@ class MouseHandlerBase
protected:
MouseInfo m_info{};
std::vector<Mouse> m_mice;
steady_clock::time_point last_update{};
steady_clock::time_point m_last_update{};
bool is_time_for_update(double elapsed_time_ms = 10.0); // 4-10 ms, let's use 10 for now

View file

@ -46,7 +46,7 @@ error_code generic_async_transaction_context::wait_for_completion()
return *result;
}
completion_cond.wait(lock);
completion_cond.wait(lock, [this] { return result.has_value(); });
return *result;
}

View file

@ -951,13 +951,16 @@ namespace np
{
thread_base::set_name("NP Trans Worker");
auto res = trans_ctx->wake_cond.wait_for(lock, std::chrono::microseconds(trans_ctx->timeout));
bool has_value = trans_ctx->wake_cond.wait_for(lock, std::chrono::microseconds(trans_ctx->timeout), [&]
{
return trans_ctx->result.has_value();
});
{
std::lock_guard lock_threads(this->mutex_async_transactions);
this->async_transactions.erase(req_id);
}
if (res == std::cv_status::timeout)
if (!has_value)
{
trans_ctx->result = SCE_NP_COMMUNITY_ERROR_TIMEOUT;
return;

View file

@ -1467,7 +1467,7 @@ namespace rpcn
return error;
}
bool rpcn_client::add_friend(const std::string& friend_username)
std::optional<ErrorType> rpcn_client::add_friend(const std::string& friend_username)
{
std::vector<u8> data;
std::copy(friend_username.begin(), friend_username.end(), std::back_inserter(data));
@ -1478,19 +1478,18 @@ namespace rpcn
std::vector<u8> packet_data;
if (!forge_send_reply(CommandType::AddFriend, req_id, data, packet_data))
{
return false;
return std::nullopt;
}
vec_stream reply(packet_data);
auto error = static_cast<ErrorType>(reply.get<u8>());
const auto error = static_cast<ErrorType>(reply.get<u8>());
if (error != rpcn::ErrorType::NoError)
{
return false;
}
if (error == ErrorType::NoError)
rpcn_log.success("add_friend(\"%s\") succeeded", friend_username);
else
rpcn_log.error("add_friend(\"%s\") failed with error: %s", error);
rpcn_log.success("You have successfully added \"%s\" as a friend", friend_username);
return true;
return error;
}
bool rpcn_client::remove_friend(const std::string& friend_username)

View file

@ -293,7 +293,7 @@ namespace rpcn
ErrorType send_reset_token(std::string_view npid, std::string_view email);
ErrorType reset_password(std::string_view npid, std::string_view token, std::string_view password);
ErrorType delete_account();
bool add_friend(const std::string& friend_username);
std::optional<ErrorType> add_friend(const std::string& friend_username);
bool remove_friend(const std::string& friend_username);
u32 get_num_friends();

View file

@ -1351,7 +1351,7 @@ void GLGSRender::notify_tile_unbound(u32 tile)
}
}
bool GLGSRender::release_GCM_label(u32 address, u32 args)
bool GLGSRender::release_GCM_label(u32 type, u32 address, u32 args)
{
if (!backend_config.supports_host_gpu_labels)
{
@ -1360,7 +1360,7 @@ bool GLGSRender::release_GCM_label(u32 address, u32 args)
auto host_ctx = ensure(m_host_dma_ctrl->host_ctx());
if (host_ctx->texture_loads_completed())
if (type == NV4097_TEXTURE_READ_SEMAPHORE_RELEASE && host_ctx->texture_loads_completed())
{
// We're about to poll waiting for GPU state, ensure the context is still valid.
gl::check_state();

View file

@ -206,7 +206,7 @@ public:
void discard_occlusion_query(rsx::reports::occlusion_query_info* query) override;
// DMA
bool release_GCM_label(u32 address, u32 data) override;
bool release_GCM_label(u32 type, u32 address, u32 data) override;
void enqueue_host_context_write(u32 offset, u32 size, const void* data);
void on_guest_texture_read();

View file

@ -4,6 +4,7 @@
#include "Emu/RSX/RSXThread.h"
#include "Emu/RSX/Core/RSXReservationLock.hpp"
#include "Emu/RSX/Common/tiled_dma_copy.hpp"
#include "Emu/RSX/Host/MM.h"
#include "context_accessors.define.h"
@ -581,9 +582,11 @@ namespace rsx
const u16 out_h = REGS(ctx)->blit_engine_output_height();
// Lock here. RSX cannot execute any locking operations from this point, including ZCULL read barriers
const u32 read_length = src.pitch * src.height;
const u32 write_length = dst.pitch * dst.clip_height;
auto res = ::rsx::reservation_lock<true>(
dst.rsx_address, dst.pitch * dst.clip_height,
src.rsx_address, src.pitch * src.height);
dst.rsx_address, write_length,
src.rsx_address, read_length);
if (!g_cfg.video.force_cpu_blit_processing &&
(dst.dma == CELL_GCM_CONTEXT_DMA_MEMORY_FRAME_BUFFER || src.dma == CELL_GCM_CONTEXT_DMA_MEMORY_FRAME_BUFFER) &&
@ -593,6 +596,14 @@ namespace rsx
return;
}
// Conservative MM flush
rsx::simple_array<utils::address_range64> flush_mm_ranges =
{
utils::address_range64::start_length(reinterpret_cast<u64>(dst.pixels), write_length),
utils::address_range64::start_length(reinterpret_cast<u64>(src.pixels), read_length)
};
rsx::mm_flush(flush_mm_ranges);
std::vector<u8> mirror_tmp;
bool src_is_temp = false;
@ -619,7 +630,7 @@ namespace rsx
const bool interpolate = in_inter == blit_engine::transfer_interpolator::foh;
auto real_dst = dst.pixels;
const auto tiled_region = RSX(ctx)->get_tiled_memory_region(utils::address_range32::start_length(dst.rsx_address, dst.pitch * dst.clip_height));
const auto tiled_region = RSX(ctx)->get_tiled_memory_region(utils::address_range32::start_length(dst.rsx_address, write_length));
std::vector<u8> tmp;
if (tiled_region)

View file

@ -86,7 +86,7 @@ namespace rsx
RSX(ctx)->performance_counters.idle_time += (get_system_time() - start);
}
void semaphore_release(context* ctx, u32 /*reg*/, u32 arg)
void semaphore_release(context* ctx, u32 reg, u32 arg)
{
const u32 offset = REGS(ctx)->semaphore_offset_406e();
@ -122,7 +122,7 @@ namespace rsx
arg = 1;
}
util::write_gcm_label<false, true>(ctx, addr, arg);
util::write_gcm_label<false, true>(ctx, reg, addr, arg);
}
}
}

View file

@ -690,7 +690,7 @@ namespace rsx
});
}
void texture_read_semaphore_release(context* ctx, u32 /*reg*/, u32 arg)
void texture_read_semaphore_release(context* ctx, u32 reg, u32 arg)
{
// Pipeline barrier seems to be equivalent to a SHADER_READ stage barrier.
// Ideally the GPU only needs to have cached all textures declared up to this point before writing the label.
@ -715,15 +715,15 @@ namespace rsx
if (g_cfg.video.strict_rendering_mode) [[ unlikely ]]
{
util::write_gcm_label<true, true>(ctx, addr, arg);
util::write_gcm_label<true, true>(ctx, reg, addr, arg);
}
else
{
util::write_gcm_label<true, false>(ctx, addr, arg);
util::write_gcm_label<true, false>(ctx, reg, addr, arg);
}
}
void back_end_write_semaphore_release(context* ctx, u32 /*reg*/, u32 arg)
void back_end_write_semaphore_release(context* ctx, u32 reg, u32 arg)
{
// Full pipeline barrier. GPU must flush pipeline before writing the label
@ -744,7 +744,7 @@ namespace rsx
}
const u32 val = (arg & 0xff00ff00) | ((arg & 0xff) << 16) | ((arg >> 16) & 0xff);
util::write_gcm_label<true, true>(ctx, addr, val);
util::write_gcm_label<true, true>(ctx, reg, addr, val);
}
void sync(context* ctx, u32, u32)

View file

@ -13,13 +13,13 @@ namespace rsx
namespace util
{
template <bool FlushDMA, bool FlushPipe>
static void write_gcm_label(context* ctx, u32 address, u32 data)
static void write_gcm_label(context* ctx, u32 type, u32 address, u32 data)
{
const bool is_flip_sema = (address == (RSX(ctx)->label_addr + 0x10) || address == (RSX(ctx)->device_addr + 0x30));
if (!is_flip_sema)
{
// First, queue the GPU work. If it flushes the queue for us, the following routines will be faster.
const bool handled = RSX(ctx)->get_backend_config().supports_host_gpu_labels && RSX(ctx)->release_GCM_label(address, data);
const bool handled = RSX(ctx)->get_backend_config().supports_host_gpu_labels && RSX(ctx)->release_GCM_label(type, address, data);
if (vm::_ref<RsxSemaphore>(address) == data)
{

View file

@ -9,7 +9,7 @@ namespace glsl
glsl_compute_program = 2,
// Meta
glsl_invalid_program = 0xff
glsl_invalid_program = 7
};
enum glsl_rules : unsigned char

View file

@ -380,7 +380,7 @@ namespace rsx
flags32_t read_barrier(u32 memory_address, u32 memory_range, bool unconditional);
virtual void write_barrier(u32 /*memory_address*/, u32 /*memory_range*/) {}
virtual void sync_hint(FIFO::interrupt_hint hint, reports::sync_hint_payload_t payload);
virtual bool release_GCM_label(u32 /*address*/, u32 /*value*/) { return false; }
virtual bool release_GCM_label(u32 /*type*/, u32 /*address*/, u32 /*value*/) { return false; }
protected:

View file

@ -1541,7 +1541,7 @@ std::pair<volatile vk::host_data_t*, VkBuffer> VKGSRender::map_host_object_data(
return { m_host_dma_ctrl->host_ctx(), m_host_object_data->value };
}
bool VKGSRender::release_GCM_label(u32 address, u32 args)
bool VKGSRender::release_GCM_label(u32 type, u32 address, u32 args)
{
if (!backend_config.supports_host_gpu_labels)
{
@ -1550,7 +1550,7 @@ bool VKGSRender::release_GCM_label(u32 address, u32 args)
auto host_ctx = ensure(m_host_dma_ctrl->host_ctx());
if (host_ctx->texture_loads_completed())
if (type == NV4097_TEXTURE_READ_SEMAPHORE_RELEASE && host_ctx->texture_loads_completed())
{
// All texture loads already seen by the host GPU
// Wait for all previously submitted labels to be flushed
@ -1572,13 +1572,10 @@ bool VKGSRender::release_GCM_label(u32 address, u32 args)
const auto release_event_id = host_ctx->on_label_acquire();
vk::insert_global_memory_barrier(*m_current_command_buffer);
if (host_ctx->has_unflushed_texture_loads())
{
if (vk::is_renderpass_open(*m_current_command_buffer))
{
vk::end_renderpass(*m_current_command_buffer);
}
vkCmdUpdateBuffer(*m_current_command_buffer, mapping.second->value, mapping.first, 4, &write_data);
flush_command_queue();
}

View file

@ -221,7 +221,7 @@ private:
void frame_context_cleanup(vk::frame_context_t *ctx);
void advance_queued_frames();
void present(vk::frame_context_t *ctx);
void reinitialize_swapchain();
bool reinitialize_swapchain();
vk::viewable_image* get_present_source(vk::present_surface_info* info, const rsx::avconf& avconfig);
@ -254,7 +254,7 @@ public:
// Sync
void write_barrier(u32 address, u32 range) override;
void sync_hint(rsx::FIFO::interrupt_hint hint, rsx::reports::sync_hint_payload_t payload) override;
bool release_GCM_label(u32 address, u32 data) override;
bool release_GCM_label(u32 type, u32 address, u32 data) override;
void begin_occlusion_query(rsx::reports::occlusion_query_info* query) override;
void end_occlusion_query(rsx::reports::occlusion_query_info* query) override;

View file

@ -33,7 +33,7 @@ namespace
}
}
void VKGSRender::reinitialize_swapchain()
bool VKGSRender::reinitialize_swapchain()
{
m_swapchain_dims.width = m_frame->client_width();
m_swapchain_dims.height = m_frame->client_height();
@ -44,7 +44,7 @@ void VKGSRender::reinitialize_swapchain()
if (m_swapchain_dims.width == 0 || m_swapchain_dims.height == 0)
{
swapchain_unavailable = true;
return;
return false;
}
// NOTE: This operation will create a hard sync point
@ -97,7 +97,7 @@ void VKGSRender::reinitialize_swapchain()
{
rsx_log.warning("Swapchain initialization failed. Request ignored [%dx%d]", m_swapchain_dims.width, m_swapchain_dims.height);
swapchain_unavailable = true;
return;
return false;
}
// Re-initialize CPU frame contexts
@ -135,6 +135,7 @@ void VKGSRender::reinitialize_swapchain()
swapchain_unavailable = false;
should_reinitialize_swapchain = false;
return true;
}
void VKGSRender::present(vk::frame_context_t *ctx)
@ -426,11 +427,32 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info)
if (swapchain_unavailable || should_reinitialize_swapchain)
{
reinitialize_swapchain();
// Reinitializing the swapchain is a failable operation. However, not all failures are fatal (e.g minimized window).
// In the worst case, we can have the driver refuse to create the swapchain while we already deleted the previous one.
// In such scenarios, we have to retry a few times before giving up as we cannot proceed without a swapchain.
for (int i = 0; i < 10; ++i)
{
if (reinitialize_swapchain() || m_current_frame)
{
// If m_current_frame exists, then the initialization failure is non-fatal. Proceed as usual.
break;
}
if (Emu.IsStopped())
{
m_frame->flip(m_context);
rsx::thread::flip(info);
return;
}
std::this_thread::sleep_for(100ms);
}
}
m_profiler.start();
ensure(m_current_frame, "Invalid swapchain setup. Resizing the game window failed.");
if (m_current_frame == &m_aux_frame_context)
{
m_current_frame = &m_frame_context_storage[m_current_queue_index];
@ -582,6 +604,7 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info)
rsx_log.warning("vkAcquireNextImageKHR failed with VK_ERROR_OUT_OF_DATE_KHR. Flip request ignored until surface is recreated.");
swapchain_unavailable = true;
reinitialize_swapchain();
ensure(m_current_frame, "Could not reinitialize swapchain after VK_ERROR_OUT_OF_DATE_KHR signal!");
continue;
default:
vk::die_with_error(status);

View file

@ -252,7 +252,7 @@ void init_fxo_for_exec(utils::serial* ar, bool full = false)
// Reserved area
if (!load_and_check_reserved(*ar, advance))
{
sys_log.error("Potential failure to load savestate: padding buyes are not 0. %s", *ar);
sys_log.error("Potential failure to load savestate: padding bytes are not 0. %s", *ar);
}
}
}
@ -310,7 +310,7 @@ static void fixup_settings(const psf::registry* _psf)
if (g_cfg.net.net_active == np_internet_status::disabled && g_cfg.net.psn_status != np_psn_status::disabled)
{
sys_log.warning("Net status was set to disconnected so psn status was disabled");
sys_log.warning("Net status was set to disconnected so PSN status was disabled.");
g_cfg.net.psn_status.set(np_psn_status::disabled);
}
}
@ -4642,7 +4642,7 @@ game_boot_result Emulator::InsertDisc(const std::string& path)
else
{
// TODO: find out where other discs are mounted
sys_log.todo("Mounting non-ps2/ps3 disc in dev_bdvd. Is this correct? (path='%s')", disc_root);
sys_log.todo("Mounting non-PS2/PS3 disc in dev_bdvd. Is this correct? (path='%s')", disc_root);
ensure(vfs::mount("/dev_bdvd", disc_root));
}

View file

@ -0,0 +1,146 @@
#include "mouse_gyro_handler.h"
#include <QEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QWindow>
#include <algorithm>
void mouse_gyro_handler::clear()
{
active = false;
reset = false;
gyro_x = DEFAULT_MOTION_X;
gyro_y = DEFAULT_MOTION_Y;
gyro_z = DEFAULT_MOTION_Z;
}
bool mouse_gyro_handler::toggle_enabled()
{
enabled = !enabled;
clear();
return enabled;
}
void mouse_gyro_handler::set_gyro_active()
{
active = true;
}
void mouse_gyro_handler::set_gyro_reset()
{
active = false;
reset = true;
}
void mouse_gyro_handler::set_gyro_xz(s32 off_x, s32 off_y)
{
if (!active)
return;
gyro_x = static_cast<u16>(std::clamp(off_x, 0, DEFAULT_MOTION_X * 2 - 1));
gyro_z = static_cast<u16>(std::clamp(off_y, 0, DEFAULT_MOTION_Z * 2 - 1));
}
void mouse_gyro_handler::set_gyro_y(s32 steps)
{
if (!active)
return;
gyro_y = static_cast<u16>(std::clamp(gyro_y + steps, 0, DEFAULT_MOTION_Y * 2 - 1));
}
void mouse_gyro_handler::handle_event(QEvent* ev, const QWindow& win)
{
if (!enabled)
return;
// Mouse-based motion input.
// Captures mouse events while the game window is focused.
// Updates motion sensor values via mouse position and mouse wheel while RMB is held.
// Intentionally independent of chosen pad configuration.
switch (ev->type())
{
case QEvent::MouseButtonPress:
{
auto* e = static_cast<QMouseEvent*>(ev);
if (e->button() == Qt::RightButton)
{
// Enable mouse-driven gyro emulation while RMB is held.
set_gyro_active();
}
break;
}
case QEvent::MouseButtonRelease:
{
auto* e = static_cast<QMouseEvent*>(ev);
if (e->button() == Qt::RightButton)
{
// Disable gyro emulation and request a one-shot motion reset.
set_gyro_reset();
}
break;
}
case QEvent::MouseMove:
{
auto* e = static_cast<QMouseEvent*>(ev);
// Track cursor offset from window center.
const QPoint center(win.width() / 2, win.height() / 2);
const QPoint cur = e->position().toPoint();
const s32 off_x = cur.x() - center.x() + DEFAULT_MOTION_X;
const s32 off_y = cur.y() - center.y() + DEFAULT_MOTION_Z;
// Determine motion from relative mouse position while gyro emulation is active.
set_gyro_xz(off_x, off_y);
break;
}
case QEvent::Wheel:
{
auto* e = static_cast<QWheelEvent*>(ev);
// Track mouse wheel steps.
const s32 steps = e->angleDelta().y() / 120;
// Accumulate mouse wheel steps while gyro emulation is active.
set_gyro_y(steps);
break;
}
default:
{
break;
}
}
}
void mouse_gyro_handler::apply_gyro(const std::shared_ptr<Pad>& pad)
{
if (!enabled)
return;
if (!pad || !pad->is_connected())
return;
// Inject mouse-based motion sensor values into pad sensors for gyro emulation.
// The Qt frontend maps cursor offset and wheel input to absolute motion values while RMB is held.
if (reset)
{
// RMB released → reset motion
pad->m_sensors[0].m_value = DEFAULT_MOTION_X;
pad->m_sensors[1].m_value = DEFAULT_MOTION_Y;
pad->m_sensors[2].m_value = DEFAULT_MOTION_Z;
clear();
}
else
{
// RMB held → accumulate motion
// Axes have been chosen as tested in Sly 4 minigames. Top-down view motion uses X/Z axes.
pad->m_sensors[0].m_value = gyro_x; // Mouse X → Motion X
pad->m_sensors[1].m_value = gyro_y; // Mouse Wheel → Motion Y
pad->m_sensors[2].m_value = gyro_z; // Mouse Y → Motion Z
}
}

View file

@ -0,0 +1,33 @@
#pragma once
#include "util/types.hpp"
#include "util/atomic.hpp"
#include "Emu/Io/pad_types.h"
class QEvent;
class QWindow;
// Mouse-based motion sensor emulation state.
class mouse_gyro_handler
{
private:
atomic_t<bool> enabled = false; // Whether mouse-based gyro emulation mode has been enabled by using the associated hotkey
atomic_t<bool> active = false; // Whether right mouse button is currently held (gyro active)
atomic_t<bool> reset = false; // One-shot reset request on right mouse button release
atomic_t<s32> gyro_x = DEFAULT_MOTION_X; // Accumulated from mouse X position relative to center
atomic_t<s32> gyro_y = DEFAULT_MOTION_Y; // Accumulated from mouse wheel delta
atomic_t<s32> gyro_z = DEFAULT_MOTION_Z; // Accumulated from mouse Y position relative to center
void set_gyro_active();
void set_gyro_reset();
void set_gyro_xz(s32 off_x, s32 off_y);
void set_gyro_y(s32 steps);
public:
void clear();
bool toggle_enabled();
void handle_event(QEvent* ev, const QWindow& win);
void apply_gyro(const std::shared_ptr<Pad>& pad);
};

View file

@ -81,6 +81,9 @@ void pad_thread::Init()
{
std::lock_guard lock(pad::g_pad_mutex);
// Reset mouse-based gyro state
m_mouse_gyro.clear();
// Cache old settings if possible
std::array<pad_setting, CELL_PAD_MAX_PORT_NUM> pad_settings;
for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++) // max 7 pads
@ -606,6 +609,10 @@ void pad_thread::operator()()
if (Emu.IsRunning())
{
update_pad_states();
// Apply mouse-based gyro emulation.
// Intentionally bound to Player 1 only.
m_mouse_gyro.apply_gyro(m_pads[0]);
}
m_info.now_connect = connected_devices + num_ldd_pad;
@ -624,15 +631,18 @@ void pad_thread::operator()()
if (!pad->is_connected())
continue;
for (const auto& button : pad->m_buttons)
for (const Button& button : pad->m_buttons)
{
if (button.m_pressed && (
button.m_outKeyCode == CELL_PAD_CTRL_CROSS ||
button.m_outKeyCode == CELL_PAD_CTRL_CIRCLE ||
button.m_outKeyCode == CELL_PAD_CTRL_TRIANGLE ||
button.m_outKeyCode == CELL_PAD_CTRL_SQUARE ||
button.m_outKeyCode == CELL_PAD_CTRL_START ||
button.m_outKeyCode == CELL_PAD_CTRL_SELECT))
(button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && (
button.m_outKeyCode == CELL_PAD_CTRL_START ||
button.m_outKeyCode == CELL_PAD_CTRL_SELECT)) ||
(button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2 && (
button.m_outKeyCode == CELL_PAD_CTRL_CROSS ||
button.m_outKeyCode == CELL_PAD_CTRL_CIRCLE ||
button.m_outKeyCode == CELL_PAD_CTRL_TRIANGLE ||
button.m_outKeyCode == CELL_PAD_CTRL_SQUARE))
))
{
any_button_pressed = true;
break;
@ -669,7 +679,7 @@ void pad_thread::operator()()
break;
}
for (const auto& button : pad->m_buttons)
for (const Button& button : pad->m_buttons)
{
if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_PS && button.m_pressed)
{
@ -728,7 +738,7 @@ void pad_thread::operator()()
if (!pad->is_connected())
continue;
for (const auto& button : pad->m_buttons)
for (const Button& button : pad->m_buttons)
{
if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_START && button.m_pressed)
{

View file

@ -5,6 +5,7 @@
#include "Emu/Io/pad_types.h"
#include "Emu/Io/pad_config.h"
#include "Emu/Io/pad_config_types.h"
#include "Input/mouse_gyro_handler.h"
#include "Utilities/mutex.h"
#include <map>
@ -41,6 +42,8 @@ public:
static auto constexpr thread_name = "Pad Thread"sv;
mouse_gyro_handler& get_mouse_gyro() { return m_mouse_gyro; }
protected:
void Init();
void InitLddPad(u32 handle, const u32* port_status);
@ -67,6 +70,8 @@ private:
bool m_resume_emulation_flag = false;
bool m_ps_button_pressed = false;
atomic_t<bool> m_home_menu_open = false;
mouse_gyro_handler m_mouse_gyro;
};
namespace pad

View file

@ -194,6 +194,7 @@
<ClCompile Include="Input\dualsense_pad_handler.cpp" />
<ClCompile Include="Input\gui_pad_thread.cpp" />
<ClCompile Include="Input\hid_pad_handler.cpp" />
<ClCompile Include="Input\mouse_gyro_handler.cpp" />
<ClCompile Include="Input\ps_move_calibration.cpp" />
<ClCompile Include="Input\ps_move_config.cpp" />
<ClCompile Include="Input\ps_move_tracker.cpp" />
@ -1079,6 +1080,7 @@
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
</CustomBuild>
<ClInclude Include="Input\mouse_gyro_handler.h" />
<ClInclude Include="Input\ps_move_calibration.h" />
<ClInclude Include="Input\ps_move_config.h" />
<ClInclude Include="Input\ps_move_tracker.h" />

View file

@ -1272,6 +1272,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_game_list_context_menu.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="Input\mouse_gyro_handler.cpp">
<Filter>Io</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@ -1511,6 +1514,9 @@
<ClInclude Include="Input\sdl_camera_video_sink.h">
<Filter>Io\camera</Filter>
</ClInclude>
<ClInclude Include="Input\mouse_gyro_handler.h">
<Filter>Io</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">

View file

@ -157,6 +157,7 @@ add_library(rpcs3_ui STATIC
../Input/hid_pad_handler.cpp
../Input/keyboard_pad_handler.cpp
../Input/mm_joystick_handler.cpp
../Input/mouse_gyro_handler.cpp
../Input/pad_thread.cpp
../Input/product_info.cpp
../Input/ps_move_calibration.cpp

View file

@ -99,7 +99,7 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p
// The downloader's signals are expected to be disconnected and customized before start is called.
// Therefore we need to (re)connect its signal(s) here and not in the constructor.
connect(this, &downloader::signal_buffer_update, this, &downloader::handle_buffer_update);
connect(this, &downloader::signal_buffer_update, this, &downloader::handle_buffer_update, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
if (show_progress_dialog)
{
@ -169,7 +169,7 @@ usz downloader::update_buffer(char* data, usz size)
const auto old_size = m_curl_buf.size();
const auto new_size = old_size + size;
m_curl_buf.resize(static_cast<int>(new_size));
memcpy(m_curl_buf.data() + old_size, data, size);
std::memcpy(m_curl_buf.data() + old_size, data, size);
int max = 0;
@ -197,6 +197,5 @@ void downloader::handle_buffer_update(int size, int max) const
{
m_progress_dialog->SetRange(0, max > 0 ? max : m_progress_dialog->maximum());
m_progress_dialog->SetValue(size);
QApplication::processEvents();
}
}

View file

@ -19,6 +19,7 @@
#include "Emu/RSX/Overlays/overlay_message.h"
#include "Emu/Io/interception.h"
#include "Emu/Io/recording_config.h"
#include "Input/pad_thread.h"
#include <QApplication>
#include <QDateTime>
@ -402,6 +403,15 @@ void gs_frame::handle_shortcut(gui::shortcuts::shortcut shortcut_key, const QKey
audio::change_volume(-5);
break;
}
case gui::shortcuts::shortcut::gw_toggle_mouse_gyro:
{
if (auto* pad_thr = pad::get_pad_thread(true))
{
const bool mouse_gyro_enabled = pad_thr->get_mouse_gyro().toggle_enabled();
gui_log.notice("Mouse-based gyro emulation %s", mouse_gyro_enabled ? "enabled" : "disabled");
}
break;
}
default:
{
break;
@ -1216,6 +1226,16 @@ bool gs_frame::event(QEvent* ev)
// This will make the cursor visible again if it was hidden by the mouse idle timeout
handle_cursor(visibility(), false, false, true);
}
// Handle events for mouse-based gyro emulation.
if (Emu.IsRunning())
{
if (auto* pad_thr = pad::get_pad_thread(true))
{
pad_thr->get_mouse_gyro().handle_event(ev, *this);
}
}
return QWindow::event(ev);
}

View file

@ -163,11 +163,6 @@ log_frame::log_frame(std::shared_ptr<gui_settings> _gui_settings, QWidget* paren
CreateAndConnectActions();
LoadSettings();
if (m_ansi_tty)
{
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
}
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &log_frame::UpdateUI);
}
@ -225,7 +220,7 @@ void log_frame::CreateAndConnectActions()
// I, for one, welcome our lambda overlord
// It's either this or a signal mapper
// Then, probably making a list of these actions so that it's easier to iterate to generate the mapper.
auto l_initAct = [this](QAction* act, logs::level logLevel)
const auto l_initAct = [this](QAction* act, logs::level logLevel)
{
act->setCheckable(true);
@ -298,7 +293,7 @@ void log_frame::CreateAndConnectActions()
if (m_ansi_tty && !m_tty_ansi_highlighter)
{
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty);
}
else if (!m_ansi_tty && m_tty_ansi_highlighter)
{
@ -607,6 +602,12 @@ void log_frame::RepaintTextColors()
html.replace(old_style, new_style);
m_log->document()->setHtml(html);
if (m_tty_ansi_highlighter)
{
m_tty_ansi_highlighter->update_colors(m_tty);
m_tty_ansi_highlighter->rehighlight();
}
}
void log_frame::UpdateUI()

View file

@ -28,7 +28,8 @@ LOG_CHANNEL(gui_log, "GUI");
log_viewer::log_viewer(std::shared_ptr<gui_settings> gui_settings)
: m_gui_settings(std::move(gui_settings))
{
setWindowTitle(tr("Log Viewer"));
update_title();
setObjectName("log_viewer");
setAttribute(Qt::WA_DeleteOnClose);
setAttribute(Qt::WA_StyledBackground);
@ -59,15 +60,33 @@ log_viewer::log_viewer(std::shared_ptr<gui_settings> gui_settings)
connect(m_log_text, &QWidget::customContextMenuRequested, this, &log_viewer::show_context_menu);
}
void log_viewer::update_title()
{
QString suffix;
if (!m_filter_term.isEmpty())
{
suffix = tr(" | Filter '%0'").arg(m_filter_term);
}
if (!m_exclude_term.isEmpty())
{
suffix += tr(" | Exclude '%0'").arg(m_exclude_term);
}
setWindowTitle(tr("Log Viewer%0").arg(suffix));
}
void log_viewer::show_context_menu(const QPoint& pos)
{
QMenu menu;
QAction* clear = new QAction(tr("&Clear"));
QAction* copy = new QAction(tr("&Copy"));
QAction* open = new QAction(tr("&Open log file"));
QAction* save = new QAction(tr("&Save filtered log"));
QAction* filter = new QAction(tr("&Filter log"));
QAction* config = new QAction(tr("&Check config"));
QAction* clear = new QAction(tr("&Clear"));
QAction* copy = new QAction(tr("&Copy"));
QAction* open = new QAction(tr("&Open log file"));
QAction* save = new QAction(tr("&Save filtered log"));
QAction* filter = new QAction(tr("&Filter log%0").arg(m_filter_term.isEmpty() ? "" : QString(" (%0)").arg(m_filter_term)));
QAction* exclude = new QAction(tr("&Exclude%0").arg(m_exclude_term.isEmpty() ? "" : QString(" (%0)").arg(m_exclude_term)));
QAction* config = new QAction(tr("&Check config"));
QAction* timestamps = new QAction(tr("&Show Timestamps"));
timestamps->setCheckable(true);
@ -91,7 +110,7 @@ void log_viewer::show_context_menu(const QPoint& pos)
QAction* trace_act = new QAction(tr("Trace"), log_level_acts);
log_level_acts->setExclusive(false);
auto init_action = [this](QAction* act, logs::level logLevel)
const auto init_action = [this](QAction* act, logs::level logLevel)
{
act->setCheckable(true);
act->setChecked(m_log_levels.test(static_cast<u32>(logLevel)));
@ -120,6 +139,7 @@ void log_viewer::show_context_menu(const QPoint& pos)
menu.addAction(open);
menu.addAction(config);
menu.addAction(filter);
menu.addAction(exclude);
menu.addAction(save);
menu.addSeparator();
menu.addAction(timestamps);
@ -187,7 +207,22 @@ void log_viewer::show_context_menu(const QPoint& pos)
connect(filter, &QAction::triggered, this, [this]()
{
m_filter_term = QInputDialog::getText(this, tr("Filter log"), tr("Enter text"), QLineEdit::EchoMode::Normal, m_filter_term);
bool ok = false;
QString filter_term = QInputDialog::getText(this, tr("Filter log"), tr("Enter text"), QLineEdit::EchoMode::Normal, m_filter_term, &ok);
if (!ok) return;
m_filter_term = std::move(filter_term);
update_title();
filter_log();
});
connect(exclude, &QAction::triggered, this, [this]()
{
bool ok = false;
QString exclude_term = QInputDialog::getText(this, tr("Exclude"), tr("Enter text (comma separated)"), QLineEdit::EchoMode::Normal, m_exclude_term, &ok);
if (!ok) return;
m_exclude_term = std::move(exclude_term);
m_exclude_terms = m_exclude_term.split(',', Qt::SkipEmptyParts);
update_title();
filter_log();
});
@ -309,7 +344,7 @@ void log_viewer::filter_log()
if (!m_log_levels.test(static_cast<u32>(logs::level::notice))) excluded_log_levels.push_back("·! ");
if (!m_log_levels.test(static_cast<u32>(logs::level::trace))) excluded_log_levels.push_back("·T ");
if (m_filter_term.isEmpty() && excluded_log_levels.empty() && m_show_timestamps && m_show_threads && !m_last_actions_only)
if (m_filter_term.isEmpty() && m_exclude_terms.isEmpty() && excluded_log_levels.empty() && m_show_timestamps && m_show_threads && !m_last_actions_only)
{
set_text_and_keep_position(m_full_log);
return;
@ -322,44 +357,49 @@ void log_viewer::filter_log()
const auto add_line = [this, &result, &excluded_log_levels, &timestamp_regexp, &thread_regexp](QString& line)
{
bool exclude_line = false;
for (const QString& log_level_prefix : excluded_log_levels)
if (!line.isEmpty())
{
if (line.startsWith(log_level_prefix))
for (QStringView log_level_prefix : excluded_log_levels)
{
exclude_line = true;
break;
if (line.startsWith(log_level_prefix))
{
return;
}
}
for (QStringView term : m_exclude_terms)
{
if (line.contains(term))
{
return;
}
}
}
if (exclude_line)
if (!m_filter_term.isEmpty() && !line.contains(m_filter_term))
{
return;
}
if (m_filter_term.isEmpty() || line.contains(m_filter_term))
if (line.isEmpty())
{
if (line.isEmpty())
{
result += "\n";
return;
}
result += "\n";
return;
}
if (!m_show_timestamps)
{
line.remove(timestamp_regexp);
}
if (!m_show_timestamps)
{
line.remove(timestamp_regexp);
}
if (!m_show_threads)
{
line.remove(thread_regexp);
}
if (!m_show_threads)
{
line.remove(thread_regexp);
}
if (!line.isEmpty())
{
result += line + "\n";
}
if (!line.isEmpty())
{
result += line + "\n";
}
};

View file

@ -23,6 +23,7 @@ private Q_SLOTS:
void show_context_menu(const QPoint& pos);
private:
void update_title();
void set_text_and_keep_position(const QString& text);
void filter_log();
bool is_valid_file(const QMimeData& md, bool save = false);
@ -30,6 +31,8 @@ private:
std::shared_ptr<gui_settings> m_gui_settings;
QString m_path_last;
QString m_filter_term;
QString m_exclude_term;
QStringList m_exclude_terms;
QString m_full_log;
QPlainTextEdit* m_log_text;
LogHighlighter* m_log_highlighter;

View file

@ -173,21 +173,45 @@ namespace gui
}
return res;
}
QColor get_foreground_color()
QColor get_foreground_color(QWidget* widget)
{
if (widget)
{
widget->ensurePolished();
return widget->palette().color(QPalette::ColorRole::WindowText);
}
QLabel dummy_color;
dummy_color.ensurePolished();
return dummy_color.palette().color(QPalette::ColorRole::WindowText);
}
QColor get_background_color()
QColor get_background_color(QWidget* widget)
{
if (widget)
{
widget->ensurePolished();
return widget->palette().color(QPalette::ColorRole::Window);
}
QLabel dummy_color;
dummy_color.ensurePolished();
return dummy_color.palette().color(QPalette::ColorRole::Window);
}
QColor adjust_color_for_background(const QColor& fg, const QColor& bg)
{
const int diff = fg.lightness() - bg.lightness();
if (std::abs(diff) >= 40)
{
return fg;
}
return (bg.lightness() < 128) ? fg.lighter(180) : fg.darker(180);
}
QColor get_label_color(const QString& object_name, const QColor& fallback_light, const QColor& fallback_dark, QPalette::ColorRole color_role)
{
if (!gui::custom_stylesheet_active || !gui::stylesheet.contains(object_name))

View file

@ -68,11 +68,14 @@ namespace gui
// Returns a list of all base names of files in dir whose complete file names contain one of the given name_filters
QStringList get_dir_entries(const QDir& dir, const QStringList& name_filters, bool full_path = false);
// Returns the foreground color of QLabel with respect to the current light/dark mode.
QColor get_foreground_color();
// Returns the foreground color of QLabel or the given widget with respect to the current light/dark mode.
QColor get_foreground_color(QWidget* widget = nullptr);
// Returns the background color of QLabel with respect to the current light/dark mode.
QColor get_background_color();
// Returns the background color of QLabel or the given widget with respect to the current light/dark mode.
QColor get_background_color(QWidget* widget = nullptr);
// Returns an adjusted color with better contrast, depending on the background.
QColor adjust_color_for_background(const QColor& fg, const QColor& bg);
// Returns the color specified by its color_role for the QLabels with object_name
QColor get_label_color(const QString& object_name, const QColor& fallback_light, const QColor& fallback_dark, QPalette::ColorRole color_role = QPalette::WindowText);

View file

@ -1262,13 +1262,10 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
connect(accept_request_action, &QAction::triggered, this, [this, str_sel_friend]()
{
if (!m_rpcn->add_friend(str_sel_friend))
{
QMessageBox::critical(this, tr("Error adding a friend!"), tr("An error occurred while trying to add a friend!"), QMessageBox::Ok);
}
else
if (add_friend_with_error_dialog(str_sel_friend))
{
QMessageBox::information(this, tr("Friend added!"), tr("You've successfully added a friend!"), QMessageBox::Ok);
return;
}
});
@ -1304,11 +1301,8 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
connect(send_friend_request_action, &QAction::triggered, this, [this, str_sel_friend]()
{
if (!m_rpcn->add_friend(str_sel_friend))
{
QMessageBox::critical(this, tr("Error sending a friend request!"), tr("An error occurred while trying to send a friend request!"), QMessageBox::Ok);
if (!add_friend_with_error_dialog(str_sel_friend))
return;
}
QString qstr_friend = QString::fromStdString(str_sel_friend);
add_update_list(m_lst_requests, qstr_friend, m_icon_request_sent, QVariant(false));
@ -1341,11 +1335,7 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
QMessageBox::critical(this, tr("Error validating username!"), tr("The username you entered is invalid!"), QMessageBox::Ok);
}
if (!m_rpcn->add_friend(str_friend_username))
{
QMessageBox::critical(this, tr("Error adding friend!"), tr("An error occurred while adding a friend!"), QMessageBox::Ok);
}
else
if (add_friend_with_error_dialog(str_friend_username))
{
add_update_list(m_lst_requests, QString::fromStdString(str_friend_username), m_icon_request_sent, QVariant(false));
QMessageBox::information(this, tr("Friend added!"), tr("Friend was successfully added!"), QMessageBox::Ok);
@ -1360,6 +1350,42 @@ rpcn_friends_dialog::~rpcn_friends_dialog()
m_rpcn->remove_friend_cb(friend_callback, this);
}
bool rpcn_friends_dialog::add_friend_with_error_dialog(const std::string& friend_username)
{
QString err_msg;
const auto opt_error = m_rpcn->add_friend(friend_username);
if (opt_error.has_value())
{
const auto error = opt_error.value();
if (error != rpcn::ErrorType::NoError)
{
switch (error)
{
case rpcn::ErrorType::NotFound: err_msg = tr("The specified username does not exist."); break;
case rpcn::ErrorType::InvalidInput: err_msg = tr("You cannot add yourself as a friend."); break;
case rpcn::ErrorType::Blocked: err_msg = tr("You or the other user have the other blocked."); break;
case rpcn::ErrorType::AlreadyFriend: err_msg = tr("You are already friends with this user."); break;
case rpcn::ErrorType::DbFail: err_msg = tr("A database error occurred. Please try again later."); break;
default: err_msg = tr("An unexpected error occurred."); break;
}
}
}
else
{
err_msg = tr("Failed to send the friend request.");
}
if (!err_msg.isEmpty())
{
QMessageBox::critical(this, tr("Friend Request Failed"), err_msg, QMessageBox::Ok);
return false;
}
return true;
}
bool rpcn_friends_dialog::is_ok() const
{
return m_rpcn_ok;

View file

@ -121,6 +121,7 @@ public:
private:
void add_update_list(QListWidget* list, const QString& name, const QIcon& icon, const QVariant& data);
void remove_list(QListWidget* list, const QString& name);
bool add_friend_with_error_dialog(const std::string& friend_username);
private Q_SLOTS:
void add_update_friend(const QString& name, bool status);

View file

@ -37,6 +37,7 @@ void fmt_class_string<shortcut>::format(std::string& out, u64 arg)
case shortcut::gw_mute_unmute: return "gw_mute_unmute";
case shortcut::gw_volume_up: return "gw_volume_up";
case shortcut::gw_volume_down: return "gw_volume_down";
case shortcut::gw_toggle_mouse_gyro: return "gw_toggle_mouse_gyro";
case shortcut::count: return "count";
}
@ -88,6 +89,7 @@ shortcut_settings::shortcut_settings()
{ shortcut::gw_mute_unmute, shortcut_info{ "gw_mute_unmute", tr("Mute/Unmute Audio"), "Ctrl+Shift+M", shortcut_handler_id::game_window, false } },
{ shortcut::gw_volume_up, shortcut_info{ "gw_volume_up", tr("Volume Up"), "Ctrl+Shift++", shortcut_handler_id::game_window, true } },
{ shortcut::gw_volume_down, shortcut_info{ "gw_volume_down", tr("Volume Down"), "Ctrl+Shift+-", shortcut_handler_id::game_window, true } },
{ shortcut::gw_toggle_mouse_gyro, shortcut_info{ "gw_toggle_mouse_gyro", tr("Toggle Mouse-based Gyro"), "Ctrl+G", shortcut_handler_id::game_window, false } },
})
{
}

View file

@ -46,6 +46,7 @@ namespace gui
gw_mute_unmute,
gw_volume_up,
gw_volume_down,
gw_toggle_mouse_gyro,
count
};

View file

@ -184,12 +184,37 @@ GlslHighlighter::GlslHighlighter(QTextDocument* parent) : Highlighter(parent)
commentEndExpression = QRegularExpression("\\*/");
}
AnsiHighlighter::AnsiHighlighter(QTextDocument* parent) : Highlighter(parent)
AnsiHighlighter::AnsiHighlighter(QPlainTextEdit* text_edit)
: Highlighter(text_edit ? text_edit->document() : nullptr)
{
m_escape_format.setForeground(Qt::darkGray);
m_escape_format.setFontItalic(true);
update_colors(text_edit);
}
m_foreground_color = gui::utils::get_foreground_color();
void AnsiHighlighter::update_colors(QPlainTextEdit* text_edit)
{
m_foreground_color = gui::utils::get_foreground_color(text_edit);
m_background_color = gui::utils::get_background_color(text_edit);
m_foreground_colors[30] = gui::utils::adjust_color_for_background(Qt::black, m_background_color);
m_foreground_colors[31] = gui::utils::adjust_color_for_background(Qt::red, m_background_color);
m_foreground_colors[32] = gui::utils::adjust_color_for_background(Qt::darkGreen, m_background_color);
m_foreground_colors[33] = gui::utils::adjust_color_for_background(Qt::darkYellow, m_background_color);
m_foreground_colors[34] = gui::utils::adjust_color_for_background(Qt::darkBlue, m_background_color);
m_foreground_colors[35] = gui::utils::adjust_color_for_background(Qt::darkMagenta, m_background_color);
m_foreground_colors[36] = gui::utils::adjust_color_for_background(Qt::darkCyan, m_background_color);
m_foreground_colors[37] = gui::utils::adjust_color_for_background(Qt::lightGray, m_background_color);
m_foreground_colors[39] = m_foreground_color;
m_foreground_colors[90] = gui::utils::adjust_color_for_background(Qt::darkGray, m_background_color);
m_foreground_colors[91] = gui::utils::adjust_color_for_background(Qt::red, m_background_color);
m_foreground_colors[92] = gui::utils::adjust_color_for_background(Qt::green, m_background_color);
m_foreground_colors[93] = gui::utils::adjust_color_for_background(Qt::yellow, m_background_color);
m_foreground_colors[94] = gui::utils::adjust_color_for_background(Qt::blue, m_background_color);
m_foreground_colors[95] = gui::utils::adjust_color_for_background(Qt::magenta, m_background_color);
m_foreground_colors[96] = gui::utils::adjust_color_for_background(Qt::cyan, m_background_color);
m_foreground_colors[97] = gui::utils::adjust_color_for_background(Qt::white, m_background_color);
m_escape_format.setForeground(gui::utils::adjust_color_for_background(Qt::darkGray, m_background_color));
m_escape_format.setFontItalic(true);
}
void AnsiHighlighter::highlightBlock(const QString& text)
@ -235,39 +260,26 @@ void AnsiHighlighter::highlightBlock(const QString& text)
if (!ok) continue;
switch (code)
{
case 0:
current_format = QTextCharFormat();
current_format.setForeground(m_foreground_color);
break;
case 1:
current_format.setFontWeight(QFont::Bold);
break;
case 3:
current_format.setFontItalic(true);
break;
case 4:
current_format.setFontUnderline(true);
break;
case 30: current_format.setForeground(Qt::black); break;
case 31: current_format.setForeground(Qt::red); break;
case 32: current_format.setForeground(Qt::darkGreen); break;
case 33: current_format.setForeground(Qt::darkYellow); break;
case 34: current_format.setForeground(Qt::darkBlue); break;
case 35: current_format.setForeground(Qt::darkMagenta); break;
case 36: current_format.setForeground(Qt::darkCyan); break;
case 37: current_format.setForeground(Qt::lightGray); break;
case 39: current_format.setForeground(m_foreground_color); break;
case 90: current_format.setForeground(Qt::darkGray); break;
case 91: current_format.setForeground(Qt::red); break;
case 92: current_format.setForeground(Qt::green); break;
case 93: current_format.setForeground(Qt::yellow); break;
case 94: current_format.setForeground(Qt::blue); break;
case 95: current_format.setForeground(Qt::magenta); break;
case 96: current_format.setForeground(Qt::cyan); break;
case 97: current_format.setForeground(Qt::white); break;
case 0:
current_format = QTextCharFormat();
current_format.setForeground(m_foreground_color);
break;
case 1:
current_format.setFontWeight(QFont::Bold);
break;
case 3:
current_format.setFontItalic(true);
break;
case 4:
current_format.setFontUnderline(true);
break;
default:
// Background and extended colors not yet handled
default:
break;
if (const auto it = m_foreground_colors.find(code); it != m_foreground_colors.cend())
{
current_format.setForeground(it->second);
}
break;
}
}
}
@ -278,5 +290,7 @@ void AnsiHighlighter::highlightBlock(const QString& text)
// Apply remaining format
if (pos < text.length())
{
setFormat(pos, text.length() - pos, current_format);
}
}

View file

@ -3,6 +3,9 @@
#include <QSyntaxHighlighter>
#include <QRegularExpression>
#include <QBrush>
#include <QPlainTextEdit>
#include <map>
// Inspired by https://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html
@ -59,7 +62,9 @@ class AnsiHighlighter : public Highlighter
Q_OBJECT
public:
explicit AnsiHighlighter(QTextDocument* parent = nullptr);
explicit AnsiHighlighter(QPlainTextEdit* text_edit);
void update_colors(QPlainTextEdit* text_edit);
protected:
const QRegularExpression ansi_re = QRegularExpression("\x1b\\[[0-9;]*m");
@ -67,6 +72,8 @@ protected:
QTextCharFormat m_escape_format;
QColor m_foreground_color;
QColor m_background_color;
std::map<int, QColor> m_foreground_colors;
void highlightBlock(const QString& text) override;
};

View file

@ -175,7 +175,7 @@ namespace utils
inline void pause()
{
#if defined(ARCH_ARM64)
__asm__ volatile("yield");
__asm__ volatile("isb" ::: "memory");
#elif defined(ARCH_X64)
_mm_pause();
#else

View file

@ -1011,7 +1011,12 @@ struct atomic_storage<T, 16> : atomic_storage<T, 0>
static inline T exchange(T& dest, T value)
{
__atomic_thread_fence(__ATOMIC_ACQ_REL);
// GCC has recently started thinking using this instrinsic is breaking strict aliasing rules
// TODO: remove if this ever get fixed in GCC
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
return std::bit_cast<T>(__sync_lock_test_and_set(reinterpret_cast<u128*>(&dest), std::bit_cast<u128>(value)));
#pragma GCC diagnostic pop
}
static inline void store(T& dest, T value)

View file

@ -575,34 +575,28 @@ namespace utils
return;
}
// Prepare resampler
av.swr = swr_alloc();
if (!av.swr)
{
media_log.error("audio_decoder: Failed to allocate resampler for stream #%u in file '%s'", stream_index, path);
has_error = true;
return;
}
const int dst_channels = 2;
const AVChannelLayout dst_channel_layout = AV_CHANNEL_LAYOUT_STEREO;
const AVSampleFormat dst_format = AV_SAMPLE_FMT_FLT;
int set_err = 0;
if ((set_err = av_opt_set_int(av.swr, "in_channel_count", stream->codecpar->ch_layout.nb_channels, 0)) ||
(set_err = av_opt_set_int(av.swr, "out_channel_count", dst_channels, 0)) ||
(set_err = av_opt_set_chlayout(av.swr, "in_channel_layout", &stream->codecpar->ch_layout, 0)) ||
(set_err = av_opt_set_chlayout(av.swr, "out_channel_layout", &dst_channel_layout, 0)) ||
(set_err = av_opt_set_int(av.swr, "in_sample_rate", stream->codecpar->sample_rate, 0)) ||
(set_err = av_opt_set_int(av.swr, "out_sample_rate", sample_rate, 0)) ||
(set_err = av_opt_set_sample_fmt(av.swr, "in_sample_fmt", static_cast<AVSampleFormat>(stream->codecpar->format), 0)) ||
(set_err = av_opt_set_sample_fmt(av.swr, "out_sample_fmt", dst_format, 0)))
const int set_err = swr_alloc_set_opts2(&av.swr, &dst_channel_layout, dst_format,
sample_rate, &stream->codecpar->ch_layout,
static_cast<AVSampleFormat>(stream->codecpar->format),
stream->codecpar->sample_rate, 0, nullptr);
if (set_err < 0)
{
media_log.error("audio_decoder: Failed to set resampler options: Error: %d='%s'", set_err, av_error_to_string(set_err));
has_error = true;
return;
}
if (!av.swr)
{
media_log.error("audio_decoder: Failed to allocate resampler for stream #%u in file '%s'", stream_index, path);
has_error = true;
return;
}
if (int err = swr_init(av.swr); err < 0 || !swr_is_initialized(av.swr))
{
media_log.error("audio_decoder: Resampler has not been properly initialized: %d='%s'", err, av_error_to_string(err));

View file

@ -16,9 +16,15 @@
#else
#include <unistd.h>
#include <sys/resource.h>
#ifndef __APPLE__
#ifdef __APPLE__
#include <sys/sysctl.h>
#else
#include <sys/utsname.h>
#include <errno.h>
#if defined(ARCH_ARM64) && defined(__linux__)
#include <sys/auxv.h>
#include <asm/hwcap.h>
#endif
#endif
#endif
@ -444,6 +450,103 @@ u32 utils::get_rep_movsb_threshold()
return g_value;
}
#ifdef ARCH_ARM64
bool utils::has_neon()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP) & HWCAP_ASIMD) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.AdvSIMD", &val, &len, nullptr, 0);
int val_legacy = 0;
size_t len_legacy = sizeof(val_legacy);
sysctlbyname("hw.optional.neon", &val_legacy, &len_legacy, nullptr, 0);
return val != 0 || val_legacy != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
bool utils::has_sha3()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP) & HWCAP_SHA3) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_SHA3", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
bool utils::has_dotprod()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP) & HWCAP_ASIMDDP) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_DotProd", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
bool utils::has_sve()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP) & HWCAP_SVE) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_SVE", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
bool utils::has_sve2()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP2) & HWCAP2_SVE2) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_SVE2", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
#endif
std::string utils::get_cpu_brand()
{
#if defined(ARCH_X64)
@ -496,6 +599,17 @@ std::string utils::get_system_info()
{
fmt::append(result, " | TSC: Disabled");
}
#ifdef ARCH_ARM64
if (has_neon())
{
result += " | Neon";
}
else
{
fmt::throw_exception("Neon support not present");
}
#else
if (has_avx())
{
@ -562,6 +676,7 @@ std::string utils::get_system_info()
{
result += " | TSX disabled via microcode";
}
#endif
return result;
}

View file

@ -54,7 +54,17 @@ namespace utils
bool has_appropriate_um_wait();
bool has_um_wait();
#ifdef ARCH_ARM64
bool has_neon();
bool has_sha3();
bool has_dotprod();
bool has_sve();
bool has_sve2();
#endif
std::string get_cpu_brand();
std::string get_system_info();