Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6ea921e0d | |||
| 339877ea7d | |||
| ba9464c547 | |||
| e7165cbe99 | |||
| d85f1c7155 | |||
| 9e2c41bd48 | |||
| 2d1be09180 | |||
| d25972e196 | |||
| 62d32ab45e | |||
| 1ca995cc96 | |||
| 3164d44952 | |||
| 546f3082a2 | |||
| 8d9abd396b | |||
| 8112c7cc19 | |||
| bf85a3fdd9 | |||
| 8e370c2cf1 | |||
| 6349ea2ee9 | |||
| 2f2ac69d60 | |||
| 4a92d96cf4 | |||
| 9a2243bfcb | |||
| 97704e35e0 |
@@ -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 }}
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/FAudio updated: 14f2875e27...75c79d4c8a
Vendored
+1
-1
Submodule 3rdparty/libsdl-org/SDL updated: d9d5536704...8e37db5e79
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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) },
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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--)
|
||||
{
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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.");
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user