Compare commits

..

21 Commits

Author SHA1 Message Date
Elad f6ea921e0d Merge branch 'master' into hot-cake 2026-06-07 19:19:33 +03:00
kd-11 339877ea7d rsx: Handle mipmap sampling edge case when SNORM emulation is active 2026-06-07 18:56:15 +03:00
Elad ba9464c547 cellAudio: Fix _mxr000 event queue hack 2026-06-07 17:57:40 +03:00
Malcolm e7165cbe99 RSX: Use one-shot cacheline wait in semaphore acquire 2026-06-07 15:55:28 +02:00
Malcolm d85f1c7155 RSX: Add hardware spin_wait helper, use it in flush requests 2026-06-07 15:55:28 +02:00
Rad0van 9e2c41bd48 cellAudio: fix _mxr000 surmixer event-queue key constant
The "Dirty hack for sound" in AudioSetNotifyEventQueue() is gated on a
named constant c_mxr000 that is supposed to equal the IPC key of the
_mxr000 event queue created by the game's _cellsurMixerMain thread
(0x8000cafe02460300, e.g. Virtua Fighter 5 FS / SEGA AM2 CRI titles).

When the inline literal was refactored into the named constant in
730badd37 ("cellAudio: Move and partially fix _mxr000 hack", 2023) a
trailing hex digit was dropped: 0x8000cafe0246030 instead of
0x8000cafe02460300 (the value used by the original 2018 hack in
cc0d7c598). As a result `key == c_mxr000` never matches and
lv2_event_queue::find(c_mxr000) looks up the wrong key, so the
surmixer-queue serialization path is effectively dead for every
_mxr000 CRI game.

Restore the correct 16-digit key so the gate and lookup match the
queue the game actually creates.

Note: this corrects a real latent bug but does not by itself resolve
the cellSurMixer null-deref in VF5FS (#17066), which is a separate,
flaky CRI channel-strip init divergence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:05:29 +02:00
Whatcookie 2d1be09180 SPU LLVM: Use TBL for AArch64 ROTQBY shuffles 2026-06-06 18:51:35 +02:00
Rad0van d25972e196 JIT/AArch64: advertise +i8mm to the LLVM target machine
The PPU/SPU recompilers emit i8mm intrinsics (ummla/smmla, used by the
SPU GBB/GBH gather paths) gated on utils::has_i8mm(). The JIT's MAttrs
list mirrored dotprod/sha3/sve from HWCAP but never added i8mm, and the
resolved -mcpu on Apple silicon is the cortex-a78 fallback (no i8mm), so
the backend aborted with "Cannot select: intrinsic %llvm.aarch64.neon.ummla"
on every game. Mirror i8mm into MAttrs like the other features.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 52d121fee79cc569c9ac273852edd0e493ae51fc)
2026-06-06 18:34:38 +03:00
Megamouse 62d32ab45e Update SDL to 3.4.10 2026-06-06 01:23:02 +02:00
Megamouse 1ca995cc96 Update FAudio to 26.06 2026-06-06 01:23:02 +02:00
Malcolm 3164d44952 SPU LLVM: Use I8MM for GBH and GBB 2026-06-05 23:19:35 +02:00
Sanjay Govind 546f3082a2 usb_device: present wii instruments as PS3 instruments (#18796)
The instruments work exactly the same but the IDs are different, so
doing this allows them to work in games
2026-06-05 21:08:46 +02:00
Nico Franke 8d9abd396b Make FFB direction encoding for emulated G27 configurable (#18776)
This adds a dropdown in the emulated G27 settings dialog ("Force
feedback direction encoding") with three options: Steering Axis
(default), Cartesian, Polar. Default is Steering Axis so existing users
see no change.

RPCS3 right now hardcodes SDL_HAPTIC_STEERING_AXIS for the spring and
constant-force effects it sends to the host wheel. That's SDL's
recommended encoding for wheels and works on the hardware the code was
written against. Some Windows DirectInput drivers (like my MOZA R3) do
not react to these affects at all. This now allows changing it to an
encoding that works.
2026-06-05 20:09:35 +02:00
Megamouse 8112c7cc19 Initialize rtmidi async
On my system this opening the settings takes 10 seconds on a cold boot.
Letting it initialize async during boot reduces this delay drastically.
2026-06-05 16:28:33 +02:00
kd-11 bf85a3fdd9 rsx/vk: Suppress the pool growth signal a bit
- Still allows for growth when needed but avoids creating larger sets unless there really is demand for it
2026-06-05 15:42:32 +02:00
kd-11 8e370c2cf1 rsx/vk: Implement auto-scaling of descriptor pool sizes 2026-06-05 15:42:32 +02:00
Malcolm 6349ea2ee9 SPU LLVM: SVE multiply optimizations
- Reduces most SPU multiply instructions to a single operation
- Add SVE length detection, only use SVE instructions for 128b sve for
  now
2026-06-05 14:58:24 +02:00
Whatcookie 2f2ac69d60 SPU LLVM: Idiomatic FSM implementation
- Compiles down to just 2 instructions on Neon, instead of falling back to scalar instructions
- Remove this workaround when LLVM fixes this issue upstream: https://github.com/llvm/llvm-project/issues/200325
2026-06-05 09:48:16 +02:00
Malcolm 4a92d96cf4 SPU LLVM: Use 128-bit SVE2 for FMS 2026-06-05 08:41:17 +02:00
Windsurf7 9a2243bfcb Add "Enable Mouse-based Gyro" setting to I/O config (#18835)
This is a continuation of my work on #18113.

Addresses issue #18197.

Adds a new checkbox in the I/O settings tab that enables mouse-based
gyro emulation at game startup, making the feature more discoverable to
users.
2026-06-04 14:49:48 +02:00
digant73 97704e35e0 Remove duplicated code 2026-06-04 12:06:42 +03:00
48 changed files with 1093 additions and 156 deletions
+4 -4
View File
@@ -30,23 +30,23 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
docker_img: "rpcs3/rpcs3-ci-jammy:1.13"
docker_img: "rpcs3/rpcs3-ci-jammy:1.14"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: clang
UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux"
- os: ubuntu-24.04
docker_img: "rpcs3/rpcs3-ci-jammy:1.13"
docker_img: "rpcs3/rpcs3-ci-jammy:1.14"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: gcc
- os: ubuntu-24.04-arm
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.13"
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.14"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: clang
UPLOAD_COMMIT_HASH: a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux-arm64"
- os: ubuntu-24.04-arm
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.13"
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.14"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: gcc
name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }}
+9
View File
@@ -750,6 +750,15 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
else
attributes.push_back("-dotprod");
// The recompilers emit i8mm intrinsics (e.g. ummla) gated on utils::has_i8mm().
// The JIT target features must advertise i8mm too, otherwise the backend fails
// with "Cannot select: intrinsic %llvm.aarch64.neon.ummla" whenever the resolved
// -mcpu does not already imply it (e.g. the cortex-a78 fallback on Apple silicon).
if (utils::has_i8mm())
attributes.push_back("+i8mm");
else
attributes.push_back("-i8mm");
if (utils::has_sve())
attributes.push_back("+sve");
else
+15
View File
@@ -207,6 +207,21 @@ void cpu_translator::initialize(llvm::LLVMContext& context, llvm::ExecutionEngin
{
m_use_dotprod = true;
}
if (utils::has_i8mm())
{
m_use_i8mm = true;
}
if (utils::has_sve() && utils::sve_length() == 128)
{
m_use_sve_128 = true;
}
if (utils::has_sve2() && utils::sve_length() == 128)
{
m_use_sve2_128 = true;
}
#endif
}
+147 -2
View File
@@ -3121,8 +3121,15 @@ protected:
// ARMv8 SDOT/UDOT
bool m_use_dotprod = false;
// ARMv8.6 SMMLA/UMMLA
bool m_use_i8mm = false;
// Allow direct TBL2/TBX2 emission.
bool m_use_tbl2 = true;
bool m_use_sve_128 = false;
bool m_use_sve2_128 = false;
#else
// Allow FMA
bool m_use_fma = false;
@@ -3724,6 +3731,32 @@ template <typename T1, typename T2, typename T3>
return result;
}
template <typename T1, typename T2, typename T3>
value_t<u32[4]> ummla(T1 a, T2 b, T3 c)
{
value_t<u32[4]> result;
const auto data0 = a.eval(m_ir);
const auto data1 = b.eval(m_ir);
const auto data2 = c.eval(m_ir);
result.value = m_ir->CreateCall(get_intrinsic<u32[4], u8[16]>(llvm::Intrinsic::aarch64_neon_ummla), {data0, data1, data2});
return result;
}
template <typename T1, typename T2, typename T3>
value_t<u32[4]> smmla(T1 a, T2 b, T3 c)
{
value_t<u32[4]> result;
const auto data0 = a.eval(m_ir);
const auto data1 = b.eval(m_ir);
const auto data2 = c.eval(m_ir);
result.value = m_ir->CreateCall(get_intrinsic<u32[4], u8[16]>(llvm::Intrinsic::aarch64_neon_smmla), {data0, data1, data2});
return result;
}
template <typename T1, typename T2>
value_t<s32[4]> smull(T1 a, T2 b)
{
@@ -3747,8 +3780,120 @@ template <typename T1, typename T2, typename T3>
result.value = m_ir->CreateCall(get_intrinsic<u32[4]>(llvm::Intrinsic::aarch64_neon_umull), {data0, data1});
return result;
}
template <typename T1, typename T2>
llvm::Value* to_sve_vector(llvm::Value* value)
{
if (llvm::isa<llvm::ScalableVectorType>(value->getType()))
{
return value;
}
const auto fixed_type = llvm::cast<llvm::FixedVectorType>(value->getType());
const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements());
return m_ir->CreateInsertVector(scalable_type, llvm::UndefValue::get(scalable_type), value, m_ir->getInt64(0));
}
llvm::Value* from_sve_vector(llvm::Value* value, llvm::FixedVectorType* fixed_type)
{
if (value->getType() == fixed_type)
{
return value;
}
return m_ir->CreateExtractVector(fixed_type, value, m_ir->getInt64(0));
}
llvm::Value* sve_ptrue(llvm::FixedVectorType* fixed_type)
{
const auto pred_type = llvm::ScalableVectorType::get(m_ir->getInt1Ty(), fixed_type->getNumElements());
return m_ir->CreateIntrinsic(llvm::Intrinsic::aarch64_sve_ptrue, {pred_type}, {m_ir->getInt32(31)});
}
llvm::Value* sve_fnmls(llvm::Value* acc, llvm::Value* lhs, llvm::Value* rhs)
{
const auto fixed_type = llvm::cast<llvm::FixedVectorType>(acc->getType());
const auto vacc = to_sve_vector(acc);
const auto vlhs = to_sve_vector(lhs);
const auto vrhs = to_sve_vector(rhs);
const auto result = m_ir->CreateIntrinsic(llvm::Intrinsic::aarch64_sve_fnmls, {vacc->getType()}, {sve_ptrue(fixed_type), vacc, vlhs, vrhs});
return from_sve_vector(result, fixed_type);
}
template <typename T, typename T1, typename T2>
value_t<T> sve_mull(llvm::Intrinsic::ID id, T1 a, T2 b)
{
value_t<T> result;
const auto fixed_type = llvm::cast<llvm::FixedVectorType>(get_type<T>());
const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements());
const auto data0 = to_sve_vector(a.eval(m_ir));
const auto data1 = to_sve_vector(b.eval(m_ir));
const std::array<llvm::Type*, 1> types{scalable_type};
result.value = from_sve_vector(m_ir->CreateIntrinsic(id, types, {data0, data1}), fixed_type);
return result;
}
template <typename T, typename T0, typename T1, typename T2>
value_t<T> sve_mlal(llvm::Intrinsic::ID id, T0 acc, T1 a, T2 b)
{
value_t<T> result;
const auto fixed_type = llvm::cast<llvm::FixedVectorType>(get_type<T>());
const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements());
const auto data0 = to_sve_vector(acc.eval(m_ir));
const auto data1 = to_sve_vector(a.eval(m_ir));
const auto data2 = to_sve_vector(b.eval(m_ir));
const std::array<llvm::Type*, 1> types{scalable_type};
result.value = from_sve_vector(m_ir->CreateIntrinsic(id, types, {data0, data1, data2}), fixed_type);
return result;
}
template <typename T1, typename T2>
value_t<s32[4]> sve_smullb(T1 a, T2 b)
{
return sve_mull<s32[4]>(llvm::Intrinsic::aarch64_sve_smullb, a, b);
}
template <typename T1, typename T2>
value_t<s32[4]> sve_smullt(T1 a, T2 b)
{
return sve_mull<s32[4]>(llvm::Intrinsic::aarch64_sve_smullt, a, b);
}
template <typename T1, typename T2>
value_t<u32[4]> sve_umullb(T1 a, T2 b)
{
return sve_mull<u32[4]>(llvm::Intrinsic::aarch64_sve_umullb, a, b);
}
template <typename T1, typename T2>
value_t<u32[4]> sve_umullt(T1 a, T2 b)
{
return sve_mull<u32[4]>(llvm::Intrinsic::aarch64_sve_umullt, a, b);
}
template <typename T0, typename T1, typename T2>
value_t<s32[4]> sve_smlalb(T0 acc, T1 a, T2 b)
{
return sve_mlal<s32[4]>(llvm::Intrinsic::aarch64_sve_smlalb, acc, a, b);
}
template <typename T0, typename T1, typename T2>
value_t<s32[4]> sve_smlalt(T0 acc, T1 a, T2 b)
{
return sve_mlal<s32[4]>(llvm::Intrinsic::aarch64_sve_smlalt, acc, a, b);
}
template <typename T0, typename T1, typename T2>
value_t<u32[4]> sve_umlalt(T0 acc, T1 a, T2 b)
{
return sve_mlal<u32[4]>(llvm::Intrinsic::aarch64_sve_umlalt, acc, a, b);
}
template <typename T1, typename T2>
auto addp(T1 a, T2 b)
{
using T_vector = typename is_llvm_expr<T1>::type;
+25 -25
View File
@@ -1662,36 +1662,35 @@ error_code AudioSetNotifyEventQueue(ppu_thread& ppu, u64 key, u32 iFlags)
lv2_sleep(20, &ppu);
// Dirty hack for sound: confirm the creation of _mxr000 event queue by _cellsurMixerMain thread
constexpr u64 c_mxr000 = 0x8000cafe0246030;
constexpr u64 c_mxr000 = 0x8000cafe02460300;
if (key == c_mxr000 || key == 0)
{
bool has_sur_mixer_thread = false;
for (usz count = 0; !lv2_event_queue::find(c_mxr000) && count < 100; count++)
const bool has_sur_mixer_thread = idm::select<named_thread<ppu_thread>>([&](u32 id, named_thread<ppu_thread>& test_ppu)
{
if (has_sur_mixer_thread || idm::select<named_thread<ppu_thread>>([&](u32 id, named_thread<ppu_thread>& test_ppu)
// Confirm thread existence
if (id == ppu.id)
{
// Confirm thread existence
if (id == ppu.id)
{
return false;
}
const auto ptr = test_ppu.ppu_tname.load();
if (!ptr)
{
return false;
}
return *ptr == "_cellsurMixerMain"sv;
}).ret)
{
has_sur_mixer_thread = true;
return false;
}
else
const auto ptr = test_ppu.ppu_tname.load();
if (!ptr)
{
return false;
}
return *ptr == "_cellsurMixerMain"sv;
}).ret;
bool was_mxr000_queue_found = false;
for (usz count = 0; has_sur_mixer_thread && count < 100; count++)
{
if (lv2_event_queue::find(c_mxr000))
{
was_mxr000_queue_found = true;
break;
}
@@ -1701,13 +1700,14 @@ error_code AudioSetNotifyEventQueue(ppu_thread& ppu, u64 key, u32 iFlags)
return {};
}
cellAudio.error("AudioSetNotifyEventQueue(): Waiting for _mxr000. x%d", count);
(count < 3 ? cellAudio.warning : cellAudio.error)("AudioSetNotifyEventQueue(): Waiting for _mxr000. x%d", count);
lv2_sleep(50'000, &ppu);
}
if (has_sur_mixer_thread && lv2_event_queue::find(c_mxr000))
if (key == 0 && was_mxr000_queue_found)
{
// Correct key value argument
key = c_mxr000;
}
}
+282 -36
View File
@@ -5524,6 +5524,44 @@ public:
const auto a = get_vr<s16[8]>(op.ra);
#ifdef ARCH_ARM64
if (m_use_i8mm)
{
if (match_vr<s16[8], s32[4], s64[2]>(op.ra, [&](auto c, auto MP)
{
using VT = typename decltype(MP)::type;
if (auto [ok, x] = match_expr(c, sext<VT>(match<bool[std::extent_v<VT>]>())); ok)
{
const auto zeroes = splat<u32[4]>(0);
const auto es = zshuffle(bitcast<u8[16]>(a), 16, 16, 16, 16, 16, 16, 16, 16, 0, 2, 4, 6, 8, 10, 12, 14);
set_vr(op.rt, smmla(zeroes, es, build<u8[16]>(
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
-0x01, -0x02, -0x04, -0x08,
-0x10, -0x20, -0x40, -0x80
)));
return true;
}
return false;
}))
{
return;
}
const auto zeroes = splat<u32[4]>(0);
const auto masked = a & 0x01;
const auto es = zshuffle(bitcast<u8[16]>(masked), 16, 16, 16, 16, 16, 16, 16, 16, 0, 2, 4, 6, 8, 10, 12, 14);
set_vr(op.rt, ummla(zeroes, es, build<u8[16]>(
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80
)));
return;
}
// Use dot product instructions with special values to shift then sum results into the preferred slot
if (m_use_dotprod)
{
@@ -5579,6 +5617,48 @@ public:
const auto a = get_vr<u8[16]>(op.ra);
#ifdef ARCH_ARM64
if (m_use_i8mm)
{
if (match_vr<s8[16], s16[8], s32[4], s64[2]>(op.ra, [&](auto c, auto MP)
{
using VT = typename decltype(MP)::type;
if (auto [ok, x] = match_expr(c, sext<VT>(match<bool[std::extent_v<VT>]>())); ok)
{
const auto zeroes = splat<u32[4]>(0);
const auto extracted = smmla(zeroes, a, build<u8[16]>(
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
-0x01, -0x02, -0x04, -0x08,
-0x10, -0x20, -0x40, -0x80
));
const auto es = zshuffle(bitcast<u8[16]>(extracted), 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 4, 12, 16, 16);
set_vr(op.rt, bitcast<u32[4]>(es));
return true;
}
return false;
}))
{
return;
}
const auto zeroes = splat<u32[4]>(0);
const auto masked = a & 0x01;
const auto extracted = ummla(zeroes, masked, build<u8[16]>(
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80
));
const auto es = zshuffle(bitcast<u8[16]>(extracted), 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 4, 12, 16, 16);
set_vr(op.rt, bitcast<u32[4]>(es));
return;
}
// Use dot product instructions with special values to shift then sum results into the preferred slot
if (m_use_dotprod)
{
@@ -5657,22 +5737,44 @@ public:
}
const auto v = extract(get_vr(op.ra), 3);
#ifdef ARCH_ARM64
// Workaround for bad codegen via LLVM
// More idiomatic version that compiles to 2 neon instructions
// Remove me when addressed by upstream llvm: https://github.com/llvm/llvm-project/issues/200325 - Whatcookie
const auto masks = build<u32[4]>(1, 2, 4, 8);
const auto bits = vsplat<u32[4]>(zext<u32>(trunc<i4>(v)));
set_vr(op.rt, sext<s32[4]>((bits & masks) == masks));
#else
const auto m = bitcast<bool[4]>(trunc<i4>(v));
set_vr(op.rt, sext<s32[4]>(m));
#endif
}
void FSMH(spu_opcode_t op)
{
const auto v = extract(get_vr(op.ra), 3);
#ifdef ARCH_ARM64
const auto masks = build<u16[8]>(1, 2, 4, 8, 16, 32, 64, 128);
const auto bits = vsplat<u16[8]>(zext<u16>(trunc<u8>(v)));
set_vr(op.rt, sext<s16[8]>((bits & masks) == masks));
#else
const auto m = bitcast<bool[8]>(trunc<u8>(v));
set_vr(op.rt, sext<s16[8]>(m));
#endif
}
void FSMB(spu_opcode_t op)
{
const auto v = extract(get_vr(op.ra), 3);
#ifdef ARCH_ARM64
const auto masks = build<u8[16]>(1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128);
const auto bytes = bitcast<u8[16]>(vsplat<u16[8]>(trunc<u16>(v)));
const auto bits = zshuffle(bytes, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1);
set_vr(op.rt, sext<s8[16]>((bits & masks) == masks));
#else
const auto m = bitcast<bool[16]>(trunc<u16>(v));
set_vr(op.rt, sext<s8[16]>(m));
#endif
}
template <typename TA>
@@ -5681,6 +5783,45 @@ public:
return zshuffle(std::forward<TA>(a), 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
}
static auto rotqby_reverse_base()
{
return build<u8[16]>(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
}
static auto rotqby_forward_base()
{
return build<u8[16]>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
}
static auto rotqby_zero_base()
{
#ifdef ARCH_ARM64
return rotqby_forward_base();
#else
return build<u8[16]>(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127);
#endif
}
static auto rotqby_reverse_zero_base()
{
#ifdef ARCH_ARM64
return rotqby_reverse_base();
#else
return build<u8[16]>(127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112);
#endif
}
// For use in rotqby family of instructions only
template <typename T, typename U>
auto pshufb_for_x86_and_tbl_for_aarch64(T&& a, U&& b)
{
#ifdef ARCH_ARM64
return tbl(std::forward<T>(a), std::forward<U>(b));
#else
return pshufb(std::forward<T>(a), std::forward<U>(b));
#endif
}
template <typename T, typename U>
static llvm_calli<u8[16], T, U> rotqbybi(T&& a, U&& b)
{
@@ -5697,7 +5838,7 @@ public:
// Data with swapped endian from a load instruction
if (auto [ok, as] = match_expr(a, byteswap(match<u8[16]>())); ok)
{
const auto sc = build<u8[16]>(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
const auto sc = rotqby_reverse_base();
const auto sh = sc + (splat_scalar(b) >> 3);
if (m_use_avx512_icl)
@@ -5705,9 +5846,9 @@ public:
return eval(vpermb(as, sh));
}
return eval(pshufb(as, (sh & 0xf)));
return eval(pshufb_for_x86_and_tbl_for_aarch64(as, (sh & 0xf)));
}
const auto sc = build<u8[16]>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
const auto sc = rotqby_forward_base();
const auto sh = sc - (splat_scalar(b) >> 3);
if (m_use_avx512_icl)
@@ -5715,7 +5856,7 @@ public:
return eval(vpermb(a, sh));
}
return eval(pshufb(a, (sh & 0xf)));
return eval(pshufb_for_x86_and_tbl_for_aarch64(a, (sh & 0xf)));
});
set_vr(op.rt, rotqbybi(get_vr<u8[16]>(op.ra), get_vr<u8[16]>(op.rb)));
@@ -5743,15 +5884,15 @@ public:
// Data with swapped endian from a load instruction
if (auto [ok, as] = match_expr(a, byteswap(match<u8[16]>())); ok)
{
const auto sc = build<u8[16]>(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
const auto sc = rotqby_reverse_base();
const auto sh = sc - splat_scalar(minusbx);
set_vr(op.rt, pshufb(as, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, sh));
return;
}
const auto sc = build<u8[16]>(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127);
const auto sc = rotqby_zero_base();
const auto sh = sc + splat_scalar(minusbx);
set_vr(op.rt, pshufb(a, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh));
}
void SHLQBYBI(spu_opcode_t op)
@@ -5762,15 +5903,15 @@ public:
// Data with swapped endian from a load instruction
if (auto [ok, as] = match_expr(a, byteswap(match<u8[16]>())); ok)
{
const auto sc = build<u8[16]>(127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112);
const auto sc = rotqby_reverse_zero_base();
const auto sh = sc + (splat_scalar(b) >> 3);
set_vr(op.rt, pshufb(as, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, sh));
return;
}
const auto sc = build<u8[16]>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
const auto sc = rotqby_forward_base();
const auto sh = sc - (splat_scalar(b) >> 3);
set_vr(op.rt, pshufb(a, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh));
}
template <typename RT, typename T>
@@ -5924,7 +6065,7 @@ public:
// Data with swapped endian from a load instruction
if (auto [ok, as] = match_expr(a, byteswap(match<u8[16]>())); ok)
{
const auto sc = build<u8[16]>(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
const auto sc = rotqby_reverse_base();
const auto sh = eval(sc + splat_scalar(b));
if (m_use_avx512_icl)
@@ -5933,11 +6074,11 @@ public:
return;
}
set_vr(op.rt, pshufb(as, (sh & 0xf)));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, (sh & 0xf)));
return;
}
const auto sc = build<u8[16]>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
const auto sc = rotqby_forward_base();
const auto sh = eval(sc - splat_scalar(b));
if (m_use_avx512_icl)
@@ -5946,7 +6087,7 @@ public:
return;
}
set_vr(op.rt, pshufb(a, (sh & 0xf)));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, (sh & 0xf)));
}
void ROTQMBY(spu_opcode_t op)
@@ -5965,15 +6106,15 @@ public:
// Data with swapped endian from a load instruction
if (auto [ok, as] = match_expr(a, byteswap(match<u8[16]>())); ok)
{
const auto sc = build<u8[16]>(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
const auto sc = rotqby_reverse_base();
const auto sh = sc - (splat_scalar(minusbx) & 0x1f);
set_vr(op.rt, pshufb(as, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, sh));
return;
}
const auto sc = build<u8[16]>(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127);
const auto sc = rotqby_zero_base();
const auto sh = sc + (splat_scalar(minusbx) & 0x1f);
set_vr(op.rt, pshufb(a, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh));
}
void SHLQBY(spu_opcode_t op)
@@ -5984,15 +6125,15 @@ public:
// Data with swapped endian from a load instruction
if (auto [ok, as] = match_expr(a, byteswap(match<u8[16]>())); ok)
{
const auto sc = build<u8[16]>(127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112);
const auto sc = rotqby_reverse_zero_base();
const auto sh = sc + (splat_scalar(b) & 0x1f);
set_vr(op.rt, pshufb(as, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(as, sh));
return;
}
const auto sc = build<u8[16]>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
const auto sc = rotqby_forward_base();
const auto sh = sc - (splat_scalar(b) & 0x1f);
set_vr(op.rt, pshufb(a, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh));
}
template <typename T>
@@ -6090,26 +6231,26 @@ public:
void ROTQBYI(spu_opcode_t op)
{
const auto a = get_vr<u8[16]>(op.ra);
const auto sc = build<u8[16]>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
const auto sc = rotqby_forward_base();
const auto sh = (sc - get_imm<u8[16]>(op.i7, false)) & 0xf;
set_vr(op.rt, pshufb(a, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh));
}
void ROTQMBYI(spu_opcode_t op)
{
const auto a = get_vr<u8[16]>(op.ra);
const auto sc = build<u8[16]>(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127);
const auto sc = rotqby_zero_base();
const auto sh = sc + (-get_imm<u8[16]>(op.i7, false) & 0x1f);
set_vr(op.rt, pshufb(a, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh));
}
void SHLQBYI(spu_opcode_t op)
{
if (get_reg_raw(op.ra) && !op.i7) return set_reg_fixed(op.rt, get_reg_raw(op.ra), false); // For expressions matching
const auto a = get_vr<u8[16]>(op.ra);
const auto sc = build<u8[16]>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
const auto sc = rotqby_forward_base();
const auto sh = sc - (get_imm<u8[16]>(op.i7, false) & 0x1f);
set_vr(op.rt, pshufb(a, sh));
set_vr(op.rt, pshufb_for_x86_and_tbl_for_aarch64(a, sh));
}
void CGT(spu_opcode_t op)
@@ -6243,6 +6384,15 @@ public:
void MPYHHU(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<u32[4]>(op.ra, op.rb);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_umullt(bitcast<u16[8]>(a), bitcast<u16[8]>(b)));
return;
}
#endif
set_vr(op.rt, (get_vr(op.ra) >> 16) * (get_vr(op.rb) >> 16));
}
@@ -6273,11 +6423,29 @@ public:
void MPYHHA(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smlalt(get_vr<s32[4]>(op.rt), bitcast<s16[8]>(a), bitcast<s16[8]>(b)));
return;
}
#endif
set_vr(op.rt, (get_vr<s32[4]>(op.ra) >> 16) * (get_vr<s32[4]>(op.rb) >> 16) + get_vr<s32[4]>(op.rt));
}
void MPYHHAU(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<u32[4]>(op.ra, op.rb);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_umlalt(get_vr<u32[4]>(op.rt), bitcast<u16[8]>(a), bitcast<u16[8]>(b)));
return;
}
#endif
set_vr(op.rt, (get_vr(op.ra) >> 16) * (get_vr(op.rb) >> 16) + get_vr(op.rt));
}
@@ -6285,7 +6453,15 @@ public:
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)));
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smullb(bitcast<s16[8]>(a), bitcast<s16[8]>(b)));
}
else
{
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)));
}
#else
set_vr(op.rt, (get_vr<s32[4]>(op.ra) << 16 >> 16) * (get_vr<s32[4]>(op.rb) << 16 >> 16));
#endif
@@ -6298,6 +6474,15 @@ public:
void MPYHH(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smullt(bitcast<s16[8]>(a), bitcast<s16[8]>(b)));
return;
}
#endif
set_vr(op.rt, (get_vr<s32[4]>(op.ra) >> 16) * (get_vr<s32[4]>(op.rb) >> 16));
}
@@ -6305,7 +6490,15 @@ public:
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)) >> 16);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smullb(bitcast<s16[8]>(a), bitcast<s16[8]>(b)) >> 16);
}
else
{
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)) >> 16);
}
#else
set_vr(op.rt, (get_vr<s32[4]>(op.ra) << 16 >> 16) * (get_vr<s32[4]>(op.rb) << 16 >> 16) >> 16);
#endif
@@ -6320,7 +6513,15 @@ public:
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<u32[4]>(op.ra, op.rb);
set_vr(op.rt, umull(zshuffle(bitcast<u16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<u16[8]>(b), 0, 2, 4, 6)));
if (m_use_sve2_128)
{
set_vr(op.rt, sve_umullb(bitcast<u16[8]>(a), bitcast<u16[8]>(b)));
}
else
{
set_vr(op.rt, umull(zshuffle(bitcast<u16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<u16[8]>(b), 0, 2, 4, 6)));
}
#else
set_vr(op.rt, mpyu(get_vr(op.ra), get_vr(op.rb)));
#endif
@@ -6333,8 +6534,15 @@ public:
void FSMBI(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto masks = build<u8[16]>(1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128);
const auto bytes = bitcast<u8[16]>(vsplat<u16[8]>(get_imm<u16>(op.i16)));
const auto bits = zshuffle(bytes, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1);
set_vr(op.rt, sext<s8[16]>((bits & masks) == masks));
#else
const auto m = bitcast<bool[16]>(get_imm<u16>(op.i16));
set_vr(op.rt, sext<s8[16]>(m));
#endif
}
void IL(spu_opcode_t op)
@@ -6466,7 +6674,14 @@ public:
void MPYI(spu_opcode_t op)
{
#ifdef ARCH_ARM64
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(get_vr<s32[4]>(op.ra)), 0, 2, 4, 6), get_imm<s16[4]>(op.si10)));
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smullb(bitcast<s16[8]>(get_vr<s32[4]>(op.ra)), get_imm<s16[8]>(op.si10)));
}
else
{
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(get_vr<s32[4]>(op.ra)), 0, 2, 4, 6), get_imm<s16[4]>(op.si10)));
}
#else
set_vr(op.rt, (get_vr<s32[4]>(op.ra) << 16 >> 16) * get_imm<s32[4]>(op.si10));
#endif
@@ -6475,7 +6690,14 @@ public:
void MPYUI(spu_opcode_t op)
{
#ifdef ARCH_ARM64
set_vr(op.rt, umull(zshuffle(bitcast<u16[8]>(get_vr<u32[4]>(op.ra)), 0, 2, 4, 6), get_imm<u16[4]>(op.si10)));
if (m_use_sve2_128)
{
set_vr(op.rt, sve_umullb(bitcast<u16[8]>(get_vr<u32[4]>(op.ra)), get_imm<u16[8]>(op.si10)));
}
else
{
set_vr(op.rt, umull(zshuffle(bitcast<u16[8]>(get_vr<u32[4]>(op.ra)), 0, 2, 4, 6), get_imm<u16[4]>(op.si10)));
}
#else
set_vr(op.rt, (get_vr(op.ra) << 16 >> 16) * (get_imm(op.si10) & 0xffff));
#endif
@@ -7062,7 +7284,15 @@ public:
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
set_vr(op.rt4, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)) + get_vr<s32[4]>(op.rc));
if (m_use_sve2_128)
{
set_vr(op.rt4, sve_smlalb(get_vr<s32[4]>(op.rc), bitcast<s16[8]>(a), bitcast<s16[8]>(b)));
}
else
{
set_vr(op.rt4, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)) + get_vr<s32[4]>(op.rc));
}
#else
set_vr(op.rt4, (get_vr<s32[4]>(op.ra) << 16 >> 16) * (get_vr<s32[4]>(op.rb) << 16 >> 16) + get_vr<s32[4]>(op.rc));
#endif
@@ -8220,10 +8450,26 @@ public:
if (g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::approximate)
{
#ifdef ARCH_ARM64
if (m_use_sve2_128)
{
const auto ca = eval(clamp_smax(a));
const auto cb = eval(clamp_smax(b));
return value<f32[4]>(sve_fnmls(c.value, ca.value, cb.value));
}
#endif
return fma32x4(clamp_smax(a), clamp_smax(b), eval(-c));
}
else
{
#ifdef ARCH_ARM64
if (m_use_sve2_128)
{
return value<f32[4]>(sve_fnmls(c.value, a.value, b.value));
}
#endif
return fma32x4(a, b, eval(-c));
}
});
+35 -19
View File
@@ -266,12 +266,28 @@ static const std::map<logitech_personality,
}
};
// ref: https://github.com/libsdl-org/SDL/issues/7941, need to use SDL_HAPTIC_STEERING_AXIS for some windows drivers
static constexpr SDL_HapticDirection STEERING_DIRECTION =
// Configurable because some Windows DirectInput drivers silently ignore
// SDL_HAPTIC_STEERING_AXIS; see https://github.com/libsdl-org/SDL/issues/7941 .
SDL_HapticDirection usb_device_logitech_g27::make_steering_direction() const
{
.type = SDL_HAPTIC_STEERING_AXIS,
.dir = {0, 0, 0}
};
SDL_HapticDirection dir {};
switch (g_cfg_logitech_g27.ffb_direction_type.get())
{
case g27_ffb_direction_type::steering_axis:
dir.type = SDL_HAPTIC_STEERING_AXIS;
dir.dir[0] = 0;
break;
case g27_ffb_direction_type::cartesian:
dir.type = SDL_HAPTIC_CARTESIAN;
dir.dir[0] = 1;
break;
case g27_ffb_direction_type::polar:
dir.type = SDL_HAPTIC_POLAR;
dir.dir[0] = 0;
break;
}
return dir;
}
void usb_device_logitech_g27::set_personality(logitech_personality personality, bool reconnect)
{
@@ -298,8 +314,10 @@ usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std
{
set_personality(logitech_personality::driving_force_ex);
g_cfg_logitech_g27.load();
m_default_spring_effect.type = SDL_HAPTIC_SPRING;
m_default_spring_effect.condition.direction = STEERING_DIRECTION;
m_default_spring_effect.condition.direction = make_steering_direction();
m_default_spring_effect.condition.length = SDL_HAPTIC_INFINITY;
for (int i = 0; i < 1 /*3*/; i++)
{
@@ -309,8 +327,6 @@ usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std
m_default_spring_effect.condition.left_coeff[i] = 0x7FFF;
}
g_cfg_logitech_g27.load();
m_enabled = g_cfg_logitech_g27.enabled.get() && sdl_instance::get_instance().initialize();
if (!m_enabled)
@@ -1344,7 +1360,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
{
// Constant force
new_effect.type = SDL_HAPTIC_CONSTANT;
new_effect.constant.direction = STEERING_DIRECTION;
new_effect.constant.direction = make_steering_direction();
new_effect.constant.length = SDL_HAPTIC_INFINITY;
new_effect.constant.level = logitech_g27_force_to_level(buf[2 + i], m_reverse_effects);
break;
@@ -1354,7 +1370,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
{
// Spring/High resolution spring
new_effect.type = SDL_HAPTIC_SPRING;
new_effect.condition.direction = STEERING_DIRECTION;
new_effect.condition.direction = make_steering_direction();
new_effect.condition.length = SDL_HAPTIC_INFINITY;
const u8 s1 = buf[5] & 1;
const u8 s2 = (buf[5] >> 4) & 1;
@@ -1405,7 +1421,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
{
// Damper/High resolution damper
new_effect.type = SDL_HAPTIC_DAMPER;
new_effect.condition.direction = STEERING_DIRECTION;
new_effect.condition.direction = make_steering_direction();
new_effect.condition.length = SDL_HAPTIC_INFINITY;
const u8 s1 = buf[3] & 1;
const u8 s2 = buf[5] & 1;
@@ -1445,7 +1461,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
{
// Friction
new_effect.type = SDL_HAPTIC_FRICTION;
new_effect.condition.direction = STEERING_DIRECTION;
new_effect.condition.direction = make_steering_direction();
new_effect.condition.length = SDL_HAPTIC_INFINITY;
const u8 k1 = buf[2];
const u8 k2 = buf[3];
@@ -1472,7 +1488,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
{
// Auto center spring/High resolution auto center spring
new_effect.type = SDL_HAPTIC_SPRING;
new_effect.condition.direction = STEERING_DIRECTION;
new_effect.condition.direction = make_steering_direction();
new_effect.condition.length = SDL_HAPTIC_INFINITY;
const u16 saturation = logitech_g27_clip_to_saturation(buf[4]);
constexpr u16 deadband = 2 * 0xFFFF / 255;
@@ -1518,7 +1534,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
else
new_effect.type = m_reverse_effects ? SDL_HAPTIC_SAWTOOTHUP : SDL_HAPTIC_SAWTOOTHDOWN;
new_effect.type = buf[1] == 0x04 ? SDL_HAPTIC_SAWTOOTHUP : SDL_HAPTIC_SAWTOOTHDOWN;
new_effect.periodic.direction = STEERING_DIRECTION;
new_effect.periodic.direction = make_steering_direction();
new_effect.periodic.length = SDL_HAPTIC_INFINITY;
const u8 l1 = buf[2];
const u8 l2 = buf[3];
@@ -1558,7 +1574,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
{
// Trapezoid, convert to SDL_HAPTIC_SQUARE or SDL_HAPTIC_TRIANGLE
// TODO full accuracy will need some kind of rendering thread, cannot be represented with a single effect
new_effect.periodic.direction = STEERING_DIRECTION;
new_effect.periodic.direction = make_steering_direction();
new_effect.periodic.length = SDL_HAPTIC_INFINITY;
const u8 l1 = buf[2];
const u8 l2 = buf[3];
@@ -1587,7 +1603,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
// Rectangle, convert to SDL_HAPTIC_SQUARE
// TODO full accuracy will need some kind of rendering thread, cannot be represented with a single effect
new_effect.type = SDL_HAPTIC_SQUARE;
new_effect.periodic.direction = STEERING_DIRECTION;
new_effect.periodic.direction = make_steering_direction();
new_effect.periodic.length = SDL_HAPTIC_INFINITY;
const u8 l1 = buf[2];
const u8 l2 = buf[3];
@@ -1630,7 +1646,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
const u8 d = i == 0 ? d1 : d2;
const u8 l = i == 0 ? l1 : l2;
new_effect.constant.length = SDL_HAPTIC_INFINITY;
new_effect.constant.direction = STEERING_DIRECTION;
new_effect.constant.direction = make_steering_direction();
if (s == 0 || t == 0)
{
// gran turismo 6 does this, gives a variable force with no step so it just behaves as constant force
@@ -1655,7 +1671,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
else
{
new_effect.type = SDL_HAPTIC_RAMP;
new_effect.ramp.direction = STEERING_DIRECTION;
new_effect.ramp.direction = make_steering_direction();
if (l2 > l1)
logitech_g27_log.error("min force is larger than max force in ramp effect, l1 %u l2 %u", l1, l2);
const s16 l1_converted = logitech_g27_force_to_level(l1, m_reverse_effects);
@@ -1674,7 +1690,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
{
// Square
new_effect.type = SDL_HAPTIC_SQUARE;
new_effect.periodic.direction = STEERING_DIRECTION;
new_effect.periodic.direction = make_steering_direction();
const u8 a = buf[2];
const u8 tl = buf[3];
const u8 th = buf[4];
+1
View File
@@ -126,6 +126,7 @@ private:
void transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer) const;
void transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer) const;
void transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer) const;
SDL_HapticDirection make_steering_direction() const;
u32 m_controller_index = 0;
+16
View File
@@ -39,6 +39,22 @@ void fmt_class_string<hat_component>::format(std::string& out, u64 arg)
});
}
template <>
void fmt_class_string<g27_ffb_direction_type>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](g27_ffb_direction_type value)
{
switch (value)
{
case g27_ffb_direction_type::steering_axis: return "steering_axis";
case g27_ffb_direction_type::cartesian: return "cartesian";
case g27_ffb_direction_type::polar: return "polar";
}
return unknown;
});
}
emulated_logitech_g27_config g_cfg_logitech_g27;
LOG_CHANNEL(cfg_log, "CFG");
+8
View File
@@ -20,6 +20,13 @@ enum class hat_component
right
};
enum class g27_ffb_direction_type
{
steering_axis = 0,
cartesian = 1,
polar = 2,
};
// this was a bitfield, but juggling at least 3 compilers and OSes means no bitfield
// num_buttons:10 << 52 | num_hats:10 << 42 | num_axes:10 << 32 | vendor_id:16 << 16 | product_id:16
struct emulated_g27_device_type_id
@@ -111,6 +118,7 @@ public:
emulated_logitech_g27_mapping shifter_r{this, "shifter_r", 0, sdl_mapping_type::hat, 0, hat_component::left, false};
cfg::_bool reverse_effects{this, "reverse_effects", false};
cfg::_enum<g27_ffb_direction_type> ffb_direction_type{this, "ffb_direction_type", g27_ffb_direction_type::steering_axis};
cfg::uint<0, 4> compatibility_limit{this, "compatibility_limit", 4};
cfg::uint<0, 0xFFFFFFFFFFFFFFFF> ffb_device_type_id{this, "ffb_device_type_id", 0};
cfg::uint<0, 0xFFFFFFFFFFFFFFFF> led_device_type_id{this, "led_device_type_id", 0};
+4 -1
View File
@@ -412,7 +412,10 @@ usb_device_rb3_midi_drums::usb_device_rb3_midi_drums(const std::array<u8, 7>& lo
usb_device_rb3_midi_drums::~usb_device_rb3_midi_drums()
{
rtmidi_in_free(midi_in);
if (midi_in)
{
rtmidi_in_free(midi_in);
}
}
static const std::array<u8, 40> disabled_response = {
+4 -1
View File
@@ -100,7 +100,10 @@ usb_device_rb3_midi_guitar::usb_device_rb3_midi_guitar(const std::array<u8, 7>&
usb_device_rb3_midi_guitar::~usb_device_rb3_midi_guitar()
{
rtmidi_in_free(midi_in);
if (midi_in)
{
rtmidi_in_free(midi_in);
}
}
static const std::array<u8, 40> disabled_response = {
+4 -1
View File
@@ -86,7 +86,10 @@ usb_device_rb3_midi_keyboard::usb_device_rb3_midi_keyboard(const std::array<u8,
usb_device_rb3_midi_keyboard::~usb_device_rb3_midi_keyboard()
{
rtmidi_in_free(midi_in);
if (midi_in)
{
rtmidi_in_free(midi_in);
}
}
static const std::array<u8, 40> disabled_response = {
+37
View File
@@ -75,6 +75,42 @@ usb_device_passthrough::usb_device_passthrough(libusb_device* _device, libusb_de
{
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{desc.bcdUSB, desc.bDeviceClass, desc.bDeviceSubClass, desc.bDeviceProtocol, desc.bMaxPacketSize0, desc.idVendor, desc.idProduct,
desc.bcdDevice, desc.iManufacturer, desc.iProduct, desc.iSerialNumber, desc.bNumConfigurations});
patch_descriptors();
}
void usb_device_passthrough::patch_descriptors()
{
// Patch Wii vids and pids so they are presented to the console as PS3 instruments
if (device._device.idVendor == 0x1BAD) // Harmonix
{
switch (device._device.idProduct)
{
case 0x0004: // Harmonix RB1 Guitar - Wii
case 0x3010: // Harmonix RB2 Guitar - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x0200; // Harmonix Guitar
break;
case 0x0005: // Harmonix RB1 Drums - Wii
case 0x3110: // Harmonix RB2 Drums - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x0210; // Harmonix Drums
break;
case 0x3330: // Harmonix Keyboard - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x2330; // Harmonix Keyboard
break;
case 0x3430: // Harmonix Button Guitar - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x2430; // Harmonix Button Guitar
break;
case 0x3530: // Harmonix Real Guitar - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x2530; // Harmonix Real Guitar
break;
default:
break;
}
}
}
usb_device_passthrough::~usb_device_passthrough()
@@ -153,6 +189,7 @@ void usb_device_passthrough::read_descriptors()
index += buf[index];
}
}
patch_descriptors();
}
u32 usb_device_passthrough::get_configuration(u8* buf)
+3
View File
@@ -277,6 +277,9 @@ protected:
protected:
libusb_device* lusb_device = nullptr;
libusb_device_handle* lusb_handle = nullptr;
private:
void patch_descriptors();
};
class usb_device_emulated : public usb_device
+21 -1
View File
@@ -8,6 +8,8 @@
namespace gl
{
extern GLenum tex_min_filter(rsx::texture_minify_filter min_filter);
inline GLenum comparison_op(rsx::comparison_function op)
{
return static_cast<GLenum>(op);
@@ -410,8 +412,26 @@ void GLGSRender::load_texture_env()
if (is_depth_reconstructed || is_snorm)
{
GLint adjusted_min_filter = GL_NEAREST;
if (!is_depth_reconstructed) [[ unlikely ]]
{
switch (gl::tex_min_filter(tex.min_filter()))
{
default:
break;
case GL_LINEAR_MIPMAP_LINEAR:
case GL_LINEAR_MIPMAP_NEAREST:
case GL_NEAREST_MIPMAP_LINEAR:
case GL_NEAREST_MIPMAP_NEAREST:
// This is a hack and an unfortunate one at that as there is no feasible workaround.
// Doing full trilinear filtering in a shader is just dumb, approximate it instead with NEAREST_NEAREST.
adjusted_min_filter = GL_NEAREST_MIPMAP_NEAREST;
break;
}
}
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking.
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, adjusted_min_filter);
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
}
+2 -2
View File
@@ -1307,11 +1307,11 @@ void GLGSRender::do_local_task(rsx::FIFO::state state)
{
std::lock_guard lock(queue_guard);
work_queue.remove_if([](auto &q) { return q.received; });
work_queue.remove_if([](auto &q) { return q.received.load(); });
for (auto& q : work_queue)
{
if (q.processed) continue;
if (q.processed.load()) continue;
gl::command_context cmd{ gl_state };
q.result = m_gl_texture_cache.flush_all(cmd, q.section_data);
+6 -5
View File
@@ -6,6 +6,7 @@
#include "GLOverlays.h"
#include "GLShaderInterpreter.h"
#include "Emu/RSX/rsx_cache.h"
#include "util/asm.hpp"
#include <optional>
#include <unordered_map>
@@ -45,16 +46,16 @@ namespace gl
u32 address_to_flush = 0;
gl::texture_cache::thrashed_set section_data;
volatile bool processed = false;
atomic_t<bool> processed = false;
volatile bool result = false;
volatile bool received = false;
atomic_t<bool> received = false;
void producer_wait()
{
while (!processed)
utils::spin_wait(processed, [](auto v)
{
std::this_thread::yield();
}
return v;
});
received = true;
}
+2 -2
View File
@@ -45,7 +45,7 @@ namespace gl
return 1.0f;
}
int tex_min_filter(rsx::texture_minify_filter min_filter)
GLenum tex_min_filter(rsx::texture_minify_filter min_filter)
{
switch (min_filter)
{
@@ -60,7 +60,7 @@ namespace gl
fmt::throw_exception("Unknown min filter");
}
int tex_mag_filter(rsx::texture_magnify_filter mag_filter)
GLenum tex_mag_filter(rsx::texture_magnify_filter mag_filter)
{
switch (mag_filter)
{
+12 -1
View File
@@ -29,6 +29,7 @@ namespace rsx
RSX(ctx)->m_graphics_state |= rsx::pipeline_state::fragment_program_needs_rehash;
const auto& sema = vm::_ref<RsxSemaphore>(addr);
const auto& atomic_sema = vm::_ref<atomic_t<RsxSemaphore>>(addr);
if (sema == arg)
{
@@ -79,7 +80,17 @@ namespace rsx
}
}
RSX(ctx)->cpu_wait({});
if (RSX(ctx)->external_interrupt_lock ||
(RSX(ctx)->state & (cpu_flag::dbg_global_pause + cpu_flag::exit)) == cpu_flag::dbg_global_pause)
{
RSX(ctx)->cpu_wait({});
continue;
}
RSX(ctx)->on_semaphore_acquire_wait();
// Wait until the value changes or until 100us pass.
utils::spin_on_cacheline_once(atomic_sema, sema, 100);
}
RSX(ctx)->fifo_wake_delay();
+1
View File
@@ -457,6 +457,7 @@ void VKGSRender::load_texture_env()
{
mag_filter = VK_FILTER_NEAREST;
min_filter.filter = VK_FILTER_NEAREST;
min_filter.mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
if (min_filter.sample_mipmaps && mipmap_count > 1)
+6 -6
View File
@@ -254,18 +254,18 @@ namespace vk
void consumer_wait() const
{
while (num_waiters.load() != 0)
utils::spin_wait(num_waiters, [](auto v)
{
utils::pause();
}
return v == 0;
});
}
void producer_wait() const
{
while (pending_state.load())
utils::spin_wait(pending_state, [](auto v)
{
std::this_thread::yield();
}
return !v;
});
}
};
+1 -1
View File
@@ -696,7 +696,7 @@ namespace vk
void descriptor_table_t::create_descriptor_pool()
{
m_descriptor_pool = std::make_unique<descriptor_pool>();
m_descriptor_pool->create(*vk::g_render_device, m_descriptor_pool_sizes);
m_descriptor_pool->create(*vk::g_render_device, m_descriptor_pool_sizes, 16u, 4096u);
}
void descriptor_table_t::validate() const
-5
View File
@@ -148,11 +148,6 @@ namespace vk
vk_prog->binding_table.cbuf_location = location++;
}
if (vk::emulate_conditional_rendering())
{
vk_prog->binding_table.cr_pred_buffer_location = location++;
}
// Return next index
return location;
}
+68 -11
View File
@@ -110,18 +110,40 @@ namespace vk
}
}
void descriptor_pool::create(const vk::render_device& dev, const rsx::simple_array<VkDescriptorPoolSize>& pool_sizes, u32 max_sets)
u32 descriptor_pool::autoscaling_config_t::get_pool_size()
{
ensure(max_sets > 16);
if (current_size < min_pool_size)
{
current_size = min_pool_size;
return min_pool_size;
}
if (current_size >= max_pool_size)
{
current_size = max_pool_size;
return max_pool_size;
}
// Try grow
if ((increment_steps++) < (increment_min_steps - 1u))
{
return current_size;
}
increment_steps = 0u;
current_size = std::min(current_size * 2u, max_pool_size);
return current_size;
}
void descriptor_pool::create(const vk::render_device& dev, const rsx::simple_array<VkDescriptorPoolSize>& pool_sizes, u32 min_sets, u32 max_sets)
{
m_autoscaling_config.min_pool_size = std::max(min_sets, 16u);
m_autoscaling_config.max_pool_size = std::max(min_sets, max_sets);
ensure(m_autoscaling_config.max_pool_size >= 16u);
m_create_info_pool_sizes = pool_sizes;
for (auto& size : m_create_info_pool_sizes)
{
ensure(size.descriptorCount < 128); // Sanity check. Remove before commit.
size.descriptorCount *= max_sets;
}
m_create_info.flags = dev.get_descriptor_update_after_bind_support() ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT : 0;
m_create_info.maxSets = max_sets;
m_create_info.poolSizeCount = m_create_info_pool_sizes.size();
@@ -186,7 +208,7 @@ namespace vk
if (use_cache)
{
const auto alloc_size = std::min<u32>(m_create_info.maxSets - m_current_subpool_offset, max_cache_size);
const auto alloc_size = std::min<u32>(max_sets() - m_current_subpool_offset, max_cache_size);
m_allocation_request_cache.resize(alloc_size);
for (auto& layout_ : m_allocation_request_cache)
{
@@ -243,8 +265,8 @@ namespace vk
}
}
VkDescriptorPool subpool = VK_NULL_HANDLE;
if (VkResult result = vkCreateDescriptorPool(*m_owner, &m_create_info, nullptr, &subpool))
const auto [result, subpool] = new_subpool();
if (result != VK_SUCCESS)
{
if (retries-- && (result == VK_ERROR_FRAGMENTATION_EXT))
{
@@ -263,6 +285,7 @@ namespace vk
m_device_subpools.push_back(
{
.handle = subpool,
.size = m_autoscaling_config.current_size,
.busy = VK_FALSE
});
@@ -275,6 +298,40 @@ namespace vk
m_current_pool_handle = m_device_subpools[m_current_subpool_index].handle;
}
std::pair<VkResult, VkDescriptorPool> descriptor_pool::new_subpool()
{
// Try autoscaling
const auto prev_scaling_config = m_autoscaling_config;
const u32 set_count = m_autoscaling_config.get_pool_size();
// Configure request using current pool size
auto descriptor_pool_sizes = m_create_info_pool_sizes.map([set_count](const VkDescriptorPoolSize& pool_size_info)
{
auto ret = pool_size_info;
ret.descriptorCount *= set_count;
return ret;
});
m_create_info.maxSets = set_count;
m_create_info.poolSizeCount = descriptor_pool_sizes.size();
m_create_info.pPoolSizes = descriptor_pool_sizes.data();
VkDescriptorPool subpool = VK_NULL_HANDLE;
VkResult result = vkCreateDescriptorPool(*m_owner, &m_create_info, nullptr, &subpool);
if (result != VK_SUCCESS)
{
// Roll back autoscaling
m_autoscaling_config = prev_scaling_config;
}
// Cleanup
m_create_info.pPoolSizes = nullptr;
m_create_info.poolSizeCount = 0;
return { result, subpool };
}
descriptor_set::descriptor_set(VkDescriptorSet set)
{
flush();
+22 -5
View File
@@ -39,28 +39,45 @@ namespace vk
descriptor_pool() = default;
~descriptor_pool() = default;
void create(const vk::render_device& dev, const rsx::simple_array<VkDescriptorPoolSize>& pool_sizes, u32 max_sets = 1024);
void create(const vk::render_device& dev, const rsx::simple_array<VkDescriptorPoolSize>& pool_sizes, u32 min_sets = 1024, u32 max_sets = 1024);
void destroy();
VkDescriptorSet allocate(VkDescriptorSetLayout layout, VkBool32 use_cache = VK_TRUE);
operator VkDescriptorPool() { return m_current_pool_handle; }
FORCE_INLINE bool valid() const { return !m_device_subpools.empty(); }
FORCE_INLINE u32 max_sets() const { return m_create_info.maxSets; }
FORCE_INLINE u32 max_sets() const { return m_current_subpool_index >= m_device_subpools.size() ? 0u : m_device_subpools[m_current_subpool_index].size; }
private:
FORCE_INLINE bool can_allocate(u32 required_count, u32 already_used_count = 0) const { return (required_count + already_used_count) <= m_create_info.maxSets; };
FORCE_INLINE bool can_allocate(u32 required_count, u32 already_used_count = 0) const { return (required_count + already_used_count) <= max_sets(); };
void reset(u32 subpool_id, VkDescriptorPoolResetFlags flags);
void next_subpool();
std::pair<VkResult, VkDescriptorPool> new_subpool();
struct logical_subpool_t
{
VkDescriptorPool handle;
VkBool32 busy;
VkDescriptorPool handle = VK_NULL_HANDLE;
u32 size = 0;
VkBool32 busy = VK_FALSE;
};
struct autoscaling_config_t
{
u32 min_pool_size = 0;
u32 max_pool_size = 0;
u32 current_size = 0;
// Debounce setup.
static constexpr u32 increment_min_steps = 2u;
u32 increment_steps = 0;
u32 get_pool_size();
};
const vk::render_device* m_owner = nullptr;
VkDescriptorPoolCreateInfo m_create_info = {};
autoscaling_config_t m_autoscaling_config = {};
rsx::simple_array<VkDescriptorPoolSize> m_create_info_pool_sizes;
rsx::simple_array<logical_subpool_t> m_device_subpools;
+1
View File
@@ -295,6 +295,7 @@ struct cfg_root : cfg::node
cfg::_bool lock_overlay_input_to_player_one{this, "Lock overlay input to player one", false, true};
cfg::string midi_devices{this, "Emulated Midi devices", "Keyboardßßß@@@Keyboardßßß@@@Keyboardßßß@@@"};
cfg::_bool load_sdl_mappings{ this, "Load SDL GameController Mappings", true };
cfg::_bool mouse_based_gyro_enabled{ this, "Mouse-based gyro enabled", false, true };
cfg::_bool pad_debug_overlay{ this, "IO Debug overlay", false, true };
cfg::_bool mouse_debug_overlay{ this, "Mouse Debug overlay", false, true };
cfg::uint<1, 180> fake_move_rotation_cone_h{ this, "Fake Move Rotation Cone", 10, true };
+6
View File
@@ -26,6 +26,12 @@ bool mouse_gyro_handler::toggle_enabled()
return m_enabled;
}
void mouse_gyro_handler::set_enabled(bool enabled)
{
m_enabled = enabled;
clear();
}
void mouse_gyro_handler::set_gyro_active()
{
gui_log.notice("Mouse-based gyro activated");
+1
View File
@@ -13,6 +13,7 @@ class mouse_gyro_handler
public:
void clear();
bool toggle_enabled();
void set_enabled(bool enabled);
void handle_event(QEvent* ev, const QWindow& win);
void apply_gyro(const std::shared_ptr<Pad>& pad);
+1 -1
View File
@@ -82,7 +82,7 @@ void pad_thread::Init()
std::lock_guard lock(pad::g_pad_mutex);
// Reset mouse-based gyro state
m_mouse_gyro.clear();
m_mouse_gyro.set_enabled(g_cfg.io.mouse_based_gyro_enabled.get());
// Cache old settings if possible
std::array<pad_setting, CELL_PAD_MAX_PORT_NUM> pad_settings;
+2 -2
View File
@@ -302,7 +302,7 @@ bool config_checker::check_config(cfg_mode mode, QString content_or_serial, QStr
const std::array<midi_device, max_midi_devices> def_devices = mc.get_selection_list();
mc.parse_devices(val);
const std::array<midi_device, max_midi_devices> devices = mc.get_selection_list();
const std::array<midi_device, max_midi_devices>& devices = mc.get_selection_list();
for (usz i = 0; i < devices.size(); i++)
{
@@ -327,7 +327,7 @@ bool config_checker::check_config(cfg_mode mode, QString content_or_serial, QStr
const std::array<std::string, 4> def_devices = mc.get_selection_list();
mc.parse_devices(val);
const std::array<std::string, 4> devices = mc.get_selection_list();
const std::array<std::string, 4>& devices = mc.get_selection_list();
for (usz i = 0; i < devices.size(); i++)
{
+1
View File
@@ -186,6 +186,7 @@ const std::map<emu_settings_type, cfg_location> settings_location =
{ emu_settings_type::GHLtar, get_cfg_location(local_cfg.io.ghltar) },
{ emu_settings_type::MidiDevices, get_cfg_location(local_cfg.io.midi_devices) },
{ emu_settings_type::SDLMappings, get_cfg_location(local_cfg.io.load_sdl_mappings) },
{ emu_settings_type::MouseBasedGyro, get_cfg_location(local_cfg.io.mouse_based_gyro_enabled) },
{ emu_settings_type::IoDebugOverlay, get_cfg_location(local_cfg.io.pad_debug_overlay) },
{ emu_settings_type::MouseDebugOverlay, get_cfg_location(local_cfg.io.mouse_debug_overlay) },
+1
View File
@@ -178,6 +178,7 @@ enum class emu_settings_type
GHLtar,
MidiDevices,
SDLMappings,
MouseBasedGyro,
IoDebugOverlay,
MouseDebugOverlay,
@@ -547,6 +547,7 @@ void emulated_logitech_g27_settings_dialog::save_ui_state_to_config()
g_cfg_logitech_g27.enabled.set(m_enabled->isChecked());
g_cfg_logitech_g27.reverse_effects.set(m_reverse_effects->isChecked());
g_cfg_logitech_g27.ffb_direction_type.set(static_cast<g27_ffb_direction_type>(m_ffb_direction_type->currentData().toInt()));
g_cfg_logitech_g27.compatibility_limit.set(m_compatibility_limit->currentData().toInt());
if (m_ffb_device->get_device_choice() == mapping_device::NONE)
@@ -597,6 +598,7 @@ void emulated_logitech_g27_settings_dialog::load_ui_state_from_config()
m_enabled->setChecked(g_cfg_logitech_g27.enabled.get());
m_reverse_effects->setChecked(g_cfg_logitech_g27.reverse_effects.get());
m_ffb_direction_type->setCurrentIndex(m_ffb_direction_type->findData(static_cast<int>(g_cfg_logitech_g27.ffb_direction_type.get())));
m_compatibility_limit->setCurrentIndex(4 - g_cfg_logitech_g27.compatibility_limit.get());
}
@@ -658,6 +660,18 @@ emulated_logitech_g27_settings_dialog::emulated_logitech_g27_settings_dialog(QWi
m_reverse_effects = new QCheckBox(tr("Reverse force feedback effects"), this);
v_layout->addWidget(m_reverse_effects);
QHBoxLayout* ffb_dir_layout = new QHBoxLayout(this);
ffb_dir_layout->setContentsMargins(0, 0, 0, 0);
QLabel* ffb_dir_label = new QLabel(tr("Force feedback direction encoding:"), this);
ffb_dir_layout->addWidget(ffb_dir_label);
m_ffb_direction_type = new QComboBox(this);
m_ffb_direction_type->addItem(tr("Steering Axis (Default)"), static_cast<int>(g27_ffb_direction_type::steering_axis));
m_ffb_direction_type->addItem(tr("Cartesian"), static_cast<int>(g27_ffb_direction_type::cartesian));
m_ffb_direction_type->addItem(tr("Polar"), static_cast<int>(g27_ffb_direction_type::polar));
m_ffb_direction_type->setToolTip(tr("Selects the direction encoding used for force feedback effects on the host wheel.\nSteering Axis is SDL's recommended encoding for steering wheels and the safest default.\nSwitch to Cartesian or Polar only if your wheel reports no force feedback with the default."));
ffb_dir_layout->addWidget(m_ffb_direction_type);
v_layout->addLayout(ffb_dir_layout);
QHBoxLayout* compat_layout = new QHBoxLayout(this);
compat_layout->setContentsMargins(0, 0, 0, 0);
QLabel* compatibility_label = new QLabel(tr("Compatibility limit:"), this);
@@ -880,6 +894,7 @@ void emulated_logitech_g27_settings_dialog::set_enable(bool enable)
m_enabled->setEnabled(enable);
m_reverse_effects->setEnabled(enable);
m_ffb_direction_type->setEnabled(enable);
m_ffb_device->set_enable(enable);
m_led_device->set_enable(enable);
@@ -58,6 +58,7 @@ private:
QCheckBox* m_enabled = nullptr;
QCheckBox* m_reverse_effects = nullptr;
QComboBox* m_ffb_direction_type = nullptr;
QComboBox* m_compatibility_limit = nullptr;
std::map<mapping_device, Mapping*> m_mappings;
+3 -3
View File
@@ -14,7 +14,7 @@ microphone_creator::microphone_creator()
}
// We need to recreate the localized string because the microphone creator is currently only created once.
QString microphone_creator::get_none()
QString microphone_creator::get_none() const
{
return tr("None", "Microphone device");
}
@@ -49,12 +49,12 @@ void microphone_creator::refresh_list()
}
}
QStringList microphone_creator::get_microphone_list() const
const QStringList& microphone_creator::get_microphone_list() const
{
return m_microphone_list;
}
std::array<std::string, 4> microphone_creator::get_selection_list() const
const std::array<std::string, 4>& microphone_creator::get_selection_list() const
{
return m_sel_list;
}
+3 -3
View File
@@ -15,12 +15,12 @@ class microphone_creator : public QObject
public:
microphone_creator();
QString get_none();
QString get_none() const;
std::string set_device(u32 num, const QString& text);
void parse_devices(std::string_view list);
void refresh_list();
QStringList get_microphone_list() const;
std::array<std::string, 4> get_selection_list() const;
const QStringList& get_microphone_list() const;
const std::array<std::string, 4>& get_selection_list() const;
private:
QStringList m_microphone_list;
+38 -5
View File
@@ -8,24 +8,57 @@
LOG_CHANNEL(cfg_log, "CFG");
const auto midi_deleter = [](RtMidiWrapper* ptr) { if (ptr) rtmidi_in_free(ptr); };
using midi_ptr = std::unique_ptr<RtMidiWrapper, decltype(midi_deleter)>;
std::mutex midi_creator::m_midi_init_mutex = {};
std::unique_ptr<std::thread> midi_creator::m_midi_init_thread = {};
midi_creator::midi_creator()
{
setObjectName("midi_creator");
// Initialize rtmidi async. This can take 10+ seconds on a cold start.
std::lock_guard lock(m_midi_init_mutex);
if (!m_midi_init_thread)
{
m_midi_init_thread = std::make_unique<std::thread>([]
{
[[maybe_unused]] midi_ptr midi_in(rtmidi_in_create_default());
});
}
}
midi_creator::~midi_creator()
{
std::lock_guard lock(m_midi_init_mutex);
if (m_midi_init_thread && m_midi_init_thread->joinable())
{
m_midi_init_thread->join();
}
}
// We need to recreate the localized string because the midi creator is currently only created once.
QString midi_creator::get_none()
QString midi_creator::get_none() const
{
return tr("None", "MIDI device");
}
void midi_creator::refresh_list()
{
// Wait for initial initialization
{
std::lock_guard lock(m_midi_init_mutex);
if (m_midi_init_thread && m_midi_init_thread->joinable())
{
m_midi_init_thread->join();
}
}
m_midi_list.clear();
m_midi_list.append(get_none());
const auto deleter = [](RtMidiWrapper* ptr) { if (ptr) rtmidi_in_free(ptr); };
std::unique_ptr<RtMidiWrapper, decltype(deleter)> midi_in(rtmidi_in_create_default());
midi_ptr midi_in(rtmidi_in_create_default());
ensure(midi_in);
if (!midi_in->ok)
@@ -74,12 +107,12 @@ void midi_creator::refresh_list()
}
}
QStringList midi_creator::get_midi_list() const
const QStringList& midi_creator::get_midi_list() const
{
return m_midi_list;
}
std::array<midi_device, max_midi_devices> midi_creator::get_selection_list() const
const std::array<midi_device, max_midi_devices>& midi_creator::get_selection_list() const
{
return m_sel_list;
}
+10 -3
View File
@@ -3,6 +3,9 @@
#include "util/types.hpp"
#include "Emu/Io/midi_config_types.h"
#include <mutex>
#include <thread>
#include <QObject>
#include <QStringList>
@@ -12,14 +15,18 @@ class midi_creator : public QObject
public:
midi_creator();
QString get_none();
virtual ~midi_creator();
QString get_none() const;
std::string set_device(u32 num, const midi_device& device);
void parse_devices(std::string_view list);
void refresh_list();
QStringList get_midi_list() const;
std::array<midi_device, max_midi_devices> get_selection_list() const;
const QStringList& get_midi_list() const;
const std::array<midi_device, max_midi_devices>& get_selection_list() const;
private:
static std::mutex m_midi_init_mutex;
static std::unique_ptr<std::thread> m_midi_init_thread;
QStringList m_midi_list;
std::array<midi_device, max_midi_devices> m_sel_list;
};
+17 -6
View File
@@ -933,8 +933,11 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
QStringList cur_list = m_emu_settings->m_microphone_creator.get_microphone_list();
for (u32 subindex = 0; subindex < m_mics_combo.size(); subindex++)
{
if (subindex != index && m_mics_combo[subindex]->currentText() != mic_none)
cur_list.removeOne(m_mics_combo[subindex]->currentText());
if (subindex == index) continue;
if (const QString text = m_mics_combo[subindex]->currentText(); text != mic_none)
{
cur_list.removeOne(text);
}
}
m_mics_combo[index]->blockSignals(true);
m_mics_combo[index]->clear();
@@ -949,7 +952,9 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
{
m_emu_settings->SetSetting(emu_settings_type::MicrophoneDevices, m_emu_settings->m_microphone_creator.set_device(index, text));
if (const u32 next_index = index + 1; next_index < m_mics_combo.size() && text == mic_none)
{
m_mics_combo[next_index]->setCurrentText(mic_none);
}
propagate_used_devices();
};
@@ -1102,7 +1107,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
m_emu_settings->m_microphone_creator.parse_devices(m_emu_settings->GetSetting(emu_settings_type::MicrophoneDevices));
const std::array<std::string, 4> mic_sel_list = m_emu_settings->m_microphone_creator.get_selection_list();
const std::array<std::string, 4>& mic_sel_list = m_emu_settings->m_microphone_creator.get_selection_list();
for (s32 index = static_cast<int>(mic_sel_list.size()) - 1; index >= 0; index--)
{
@@ -1243,6 +1248,9 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
ui->loadSdlMappings->setVisible(false);
#endif
m_emu_settings->EnhanceCheckBox(ui->mouseBasedGyroBox, emu_settings_type::MouseBasedGyro);
SubscribeTooltip(ui->mouseBasedGyroBox, tooltips.settings.mouse_based_gyro);
#ifndef _WIN32
// Remove raw mouse handler
remove_item(ui->mouseHandlerBox, static_cast<int>(mouse_handler::raw), static_cast<int>(g_cfg.io.mouse.def));
@@ -1273,8 +1281,11 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
QStringList cur_list = m_emu_settings->m_midi_creator.get_midi_list();
for (u32 subindex = 0; subindex < m_midi_device_combo.size(); subindex++)
{
if (subindex != index && m_midi_device_combo[subindex]->currentText() != midi_none)
cur_list.removeOne(m_midi_device_combo[subindex]->currentText());
if (subindex == index) continue;
if (const QString text = m_midi_device_combo[subindex]->currentText(); text != midi_none)
{
cur_list.removeOne(text);
}
}
m_midi_device_combo[index]->blockSignals(true);
m_midi_device_combo[index]->clear();
@@ -1323,7 +1334,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
m_emu_settings->m_midi_creator.parse_devices(m_emu_settings->GetSetting(emu_settings_type::MidiDevices));
const std::array<midi_device, max_midi_devices> midi_sel_list = m_emu_settings->m_midi_creator.get_selection_list();
const std::array<midi_device, max_midi_devices>& midi_sel_list = m_emu_settings->m_midi_creator.get_selection_list();
for (s32 index = static_cast<int>(midi_sel_list.size()) - 1; index >= 0; index--)
{
+7
View File
@@ -1841,6 +1841,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mouseBasedGyroBox">
<property name="text">
<string>Enable Mouse-based Gyro</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacerIoAdditionalSettings">
<property name="orientation">
+1
View File
@@ -255,6 +255,7 @@ public:
const QString show_move_cursor = tr("Shows the raw position of the PS Move input.\nThis can be very helpful during calibration screens.");
const QString midi_devices = tr("Select up to 3 emulated MIDI devices and their types.");
const QString sdl_mappings = tr("Loads the SDL GameController database for improved gamepad compatibility. Only used in the SDL pad handler.");
const QString mouse_based_gyro = tr("Enables mouse-based gyro emulation at game startup. It can also be toggled at any time with the associated hotkey.\nHold the right mouse button to activate gyro input: moving the mouse maps to the X and Z motion axes, and the scroll wheel maps to the Y axis. Release the button to reset the motion values.");
const QString lock_overlay_input_to_player_one = tr("Locks the native overlay input to the first player.");
+197
View File
@@ -3,11 +3,14 @@
#include "util/types.hpp"
#include "util/tsc.hpp"
#include "util/atomic.hpp"
#include "util/sysinfo.hpp"
#include <functional>
#include <thread>
#ifdef ARCH_X64
#ifdef _MSC_VER
#include <intrin.h>
#include <immintrin.h>
#else
#include <immintrin.h>
#include <x86intrin.h>
@@ -216,6 +219,200 @@ namespace utils
while (get_tsc() < stop);
}
#ifdef ARCH_X64
inline u64 get_wait_cycles(u64 timeout_us, u64 tsc_freq)
{
constexpr u64 max_timeout = u64{umax};
if (!tsc_freq)
{
return 0;
}
if (timeout_us == max_timeout)
{
return max_timeout;
}
const u64 seconds = timeout_us / 1'000'000;
const u64 micros = timeout_us % 1'000'000;
if (seconds > max_timeout / tsc_freq)
{
return max_timeout;
}
const u64 sec_cycles = seconds * tsc_freq;
const u64 cycles_per_us = tsc_freq / 1'000'000;
if (micros && cycles_per_us > max_timeout / micros)
{
return max_timeout;
}
const u64 us_cycles = micros * cycles_per_us + (micros * (tsc_freq % 1'000'000)) / 1'000'000;
return sec_cycles > max_timeout - us_cycles ? max_timeout : sec_cycles + us_cycles;
}
#endif
template <typename T, usz Align>
#if defined(ARCH_X64) && !defined(_MSC_VER)
__attribute__((target("waitpkg,mwaitx")))
#endif
inline void spin_on_cacheline_once(const atomic_t<T, Align>& var, T old_value, u64 timeout_us)
{
const void* addr = &var.raw();
#if defined(ARCH_ARM64)
// WFE will wake from the periodic event stream, so the explicit timeout is ignored on ARM.
(void)timeout_us;
using wait_type = std::remove_cvref_t<decltype(var.raw())>;
using raw_type = std::conditional_t<sizeof(wait_type) == 8, u64,
std::conditional_t<sizeof(wait_type) == 4, u32,
std::conditional_t<sizeof(wait_type) == 2, u16, u8>>>;
static_assert(sizeof(wait_type) <= 8, "Unsupported atomic size for spin_on_cacheline_once");
raw_type value{};
const auto* wait_addr = static_cast<const volatile raw_type*>(addr);
if constexpr (sizeof(raw_type) == 1) __asm__ volatile("ldaxrb %w0, %1" : "=r"(value) : "Q"(*wait_addr) : "memory");
else if constexpr (sizeof(raw_type) == 2) __asm__ volatile("ldaxrh %w0, %1" : "=r"(value) : "Q"(*wait_addr) : "memory");
else if constexpr (sizeof(raw_type) == 4) __asm__ volatile("ldaxr %w0, %1" : "=r"(value) : "Q"(*wait_addr) : "memory");
else if constexpr (sizeof(raw_type) == 8) __asm__ volatile("ldaxr %x0, %1" : "=r"(value) : "Q"(*wait_addr) : "memory");
if (std::bit_cast<wait_type>(value) != old_value)
{
__asm__ volatile("clrex" ::: "memory");
return;
}
__asm__ volatile("wfe" ::: "memory");
__asm__ volatile("clrex" ::: "memory");
#elif defined(ARCH_X64)
static const bool use_umwait = has_waitpkg();
static const bool use_waitx = has_waitx();
const u64 cycles = get_wait_cycles(timeout_us, get_tsc_freq());
if (use_umwait && cycles)
{
_umonitor(const_cast<void*>(addr));
if (var.load() != old_value)
{
return;
}
constexpr u64 max_timeout = u64{umax};
const u64 now = get_tsc();
const u64 deadline = cycles > max_timeout - now ? max_timeout : now + cycles;
_umwait(0, deadline);
}
else if (use_waitx && cycles)
{
_mm_monitorx(const_cast<void*>(addr), 0, 0);
if (var.load() != old_value)
{
return;
}
constexpr u32 timer_enable = 2;
_mm_mwaitx(timer_enable, 0, cycles > u32{umax} ? u32{umax} : static_cast<u32>(cycles));
}
else
{
std::this_thread::yield();
}
#else
(void)addr;
(void)old_value;
(void)timeout_us;
std::this_thread::yield();
#endif
}
template <typename T, usz Align, typename Pred>
#if defined(ARCH_X64) && !defined(_MSC_VER)
__attribute__((target("waitpkg,mwaitx")))
#endif
inline void spin_wait(const atomic_t<T, Align>& var, Pred predicate)
{
#ifdef ARCH_X64
static const bool use_umwait = has_waitpkg();
static const bool use_waitx = has_waitx();
#endif
const auto read_mem = [&]()
{
return var.load();
};
const void* addr = &var.raw();
while (true)
{
if (predicate(read_mem()))
{
return;
}
#if defined(ARCH_ARM64)
using value_type = decltype(read_mem());
using wait_type = std::remove_cvref_t<decltype(var.raw())>;
wait_type value{};
const auto* wait_addr = static_cast<const volatile wait_type*>(addr);
if constexpr (sizeof(wait_type) == 1) __asm__ volatile("ldaxrb %w0, %1" : "=r"(value) : "Q"(*wait_addr) : "memory");
else if constexpr (sizeof(wait_type) == 2) __asm__ volatile("ldaxrh %w0, %1" : "=r"(value) : "Q"(*wait_addr) : "memory");
else if constexpr (sizeof(wait_type) == 4) __asm__ volatile("ldaxr %w0, %1" : "=r"(value) : "Q"(*wait_addr) : "memory");
else if constexpr (sizeof(wait_type) == 8) __asm__ volatile("ldaxr %x0, %1" : "=r"(value) : "Q"(*wait_addr) : "memory");
else static_assert(sizeof(wait_type) <= 8, "Unsupported atomic size for spin_wait");
if (predicate(static_cast<value_type>(value)))
{
__asm__ volatile("clrex" ::: "memory");
return;
}
__asm__ volatile("wfe" ::: "memory");
__asm__ volatile("clrex" ::: "memory");
#elif defined(ARCH_X64)
if (use_umwait)
{
_umonitor(const_cast<void*>(addr));
if (predicate(read_mem()))
{
return;
}
_umwait(0, ~0ULL);
}
else if (use_waitx)
{
_mm_monitorx(const_cast<void*>(addr), 0, 0);
if (predicate(read_mem()))
{
return;
}
_mm_mwaitx(0, 0, 0);
}
else
{
pause();
}
#else
pause();
#endif
}
}
// Align to power of 2
template <typename T, typename U>
requires std::is_unsigned_v<T>
+5
View File
@@ -1742,6 +1742,11 @@ public:
return base::load() != 0;
}
const uchar& raw() const
{
return base::raw();
}
// Override implicit conversion from the parent type
explicit operator uchar() const = delete;
+42 -3
View File
@@ -6,6 +6,7 @@
#if defined(ARCH_ARM64)
#include "Emu/CPU/Backends/AArch64/AArch64Common.h"
#include <arm_sve.h>
#endif
#ifdef _WIN32
#include "windows.h"
@@ -425,6 +426,26 @@ bool utils::has_dotprod()
return g_value;
}
bool utils::has_i8mm()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP2) & HWCAP2_I8MM) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_I8MM", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE) != 0;
#else
return false;
#endif
}();
return g_value;
}
bool utils::has_sve()
{
static const bool g_value = []() -> bool
@@ -461,6 +482,19 @@ bool utils::has_sve2()
return g_value;
}
#if defined(_MSC_VER)
#define sve_func
#else
#define sve_func __attribute__((__target__("+sve")))
#endif
// svcntb returns sve length in bytes, our function retuns length in bits
sve_func int utils::sve_length()
{
static const int g_value = static_cast<int>(svcntb() * 8);
return g_value;
}
#endif
std::string utils::get_cpu_brand()
@@ -517,13 +551,18 @@ std::string utils::get_system_info()
}
#ifdef ARCH_ARM64
if (has_neon())
if (!has_neon())
{
result += " | Neon";
fmt::throw_exception("Neon support not present");
}
if (has_sve())
{
fmt::append(result, " | SVE%s-%d", has_sve2() ? "2" : "", sve_length());
}
else
{
fmt::throw_exception("Neon support not present");
result += " | Neon";
}
#else
+4
View File
@@ -61,9 +61,13 @@ namespace utils
bool has_dotprod();
bool has_i8mm();
bool has_sve();
bool has_sve2();
int sve_length();
#endif
std::string get_cpu_brand();