Compare commits

...

9 Commits

Author SHA1 Message Date
lizzie a99cc426d6 [shader_recompiler] add 'legacy descriptor indices' toggle for tomodachi
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-05-16 14:34:48 +00:00
lizzie 4d49341918 [vk, opengl] recognize and use ETC2 (if available) textures natively (#3237)
this makes it so VK and OGL backends map the NVIDIA's ETC2 into VK_FORMAT_ETC-whatever and GL_ETC-whatever remaps, instead of using the default fallback for AR8G8B8. in short, just make the ETC2 textures be submitted as ETC2 instead of being submit as A8R8G8B8.

Signed-off-by: lizzie lizzie@eden-emu.dev

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3237
Reviewed-by: Ghost <>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-15 22:08:09 +02:00
lizzie 2f0f8a979c [dynarmic, macroHLE] Use faster ankerl for xbyak maps (#3716)
the nominal std::unordered_map<> isn't enough to warrant it's continued usage in xbyak internal structures, thus using ankerl should greatly remove a lot of indirection/stdc++ specific overhead from the usually poorly performant std::unordered_map

Both dynarmic and macroHLE should benefit greatly from a less-stupid unordered_dense

This should speedup both CPU and shader compilation latency (NOT BY A GREAT MARGIN) just enough to make loading zones in ToTK less horrific

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3716
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-15 22:07:45 +02:00
lizzie 413c7543ba [hle] inline HLE cmif request to not allocate on heap stuff (#3605)
so basically each construction of HLEContext and whatever would result in a heap allocation (atleast 1)

so what if instead of that we did a memset() at ctor time and we avoided heap allocations altogether?

reminder that std::vector<> CAN do small object optimisation but it's not guaranteed

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3605
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-15 22:07:03 +02:00
lizzie 975aa4e2f2 [common] remove ptr indirection on WallClock (#3864)
also devirtualizes manually since compiler doesn't do it with LTO

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3864
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-15 22:06:38 +02:00
lizzie a1f9e68f46 [hid_core] remove contentious mutex from EmulatedController and just rely on atomic semantics for fields (#3866)
inputs shouldnt be that critical to require a full mutex of them

this relies on CPU guaranteeing u32/u16/u8 atomic load/stores for EmulatedController fields, which works on x86_64 but may not have the same behaviour on other architectures - thats why i wrap them in `std::atomic<>`

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3866
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-15 22:06:23 +02:00
lizzie 02dee4a20b [file_sys/system_archive] remove uneeded ctor/dtor initializations for std::map<> when creating system archives for nx_tzdb generated files (#3919)
sounds like word salad but let me say:

- std::map<> created a static ctor for EVERY SINGLE ZONEINFO
- fuck that, instead lets just use a raw array and construct things statically
- works the same except with less baggage carried around (+ less heap allocations!!!)

this should help reduce codesize due to the aforementioned global ctor/dtor

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3919
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-15 22:05:32 +02:00
lizzie bc9b9480fb [dynarmic] fix 12th-gen Intel CPUs crashing due to UMONITOR (#3954)
see https://github.com/herumi/xbyak/issues/255

> Proof: https://godbolt.org/z/9vseq4Ynj
> Xbyak currently implements it as:
> ```c++
> void umonitor(const Reg& r) {
> int idx = r.getIdx();
> if (idx > 7) XBYAK_THROW(ERR_BAD_PARAMETER) //umonitor DOES accept r8,r9,r10,etc this is NOT correct
> int bit = r.getBit();
> if (BIT != bit) {
>   if ((BIT == 32 && bit == 16) || (BIT == 64 && bit == 32)) {
>     db(0x67);
>   } else {
>     XBYAK_THROW(ERR_BAD_SIZE_OF_REGISTER)
>   }
> }
> db(0xF3); db(0x0F); db(0xAE); setModRM(3, 6, idx);
> }
> ```
> My program was throwing Xbyak::Exception and I tracked it down to this particular umonitor

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3954
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-05-15 22:01:42 +02:00
lizzie d1ceeeca22 [cmake] use -mtls-dialect=gnu2 (#3948)
see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120933

we use TLS very sparingly (which is a good thing), some of our dependencies, in turn, may not
we should be aware of that fact

allegedly, there are minor glibc issues and such, but most distros should be fine
additionally, this is only enabled for FreeBSD and Linux, if it works on FreeBSD, naturally every Linux distro should support it as well

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3948
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-05-14 00:17:13 +02:00
44 changed files with 661 additions and 990 deletions
+21 -6
View File
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
@@ -16,15 +19,24 @@ if (NOT FILE_LIST)
endif()
set(DIRECTORY_NAME ${HEADER_NAME})
set(FILE_DATA "")
string(APPEND FILE_DATA "[[nodiscard]] static inline std::vector<FileSys::VirtualFile> CollectFiles_${DIRECTORY_NAME}() {\n")
string(APPEND FILE_DATA [[
std::vector<FileSys::VirtualFile> vfs_files;
auto const fn = [&](std::string_view name, std::span<const u8> data) {
vfs_files.push_back(std::make_shared<FileSys::VectorVfsFile>(
std::vector<u8>(data.begin(), data.end()),
std::string{name}
));
};
]])
foreach(ZONE_FILE ${FILE_LIST})
if (ZONE_FILE STREQUAL "\n")
continue()
endif()
string(APPEND FILE_DATA "{\"${ZONE_FILE}\",\n{")
string(APPEND FILE_DATA " {\n")
string(APPEND FILE_DATA " constexpr uint8_t tzdb_data[] = {\n")
file(READ ${ZONE_PATH}/${ZONE_FILE} ZONE_DATA HEX)
string(LENGTH "${ZONE_DATA}" ZONE_DATA_LEN)
foreach(I RANGE 0 ${ZONE_DATA_LEN} 2)
@@ -42,9 +54,12 @@ foreach(ZONE_FILE ${FILE_LIST})
string(APPEND FILE_DATA " ")
endif()
endforeach()
string(APPEND FILE_DATA "}},\n")
string(APPEND FILE_DATA " };\n")
string(APPEND FILE_DATA " fn(\"${ZONE_FILE}\", tzdb_data);\n")
string(APPEND FILE_DATA " }\n")
endforeach()
string(APPEND FILE_DATA " return vfs_files;\n")
string(APPEND FILE_DATA "}\n")
file(READ ${NX_TZDB_SOURCE_DIR}/tzdb_template.h.in NX_TZDB_TEMPLATE_H_IN)
file(CONFIGURE OUTPUT ${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h CONTENT "${NX_TZDB_TEMPLATE_H_IN}")
+3 -3
View File
@@ -9,10 +9,10 @@
namespace NxTzdb {
// @DIRECTORY_NAME@
// clang-format off
const static std::map<const char*, const std::vector<uint8_t>> @DIRECTORY_NAME@ =
{
@FILE_DATA@};
@FILE_DATA@
// clang-format on
} // namespace NxTzdb
+4 -1
View File
@@ -159,7 +159,10 @@ else()
endif()
if (ARCHITECTURE_x86_64)
add_compile_options(-mcx16)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
endif()
endif()
if (APPLE AND CXX_CLANG)
+1 -8
View File
@@ -184,19 +184,12 @@ if(ARCHITECTURE_x86_64)
x64/cpu_detect.h
x64/cpu_wait.cpp
x64/cpu_wait.h
x64/native_clock.cpp
x64/native_clock.h
x64/rdtsc.cpp
x64/rdtsc.h
x64/xbyak_abi.h
x64/xbyak_util.h)
x64/xbyak.h)
target_link_libraries(common PRIVATE xbyak::xbyak)
endif()
if(HAS_NCE)
target_sources(common PRIVATE arm64/native_clock.cpp arm64/native_clock.h)
endif()
if(MSVC)
target_compile_definitions(
common
-87
View File
@@ -1,87 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef ANDROID
#include <sys/system_properties.h>
#endif
#include "common/arm64/native_clock.h"
namespace Common::Arm64 {
namespace {
NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
return (static_cast<NativeClock::FactorType>(num) << 64) / den;
}
u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
return static_cast<u64>((m * factor) >> 64);
}
} // namespace
NativeClock::NativeClock() {
const u64 host_cntfrq = GetHostCNTFRQ();
ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
}
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)};
}
std::chrono::microseconds NativeClock::GetTimeUS() const {
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)};
}
std::chrono::milliseconds NativeClock::GetTimeMS() const {
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)};
}
s64 NativeClock::GetCNTPCT() const {
return MultiplyHigh(GetUptime(), guest_cntfrq_factor);
}
s64 NativeClock::GetGPUTick() const {
return MultiplyHigh(GetUptime(), gputick_cntfrq_factor);
}
s64 NativeClock::GetUptime() const {
s64 cntvct_el0 = 0;
asm volatile("dsb ish\n\t"
"mrs %[cntvct_el0], cntvct_el0\n\t"
"dsb ish\n\t"
: [cntvct_el0] "=r"(cntvct_el0));
return cntvct_el0;
}
bool NativeClock::IsNative() const {
return true;
}
s64 NativeClock::GetHostCNTFRQ() {
u64 cntfrq_el0 = 0;
std::string_view board{""};
#ifdef ANDROID
char buffer[PROP_VALUE_MAX];
int len{__system_property_get("ro.product.board", buffer)};
board = std::string_view(buffer, static_cast<size_t>(len));
#endif
if (board == "s5e9925") { // Exynos 2200
cntfrq_el0 = 25600000;
} else if (board == "exynos2100") { // Exynos 2100
cntfrq_el0 = 26000000;
} else if (board == "exynos9810") { // Exynos 9810
cntfrq_el0 = 26000000;
} else if (board == "s5e8825") { // Exynos 1280
cntfrq_el0 = 26000000;
} else {
asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
}
return cntfrq_el0;
}
} // namespace Common::Arm64
-45
View File
@@ -1,45 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/wall_clock.h"
namespace Common::Arm64 {
class NativeClock final : public WallClock {
public:
explicit NativeClock();
std::chrono::nanoseconds GetTimeNS() const override;
std::chrono::microseconds GetTimeUS() const override;
std::chrono::milliseconds GetTimeMS() const override;
s64 GetCNTPCT() const override;
s64 GetGPUTick() const override;
s64 GetUptime() const override;
bool IsNative() const override;
static s64 GetHostCNTFRQ();
public:
using FactorType = unsigned __int128;
FactorType GetGuestCNTFRQFactor() const {
return guest_cntfrq_factor;
}
private:
FactorType ns_cntfrq_factor;
FactorType us_cntfrq_factor;
FactorType ms_cntfrq_factor;
FactorType guest_cntfrq_factor;
FactorType gputick_cntfrq_factor;
};
} // namespace Common::Arm64
+2
View File
@@ -591,6 +591,8 @@ struct Values {
SwitchableSetting<bool> gpu_unswizzle_enabled{linkage, false, "gpu_unswizzle_enabled",
Category::RendererHacks};
SwitchableSetting<bool> legacy_descriptor_indices{linkage, true, "legacy_descriptor_indices", Category::RendererHacks};
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
#if defined(ANDROID)
ExtendedDynamicState::Disabled,
+174 -55
View File
@@ -1,77 +1,196 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/steady_clock.h"
#include "common/uint128.h"
#include "common/wall_clock.h"
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
#include "common/x64/native_clock.h"
#include "common/x64/rdtsc.h"
#endif
#ifdef HAS_NCE
#include "common/arm64/native_clock.h"
#endif
namespace Common {
class StandardWallClock final : public WallClock {
public:
explicit StandardWallClock() {}
std::chrono::nanoseconds GetTimeNS() const override {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
std::chrono::microseconds GetTimeUS() const override {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
std::chrono::milliseconds GetTimeMS() const override {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
s64 GetCNTPCT() const override {
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
}
s64 GetGPUTick() const override {
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
}
s64 GetUptime() const override {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
}
bool IsNative() const override {
return false;
}
};
std::unique_ptr<WallClock> CreateOptimalClock() {
#if defined(ARCHITECTURE_x86_64)
const auto& caps = GetCPUCaps();
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept
: invariant{invariant_}
, rdtsc_frequency{rdtsc_frequency_}
, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_)}
, us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_)}
, ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_)}
, cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_)}
, gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_)}
{}
if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
} else {
// Fallback to StandardWallClock if the hardware TSC
// - Is not invariant
// - Is not more precise than 1 GHz (1ns resolution)
return std::make_unique<StandardWallClock>();
}
std::chrono::nanoseconds WallClock::GetTimeNS() const {
if (invariant)
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch());
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
}
std::chrono::microseconds WallClock::GetTimeUS() const {
if (invariant)
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
}
std::chrono::milliseconds WallClock::GetTimeMS() const {
if (invariant)
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
}
s64 WallClock::GetCNTPCT() const {
if (invariant)
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor);
}
s64 WallClock::GetGPUTick() const {
if (invariant)
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
return MultiplyHigh(GetUptime(), gputick_rdtsc_factor);
}
s64 WallClock::GetUptime() const {
if (invariant)
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
return s64(Common::X64::FencedRDTSC());
}
bool WallClock::IsNative() const {
if (invariant)
return false;
return true;
}
#elif defined(HAS_NCE)
return std::make_unique<Arm64::NativeClock>();
namespace {
[[nodiscard]] WallClock::FactorType GetFixedPointFactor(u64 num, u64 den) noexcept {
return (WallClock::FactorType(num) << 64) / den;
}
[[nodiscard]] u64 MultiplyHigh(u64 m, WallClock::FactorType factor) noexcept {
return static_cast<u64>((m * factor) >> 64);
}
[[nodiscard]] s64 GetHostCNTFRQ() noexcept {
u64 cntfrq_el0 = 0;
#ifdef ANDROID
std::string_view board{""};
char buffer[PROP_VALUE_MAX];
int len{__system_property_get("ro.product.board", buffer)};
board = std::string_view(buffer, static_cast<size_t>(len));
if (board == "s5e9925") { // Exynos 2200
cntfrq_el0 = 25600000;
} else if (board == "exynos2100") { // Exynos 2100
cntfrq_el0 = 26000000;
} else if (board == "exynos9810") { // Exynos 9810
cntfrq_el0 = 26000000;
} else if (board == "s5e8825") { // Exynos 1280
cntfrq_el0 = 26000000;
} else {
asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
}
return cntfrq_el0;
#else
return std::make_unique<StandardWallClock>();
asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
return cntfrq_el0;
#endif
}
} // namespace
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {
const u64 host_cntfrq = std::max<u64>(GetHostCNTFRQ(), 1);
ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
}
std::chrono::nanoseconds WallClock::GetTimeNS() const {
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)};
}
std::chrono::microseconds WallClock::GetTimeUS() const {
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)};
}
std::chrono::milliseconds WallClock::GetTimeMS() const {
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)};
}
s64 WallClock::GetCNTPCT() const {
return MultiplyHigh(GetUptime(), guest_cntfrq_factor);
}
s64 WallClock::GetGPUTick() const {
return MultiplyHigh(GetUptime(), gputick_cntfrq_factor);
}
s64 WallClock::GetUptime() const {
s64 cntvct_el0 = 0;
asm volatile(
"dsb ish\n\t"
"mrs %[cntvct_el0], cntvct_el0\n\t"
"dsb ish\n\t"
: [cntvct_el0] "=r"(cntvct_el0)
);
return cntvct_el0;
}
bool WallClock::IsNative() const {
return true;
}
#else
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {}
std::chrono::nanoseconds WallClock::GetTimeNS() const {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch());
}
std::chrono::microseconds WallClock::GetTimeUS() const {
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
}
std::chrono::milliseconds WallClock::GetTimeMS() const {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
}
s64 WallClock::GetCNTPCT() const {
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
}
s64 WallClock::GetGPUTick() const {
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
}
s64 WallClock::GetUptime() const {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}
bool WallClock::IsNative() const {
return false;
}
#endif
WallClock CreateOptimalClock() noexcept {
#if defined(ARCHITECTURE_x86_64)
auto const& caps = GetCPUCaps();
return WallClock(!(caps.invariant_tsc && caps.tsc_frequency >= std::nano::den), std::max<u64>(caps.tsc_frequency, 1));
#elif defined(HAS_NCE)
return WallClock(false, 1);
#else
return WallClock(true, 1);
#endif
}
+35 -10
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -20,28 +20,28 @@ public:
static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz
static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz
virtual ~WallClock() = default;
explicit WallClock(bool invariant, u64 rdtsc_frequency_) noexcept;
/// @returns The time in nanoseconds since the construction of this clock.
virtual std::chrono::nanoseconds GetTimeNS() const = 0;
std::chrono::nanoseconds GetTimeNS() const;
/// @returns The time in microseconds since the construction of this clock.
virtual std::chrono::microseconds GetTimeUS() const = 0;
std::chrono::microseconds GetTimeUS() const;
/// @returns The time in milliseconds since the construction of this clock.
virtual std::chrono::milliseconds GetTimeMS() const = 0;
std::chrono::milliseconds GetTimeMS() const;
/// @returns The guest CNTPCT ticks since the construction of this clock.
virtual s64 GetCNTPCT() const = 0;
s64 GetCNTPCT() const;
/// @returns The guest GPU ticks since the construction of this clock.
virtual s64 GetGPUTick() const = 0;
s64 GetGPUTick() const;
/// @returns The raw host timer ticks since an indeterminate epoch.
virtual s64 GetUptime() const = 0;
s64 GetUptime() const;
/// @returns Whether the clock directly uses the host's hardware clock.
virtual bool IsNative() const = 0;
bool IsNative() const;
static inline u64 NSToCNTPCT(u64 ns) {
return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
@@ -85,8 +85,33 @@ protected:
using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>;
using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>;
using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>;
#if defined(ARCHITECTURE_x86_64)
bool invariant;
u64 rdtsc_frequency;
u64 ns_rdtsc_factor;
u64 us_rdtsc_factor;
u64 ms_rdtsc_factor;
u64 cntpct_rdtsc_factor;
u64 gputick_rdtsc_factor;
#elif defined(HAS_NCE)
public:
using FactorType = unsigned __int128;
FactorType GetGuestCNTFRQFactor() const {
return guest_cntfrq_factor;
}
protected:
FactorType ns_cntfrq_factor;
FactorType us_cntfrq_factor;
FactorType ms_cntfrq_factor;
FactorType guest_cntfrq_factor;
FactorType gputick_cntfrq_factor;
#else
#endif
};
[[nodiscard]] std::unique_ptr<WallClock> CreateOptimalClock();
[[nodiscard]] WallClock CreateOptimalClock() noexcept;
} // namespace Common
-46
View File
@@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/uint128.h"
#include "common/x64/native_clock.h"
#include "common/x64/rdtsc.h"
namespace Common::X64 {
NativeClock::NativeClock(u64 rdtsc_frequency_)
: rdtsc_frequency{rdtsc_frequency_}, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den,
rdtsc_frequency)},
us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)},
gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {}
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
}
std::chrono::microseconds NativeClock::GetTimeUS() const {
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
}
std::chrono::milliseconds NativeClock::GetTimeMS() const {
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
}
s64 NativeClock::GetCNTPCT() const {
return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor);
}
s64 NativeClock::GetGPUTick() const {
return MultiplyHigh(GetUptime(), gputick_rdtsc_factor);
}
s64 NativeClock::GetUptime() const {
return static_cast<s64>(FencedRDTSC());
}
bool NativeClock::IsNative() const {
return true;
}
} // namespace Common::X64
-38
View File
@@ -1,38 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/wall_clock.h"
namespace Common::X64 {
class NativeClock final : public WallClock {
public:
explicit NativeClock(u64 rdtsc_frequency_);
std::chrono::nanoseconds GetTimeNS() const override;
std::chrono::microseconds GetTimeUS() const override;
std::chrono::milliseconds GetTimeMS() const override;
s64 GetCNTPCT() const override;
s64 GetGPUTick() const override;
s64 GetUptime() const override;
bool IsNative() const override;
private:
u64 rdtsc_frequency;
u64 ns_rdtsc_factor;
u64 us_rdtsc_factor;
u64 ms_rdtsc_factor;
u64 cntpct_rdtsc_factor;
u64 gputick_rdtsc_factor;
};
} // namespace Common::X64
@@ -1,13 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include <bitset>
#include <initializer_list>
#include <xbyak/xbyak.h>
#include "common/assert.h"
// xbyak hates human beings
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wshadow"
#endif
#ifdef __clang__
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wshadow"
#endif
// You must ensure this matches with src/common/x64/xbyak.h on root dir
#include <ankerl/unordered_dense.h>
#include <boost/unordered_map.hpp>
#define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set
#define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map
#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap
#include <xbyak/xbyak.h>
#include <xbyak/xbyak_util.h>
#include <xbyak/xbyak.h>
namespace Common::X64 {
constexpr size_t RegToIndex(const Xbyak::Reg& reg) {
@@ -174,12 +198,13 @@ inline ABIFrameInfo ABI_CalculateFrameSize(std::bitset<32> regs, size_t rsp_alig
rsp_alignment -= subtraction;
subtraction += rsp_alignment & 0xF;
return ABIFrameInfo{static_cast<s32>(subtraction),
static_cast<s32>(subtraction - xmm_base_subtraction)};
return ABIFrameInfo{
s32(subtraction),
s32(subtraction - xmm_base_subtraction)
};
}
inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs,
size_t rsp_alignment, size_t needed_frame_size = 0) {
inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, size_t rsp_alignment, size_t needed_frame_size = 0) {
auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size);
for (size_t i = 0; i < regs.size(); ++i) {
@@ -202,8 +227,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b
return ABI_SHADOW_SPACE;
}
inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs,
size_t rsp_alignment, size_t needed_frame_size = 0) {
inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, size_t rsp_alignment, size_t needed_frame_size = 0) {
auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size);
for (size_t i = 0; i < regs.size(); ++i) {
@@ -226,4 +250,38 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits
}
}
// Constants for use with cmpps/cmpss
enum {
CMP_EQ = 0,
CMP_LT = 1,
CMP_LE = 2,
CMP_UNORD = 3,
CMP_NEQ = 4,
CMP_NLT = 5,
CMP_NLE = 6,
CMP_ORD = 7,
};
constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) {
const u64 distance = target - (ref + 5);
return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL);
}
inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) {
return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target);
}
template <typename T>
inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
size_t addr = reinterpret_cast<size_t>(f);
if (IsWithin2G(code, addr)) {
code.call(f);
} else {
// ABI_RETURN is a safe temp register to use before a call
code.mov(ABI_RETURN, addr);
code.call(ABI_RETURN);
}
}
} // namespace Common::X64
-46
View File
@@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include <xbyak/xbyak.h>
#include "common/x64/xbyak_abi.h"
namespace Common::X64 {
// Constants for use with cmpps/cmpss
enum {
CMP_EQ = 0,
CMP_LT = 1,
CMP_LE = 2,
CMP_UNORD = 3,
CMP_NEQ = 4,
CMP_NLT = 5,
CMP_NLE = 6,
CMP_ORD = 7,
};
constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) {
const u64 distance = target - (ref + 5);
return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL);
}
inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) {
return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target);
}
template <typename T>
inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
size_t addr = reinterpret_cast<size_t>(f);
if (IsWithin2G(code, addr)) {
code.call(f);
} else {
// ABI_RETURN is a safe temp register to use before a call
code.mov(ABI_RETURN, addr);
code.call(ABI_RETURN);
}
}
} // namespace Common::X64
+3 -6
View File
@@ -113,8 +113,7 @@ void DynarmicCallbacks32::CallSVC(u32 swi) {
}
void DynarmicCallbacks32::AddTicks(u64 ticks) {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
@@ -123,14 +122,12 @@ void DynarmicCallbacks32::AddTicks(u64 ticks) {
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
}
u64 DynarmicCallbacks32::GetTicksRemaining() {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
return std::max<s64>(m_parent.m_system.CoreTiming().downcount, 0);
}
bool DynarmicCallbacks32::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) {
+2 -4
View File
@@ -150,8 +150,7 @@ void DynarmicCallbacks64::CallSVC(u32 svc) {
}
void DynarmicCallbacks64::AddTicks(u64 ticks) {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
@@ -160,13 +159,12 @@ void DynarmicCallbacks64::AddTicks(u64 ticks) {
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
}
u64 DynarmicCallbacks64::GetTicksRemaining() {
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
return std::max<s64>(m_parent.m_system.CoreTiming().downcount, 0);
}
u64 DynarmicCallbacks64::GetCNTPCT() {
+6 -2
View File
@@ -3,7 +3,7 @@
#include <numeric>
#include <bit>
#include "common/arm64/native_clock.h"
#include "common/wall_clock.h"
#include "common/alignment.h"
#include "common/literals.h"
#include "core/arm/nce/arm_nce.h"
@@ -578,7 +578,11 @@ void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg,
}
void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) {
static Common::Arm64::NativeClock clock{};
#if defined(HAS_NCE)
static Common::WallClock clock(false, 1);
#else
static Common::WallClock clock(true, 1);
#endif
const auto factor = clock.GetGuestCNTFRQFactor();
const auto raw_factor = std::bit_cast<std::array<u64, 2>>(factor);
+67 -88
View File
@@ -57,15 +57,51 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
Reset();
on_thread_init = std::move(on_thread_init_);
event_fifo_id = 0;
shutting_down = false;
cpu_ticks = 0;
if (is_multicore) {
timer_thread.emplace([](CoreTiming& instance) {
timer_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("HostTiming");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
instance.on_thread_init();
instance.ThreadLoop();
}, std::ref(*this));
on_thread_init();
has_started = true;
while (!stop_token.stop_requested()) {
while (!paused && !stop_token.stop_requested()) {
paused_set = false;
if (auto const next_time = Advance(); next_time) {
// There are more events left in the queue, wait until the next event.
auto wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time > 0) {
#ifdef _WIN32
while (!paused && !event.IsSet() && wait_time > 0) {
wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time >= timer_resolution_ns) {
Common::Windows::SleepForOneTick();
} else {
#ifdef ARCHITECTURE_x86_64
Common::X64::MicroSleep();
#else
std::this_thread::yield();
#endif
}
}
if (event.IsSet())
event.Reset();
#else
event.WaitFor(std::chrono::nanoseconds(wait_time));
#endif
}
} else {
// Queue is empty, wait until another event is scheduled and signals us to
// continue.
wait_set = true;
event.Wait();
}
wait_set = false;
}
paused_set = true;
pause_event.Wait();
}
});
}
}
@@ -90,7 +126,7 @@ void CoreTiming::SyncPause(bool is_paused) {
}
Pause(is_paused);
if (timer_thread) {
if (timer_thread.joinable()) {
if (!is_paused) {
pause_event.Set();
}
@@ -190,33 +226,22 @@ void CoreTiming::ResetTicks() {
}
u64 CoreTiming::GetClockTicks() const {
u64 fres;
if (is_multicore) [[likely]] {
fres = clock->GetCNTPCT();
} else {
fres = Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
u64 fres = is_multicore ? clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) {
fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock)));
}
const auto overclock = Settings::values.fast_cpu_time.GetValue();
if (overclock != Settings::CpuClock::Off) {
fres = (u64) ((double) fres * (1.7 + 0.3 * u32(overclock)));
}
if (Settings::values.sync_core_speed.GetValue()) {
const auto ticks = double(fres);
const auto speed_limit = double(Settings::SpeedLimit())*0.01;
return u64(ticks/speed_limit);
} else {
return fres;
}
if (::Settings::values.sync_core_speed.GetValue()) {
auto const ticks = f64(fres);
auto const speed_limit = f64(Settings::SpeedLimit()) * 0.01;
return u64(ticks / speed_limit);
}
return fres;
}
u64 CoreTiming::GetGPUTicks() const {
if (is_multicore) [[likely]] {
return clock->GetGPUTick();
}
return Common::WallClock::CPUTickToGPUTick(cpu_ticks);
return is_multicore
? clock.GetGPUTick()
: Common::WallClock::CPUTickToGPUTick(cpu_ticks);
}
std::optional<s64> CoreTiming::Advance() {
@@ -278,75 +303,29 @@ std::optional<s64> CoreTiming::Advance() {
}
}
void CoreTiming::ThreadLoop() {
has_started = true;
while (!shutting_down) {
while (!paused) {
paused_set = false;
const auto next_time = Advance();
if (next_time) {
// There are more events left in the queue, wait until the next event.
auto wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time > 0) {
#ifdef _WIN32
while (!paused && !event.IsSet() && wait_time > 0) {
wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time >= timer_resolution_ns) {
Common::Windows::SleepForOneTick();
} else {
#ifdef ARCHITECTURE_x86_64
Common::X64::MicroSleep();
#else
std::this_thread::yield();
#endif
}
}
if (event.IsSet()) {
event.Reset();
}
#else
event.WaitFor(std::chrono::nanoseconds(wait_time));
#endif
}
} else {
// Queue is empty, wait until another event is scheduled and signals us to
// continue.
wait_set = true;
event.Wait();
}
wait_set = false;
}
paused_set = true;
pause_event.Wait();
}
}
void CoreTiming::Reset() {
paused = true;
shutting_down = true;
pause_event.Set();
event.Set();
if (timer_thread) {
timer_thread->join();
if (timer_thread.joinable()) {
timer_thread.request_stop();
timer_thread.join();
}
timer_thread.reset();
has_started = false;
}
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
if (is_multicore) [[likely]] {
return clock->GetTimeNS();
}
return std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)};
/// @brief Returns current time in nanoseconds.
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const noexcept {
return is_multicore
? clock.GetTimeNS()
: std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)};
}
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
if (is_multicore) [[likely]] {
return clock->GetTimeUS();
}
return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
/// @brief Returns current time in microseconds.
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const noexcept {
return is_multicore
? clock.GetTimeUS()
: std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
}
#ifdef _WIN32
+6 -12
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -118,7 +118,7 @@ public:
void Idle();
s64 GetDowncount() const {
s64 GetDowncount() const noexcept {
return downcount;
}
@@ -128,11 +128,8 @@ public:
/// Returns the current GPU tick value.
u64 GetGPUTicks() const;
/// Returns current time in microseconds.
std::chrono::microseconds GetGlobalTimeUs() const;
/// Returns current time in nanoseconds.
std::chrono::nanoseconds GetGlobalTimeNs() const;
[[nodiscard]] std::chrono::microseconds GetGlobalTimeUs() const noexcept;
[[nodiscard]] std::chrono::nanoseconds GetGlobalTimeNs() const noexcept;
/// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
std::optional<s64> Advance();
@@ -141,13 +138,11 @@ public:
void SetTimerResolutionNs(std::chrono::nanoseconds ns);
#endif
private:
struct Event;
void ThreadLoop();
void Reset();
std::unique_ptr<Common::WallClock> clock;
Common::WallClock clock;
s64 global_timer = 0;
@@ -165,11 +160,10 @@ private:
Common::Event pause_event{};
mutable std::mutex basic_lock;
std::mutex advance_lock;
std::optional<std::jthread> timer_thread;
std::jthread timer_thread;
std::atomic<bool> paused{};
std::atomic<bool> paused_set{};
std::atomic<bool> wait_set{};
std::atomic<bool> shutting_down{};
std::atomic<bool> has_started{};
std::function<void()> on_thread_init{};
@@ -1,86 +1,48 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include <vector>
#include "common/swap.h"
#include "core/file_sys/system_archive/time_zone_binary.h"
#include "core/file_sys/vfs/vfs_static.h"
#include "core/file_sys/vfs/vfs_types.h"
#include "core/file_sys/vfs/vfs_vector.h"
#include "nx_tzdb.h"
namespace FileSys::SystemArchive {
const static std::map<std::string, const std::map<const char*, const std::vector<u8>>&>
tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa},
{"America", NxTzdb::america},
{"Antarctica", NxTzdb::antarctica},
{"Arctic", NxTzdb::arctic},
{"Asia", NxTzdb::asia},
{"Atlantic", NxTzdb::atlantic},
{"Australia", NxTzdb::australia},
{"Brazil", NxTzdb::brazil},
{"Canada", NxTzdb::canada},
{"Chile", NxTzdb::chile},
{"Etc", NxTzdb::etc},
{"Europe", NxTzdb::europe},
{"Indian", NxTzdb::indian},
{"Mexico", NxTzdb::mexico},
{"Pacific", NxTzdb::pacific},
{"US", NxTzdb::us}};
const static std::map<std::string, const std::map<const char*, const std::vector<u8>>&>
tzdb_america_dirs = {{"Argentina", NxTzdb::america_argentina},
{"Indiana", NxTzdb::america_indiana},
{"Kentucky", NxTzdb::america_kentucky},
{"North_Dakota", NxTzdb::america_north_dakota}};
static void GenerateFiles(std::vector<VirtualFile>& directory,
const std::map<const char*, const std::vector<u8>>& files) {
for (const auto& [filename, data] : files) {
const auto data_copy{data};
const std::string filename_copy{filename};
VirtualFile file{
std::make_shared<VectorVfsFile>(std::move(data_copy), std::move(filename_copy))};
directory.push_back(file);
}
}
static std::vector<VirtualFile> GenerateZoneinfoFiles() {
std::vector<VirtualFile> zoneinfo_files;
GenerateFiles(zoneinfo_files, NxTzdb::zoneinfo);
return zoneinfo_files;
}
VirtualDir TimeZoneBinary() {
std::vector<VirtualDir> america_sub_dirs;
for (const auto& [dir_name, files] : tzdb_america_dirs) {
std::vector<VirtualFile> vfs_files;
GenerateFiles(vfs_files, files);
america_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(
std::move(vfs_files), std::vector<VirtualDir>{}, dir_name));
}
america_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_america_argentina(), std::vector<VirtualDir>{}, "Argentina"));
america_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_america_indiana(), std::vector<VirtualDir>{}, "Indiana"));
america_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_america_kentucky(), std::vector<VirtualDir>{}, "Kentucky"));
america_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_america_north_dakota(), std::vector<VirtualDir>{}, "North_Dakota"));
std::vector<VirtualDir> zoneinfo_sub_dirs;
for (const auto& [dir_name, files] : tzdb_zoneinfo_dirs) {
std::vector<VirtualFile> vfs_files;
GenerateFiles(vfs_files, files);
if (dir_name == "America") {
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(
std::move(vfs_files), std::move(america_sub_dirs), dir_name));
} else {
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(
std::move(vfs_files), std::vector<VirtualDir>{}, dir_name));
}
}
std::vector<VirtualDir> zoneinfo_dir{std::make_shared<VectorVfsDirectory>(
GenerateZoneinfoFiles(), std::move(zoneinfo_sub_dirs), "zoneinfo")};
std::vector<VirtualFile> root_files;
GenerateFiles(root_files, NxTzdb::base);
return std::make_shared<VectorVfsDirectory>(std::move(root_files), std::move(zoneinfo_dir),
"data");
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_africa(), std::vector<VirtualDir>{}, "Africa"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_america(), std::move(america_sub_dirs), "America"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_antarctica(), std::vector<VirtualDir>{}, "Antarctica"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_arctic(), std::vector<VirtualDir>{}, "Arctic"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_asia(), std::vector<VirtualDir>{}, "Asia"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_atlantic(), std::vector<VirtualDir>{}, "Atlantic"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_australia(), std::vector<VirtualDir>{}, "Australia"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_brazil(), std::vector<VirtualDir>{}, "Brazil"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_canada(), std::vector<VirtualDir>{}, "Canada"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_chile(), std::vector<VirtualDir>{}, "Chile"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_etc(), std::vector<VirtualDir>{}, "Etc"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_europe(), std::vector<VirtualDir>{}, "Europe"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_indian(), std::vector<VirtualDir>{}, "Indian"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_mexico(), std::vector<VirtualDir>{}, "Mexico"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_pacific(), std::vector<VirtualDir>{}, "Pacific"));
zoneinfo_sub_dirs.push_back(std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_us(), std::vector<VirtualDir>{}, "US"));
std::vector<VirtualDir> zoneinfo_dir{std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_zoneinfo(), std::move(zoneinfo_sub_dirs), "zoneinfo")};
// last files (root)
return std::make_shared<VectorVfsDirectory>(NxTzdb::CollectFiles_base(), std::move(zoneinfo_dir), "data");
}
} // namespace FileSys::SystemArchive
+13
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -13,6 +16,16 @@ namespace IPC {
/// Size of the command buffer area, in 32-bit words.
constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32);
/// Must match bitfields
constexpr std::size_t MAX_BUFFER_DESCRIPTORS = 16;
constexpr std::size_t MAX_INCOMING_MOVE_HANDLERS = 16;
constexpr std::size_t MAX_INCOMING_COPY_HANDLERS = 16;
/// Doesn't need to match bitfields but usually not big enough
constexpr std::size_t MAX_OUTGOING_COPY_OBJECTS = 16;
constexpr std::size_t MAX_OUTGOING_MOVE_OBJECTS = 16;
constexpr std::size_t MAX_OUTGOING_DOMAIN_OBJECTS = 16;
enum class ControlCommand : u32 {
ConvertSessionToDomain = 0,
ConvertDomainToSession = 1,
+6 -12
View File
@@ -128,10 +128,12 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se
return ResultSuccess;
}
HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_,
Kernel::KServerSession* server_session_,
Kernel::KThread* thread_)
: server_session(server_session_), thread(thread_), kernel{kernel_}, memory{memory_} {
HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_, Kernel::KServerSession* server_session_, Kernel::KThread* thread_)
: server_session(server_session_)
, thread(thread_)
, kernel{kernel_}
, memory{memory_}
{
cmd_buf[0] = 0;
}
@@ -155,9 +157,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
}
if (incoming) {
// Populate the object lists with the data in the IPC request.
incoming_copy_handles.reserve(handle_descriptor_header->num_handles_to_copy);
incoming_move_handles.reserve(handle_descriptor_header->num_handles_to_move);
for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) {
incoming_copy_handles.push_back(rp.Pop<Handle>());
}
@@ -172,11 +171,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
}
}
buffer_x_descriptors.reserve(command_header->num_buf_x_descriptors);
buffer_a_descriptors.reserve(command_header->num_buf_a_descriptors);
buffer_b_descriptors.reserve(command_header->num_buf_b_descriptors);
buffer_w_descriptors.reserve(command_header->num_buf_w_descriptors);
for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) {
buffer_x_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>());
}
+26 -26
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -11,6 +14,7 @@
#include <string>
#include <type_traits>
#include <vector>
#include <boost/container/static_vector.hpp>
#include "common/assert.h"
#include "common/common_types.h"
@@ -181,8 +185,7 @@ private:
*/
class HLERequestContext {
public:
explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory,
Kernel::KServerSession* session, Kernel::KThread* thread);
explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KServerSession* session, Kernel::KThread* thread);
~HLERequestContext();
/// Returns a pointer to the IPC command buffer for this request.
@@ -233,19 +236,19 @@ public:
return data_payload_offset;
}
[[nodiscard]] const std::vector<IPC::BufferDescriptorX>& BufferDescriptorX() const {
[[nodiscard]] const boost::container::static_vector<IPC::BufferDescriptorX, 16>& BufferDescriptorX() const {
return buffer_x_descriptors;
}
[[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorA() const {
[[nodiscard]] const boost::container::static_vector<IPC::BufferDescriptorABW, 16>& BufferDescriptorA() const {
return buffer_a_descriptors;
}
[[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorB() const {
[[nodiscard]] const boost::container::static_vector<IPC::BufferDescriptorABW, 16>& BufferDescriptorB() const {
return buffer_b_descriptors;
}
[[nodiscard]] const std::vector<IPC::BufferDescriptorC>& BufferDescriptorC() const {
[[nodiscard]] const boost::container::static_vector<IPC::BufferDescriptorC, 16>& BufferDescriptorC() const {
return buffer_c_descriptors;
}
@@ -399,38 +402,35 @@ private:
Kernel::KHandleTable* client_handle_table{};
Kernel::KThread* thread{};
std::vector<Handle> incoming_move_handles;
std::vector<Handle> incoming_copy_handles;
boost::container::static_vector<IPC::BufferDescriptorX, IPC::MAX_BUFFER_DESCRIPTORS> buffer_x_descriptors;
boost::container::static_vector<IPC::BufferDescriptorABW, IPC::MAX_BUFFER_DESCRIPTORS> buffer_a_descriptors;
boost::container::static_vector<IPC::BufferDescriptorABW, IPC::MAX_BUFFER_DESCRIPTORS> buffer_b_descriptors;
boost::container::static_vector<IPC::BufferDescriptorABW, IPC::MAX_BUFFER_DESCRIPTORS> buffer_w_descriptors;
boost::container::static_vector<IPC::BufferDescriptorC, IPC::MAX_BUFFER_DESCRIPTORS> buffer_c_descriptors;
boost::container::static_vector<Handle, IPC::MAX_INCOMING_MOVE_HANDLERS> incoming_move_handles;
boost::container::static_vector<Handle, IPC::MAX_INCOMING_COPY_HANDLERS> incoming_copy_handles;
boost::container::static_vector<Kernel::KAutoObject*, IPC::MAX_OUTGOING_MOVE_OBJECTS> outgoing_move_objects;
boost::container::static_vector<Kernel::KAutoObject*, IPC::MAX_OUTGOING_COPY_OBJECTS> outgoing_copy_objects;
boost::container::static_vector<SessionRequestHandlerPtr, IPC::MAX_OUTGOING_DOMAIN_OBJECTS> outgoing_domain_objects;
std::vector<Kernel::KAutoObject*> outgoing_move_objects;
std::vector<Kernel::KAutoObject*> outgoing_copy_objects;
std::vector<SessionRequestHandlerPtr> outgoing_domain_objects;
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_a{};
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_x{};
std::optional<IPC::CommandHeader> command_header;
std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header;
std::optional<IPC::DataPayloadHeader> data_payload_header;
std::optional<IPC::DomainMessageHeader> domain_message_header;
std::vector<IPC::BufferDescriptorX> buffer_x_descriptors;
std::vector<IPC::BufferDescriptorABW> buffer_a_descriptors;
std::vector<IPC::BufferDescriptorABW> buffer_b_descriptors;
std::vector<IPC::BufferDescriptorABW> buffer_w_descriptors;
std::vector<IPC::BufferDescriptorC> buffer_c_descriptors;
std::weak_ptr<SessionRequestManager> manager{};
Kernel::KernelCore& kernel;
Core::Memory::Memory& memory;
u32_le command{};
u64 pid{};
u32_le command{};
u32 write_size{};
u32 data_payload_offset{};
u32 handles_offset{};
u32 domain_offset{};
std::weak_ptr<SessionRequestManager> manager{};
bool is_deferred{false};
Kernel::KernelCore& kernel;
Core::Memory::Memory& memory;
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_a{};
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_x{};
bool is_deferred = false;
};
} // namespace Service
@@ -26,8 +26,11 @@ namespace Service::android {
BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
Service::Nvidia::NvCore::NvMap& nvmap_)
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
clock{Common::CreateOptimalClock()}, nvmap(nvmap_) {
: service_context{service_context_}, core{std::move(buffer_queue_core_)}
, slots(core->slots)
, clock{Common::CreateOptimalClock()}
, nvmap(nvmap_)
{
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
}
@@ -485,7 +488,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
slots[slot].buffer_state = BufferState::Queued;
slots[slot].frame_number = core->frame_counter;
slots[slot].queue_time = timestamp;
slots[slot].presentation_time = clock->GetTimeNS().count();
slots[slot].presentation_time = clock.GetTimeNS().count();
slots[slot].fence = fence;
item.slot = slot;
@@ -89,8 +89,7 @@ private:
s32 next_callback_ticket{};
s32 current_callback_ticket{};
std::condition_variable_any callback_condition;
std::unique_ptr<Common::WallClock> clock;
Common::WallClock clock;
Service::Nvidia::NvCore::NvMap& nvmap;
};
+3 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -20,6 +20,7 @@
#include "common/settings.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/result.h"
@@ -124,8 +125,7 @@ json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& s
}
template <bool read_value, typename DescriptorType>
json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer,
Core::Memory::Memory& memory) {
json GetHLEBufferDescriptorData(const boost::container::static_vector<DescriptorType, IPC::MAX_BUFFER_DESCRIPTORS>& buffer, Core::Memory::Memory& memory) {
auto buffer_out = json::array();
for (const auto& desc : buffer) {
auto entry = json{
@@ -224,7 +224,7 @@ void A64EmitX64::GenTerminalHandlers() {
terminal_handler_fast_dispatch_hint = code.getCurr<const void*>();
calculate_location_descriptor();
code.L(rsb_cache_miss);
code.mov(r8, reinterpret_cast<u64>(fast_dispatch_table.data()));
code.mov(r8, u64(fast_dispatch_table.data()));
//code.mov(r12, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
code.mov(r12, rbx);
if (code.HasHostFeature(HostFeature::SSE42)) {
@@ -244,7 +244,7 @@ void A64EmitX64::GenTerminalHandlers() {
code.align();
fast_dispatch_table_lookup = code.getCurr<FastDispatchEntry& (*)(u64)>();
code.mov(code.ABI_PARAM2, reinterpret_cast<u64>(fast_dispatch_table.data()));
code.mov(code.ABI_PARAM2, u64(fast_dispatch_table.data()));
if (code.HasHostFeature(HostFeature::SSE42)) {
code.crc32(code.ABI_PARAM1, code.ABI_PARAM2);
}
@@ -26,7 +26,7 @@ struct FrameInfo {
};
static_assert(ABI_SHADOW_SPACE <= 32);
static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms, size_t frame_size) {
static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms, size_t frame_size) noexcept {
// We are initially 8 byte aligned because the return value is pushed onto an aligned stack after a call.
const size_t rsp_alignment = (num_gprs % 2 == 0) ? 8 : 0;
const size_t total_xmm_size = num_xmms * XMM_SIZE;
@@ -40,7 +40,7 @@ static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms
};
}
void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> const& regs) {
static void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> regs) noexcept {
using namespace Xbyak::util;
const size_t num_gprs = (ABI_ALL_GPRS & regs).count();
@@ -65,7 +65,7 @@ void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size,
}
}
void ABI_PopRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> const& regs) {
static void ABI_PopRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> regs) noexcept {
using namespace Xbyak::util;
const size_t num_gprs = (ABI_ALL_GPRS & regs).count();
@@ -107,13 +107,13 @@ void ABI_PopCallerSaveRegistersAndAdjustStack(BlockOfCode& code, const std::size
// Windows ABI registers are not in the same allocation algorithm as unix's
void ABI_PushCallerSaveRegistersAndAdjustStackExcept(BlockOfCode& code, const HostLoc exception) {
std::bitset<32> regs = ABI_ALL_CALLER_SAVE;
auto regs = ABI_ALL_CALLER_SAVE;
regs.reset(size_t(exception));
ABI_PushRegistersAndAdjustStack(code, 0, regs);
}
void ABI_PopCallerSaveRegistersAndAdjustStackExcept(BlockOfCode& code, const HostLoc exception) {
std::bitset<32> regs = ABI_ALL_CALLER_SAVE;
auto regs = ABI_ALL_CALLER_SAVE;
regs.reset(size_t(exception));
ABI_PopRegistersAndAdjustStack(code, 0, regs);
}
@@ -6,23 +6,23 @@
* SPDX-License-Identifier: 0BSD
*/
#include "dynarmic/backend/x64/constant_pool.h"
#include <cstring>
#include "common/assert.h"
#include "dynarmic/backend/x64/block_of_code.h"
#include "dynarmic/backend/x64/constant_pool.h"
namespace Dynarmic::Backend::X64 {
ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
: code(code), insertion_point(0) {
: code(code)
, insertion_point(0)
{
code.EnsureMemoryCommitted(align_size + size);
code.int3();
code.align(align_size);
pool = std::span<ConstantT>(
reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size);
pool = std::span<ConstantT>(reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size);
}
Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
@@ -8,8 +8,6 @@
#pragma once
#include <bitset>
#include <xbyak/xbyak.h>
#include "common/assert.h"
#include "common/common_types.h"
#include "dynarmic/backend/x64/xbyak.h"
@@ -3,13 +3,11 @@
#pragma once
#include <unordered_map>
#include <unordered_set>
// TODO: Defining this crashes e v e r y t h i n g
// #define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set
// #define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map
// #define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap
// You must ensure this matches with src/common/x64/xbyak.h on root dir
#include <ankerl/unordered_dense.h>
#include <boost/unordered_map.hpp>
#define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set
#define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map
#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap
#include <xbyak/xbyak.h>
#include <xbyak/xbyak_util.h>
@@ -78,7 +78,7 @@ u64 FPToFixed(size_t ibits, FPT op, size_t fbits, bool unsigned_, FPCR fpcr, Rou
}
// Detect Overflow
const int min_exponent_for_overflow = static_cast<int>(ibits) - static_cast<int>(mcl::bit::highest_set_bit(value.mantissa + (round_up ? Safe::LogicalShiftRight<u64>(1, exponent) : 0))) - (unsigned_ ? 0 : 1);
const int min_exponent_for_overflow = int(ibits) - int(mcl::bit::highest_set_bit(value.mantissa + (round_up ? Safe::LogicalShiftRight<u64>(1, exponent) : 0))) - (unsigned_ ? 0 : 1);
if (exponent >= min_exponent_for_overflow) {
// Positive overflow
if (unsigned_ || !sign) {
@@ -87,10 +87,10 @@ u64 FPToFixed(size_t ibits, FPT op, size_t fbits, bool unsigned_, FPCR fpcr, Rou
}
// Negative overflow
const u64 min_value = Safe::Negate<u64>(static_cast<u64>(1) << (ibits - 1));
const u64 min_value = Safe::Negate<u64>(u64(1) << (ibits - 1));
if (!(exponent == min_exponent_for_overflow && int_result == min_value)) {
FPProcessException(FPExc::InvalidOp, fpcr, fpsr);
return static_cast<u64>(1) << (ibits - 1);
return u64(1) << (ibits - 1);
}
}
@@ -26,6 +26,7 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32
Xbyak::Label start, loop;
code.jmp(start, code.T_NEAR);
code.L(loop);
if (waitpkg) {
// TODO: this is because we lack regalloc - so better to be safe :(
code.push(Xbyak::util::rax);
@@ -33,7 +34,14 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32
code.push(Xbyak::util::rdx);
// TODO: This clobbers EAX and EDX did we tell the regalloc?
// ARM ptr for address-monitoring
code.umonitor(ptr);
// XBYAK BUG: code.umonitor(ptr); see issue #255
// replace once xbyak has been fixed
code.db(0xF3);
if (ptr.getIdx() >= 8) code.db(0x41);
code.db(0x0F); code.db(0xAE);
code.db(uint8_t((3 << 6) | ((6 & 7) << 3) | (ptr.getIdx() & 7)));
// tmp.bit[0] = 0: C0.1 | Slow Wakup | Better Savings
// tmp.bit[0] = 1: C0.2 | Fast Wakup | Lesser Savings
// edx:eax is implicitly used as a 64-bit deadline timestamp
+4 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
* Copyright (c) 2020 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -11,7 +14,7 @@
#include <utility>
#include <catch2/catch_test_macros.hpp>
#include <xbyak/xbyak_util.h>
#include "dynarmic/backend/x64/xbyak.h"
TEST_CASE("Host CPU supports", "[a64]") {
using Cpu = Xbyak::util::Cpu;
+47 -131
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -108,10 +108,10 @@ void EmulatedController::ReloadFromSettings() {
// Other or debug controller should always be a pro controller
if (npad_id_type != NpadIdType::Other) {
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
original_npad_type = npad_type;
original_npad_type = npad_type.load();
} else {
SetNpadStyleIndex(NpadStyleIndex::Fullkey);
original_npad_type = npad_type;
original_npad_type = npad_type.load();
}
// Disable special features before disconnecting
@@ -577,10 +577,9 @@ void EmulatedController::UnloadInput() {
}
void EmulatedController::EnableConfiguration() {
std::unique_lock lock1{connect_mutex}, lock2{npad_mutex};
is_configuring = true;
tmp_is_connected = is_connected;
tmp_npad_type = npad_type;
is_configuring.store(true);
tmp_is_connected.store(is_connected);
tmp_npad_type.store(npad_type);
}
void EmulatedController::DisableConfiguration() {
@@ -600,7 +599,7 @@ void EmulatedController::DisableConfiguration() {
Disconnect();
}
SetNpadStyleIndex(tmp_npad_type);
original_npad_type = tmp_npad_type;
original_npad_type.store(tmp_npad_type);
}
// Apply temporary connected status to the real controller
@@ -614,19 +613,16 @@ void EmulatedController::DisableConfiguration() {
}
void EmulatedController::EnableSystemButtons() {
std::unique_lock lock{mutex};
system_buttons_enabled = true;
}
void EmulatedController::DisableSystemButtons() {
std::unique_lock lock{mutex};
system_buttons_enabled = false;
controller.home_button_state.raw = 0;
controller.capture_button_state.raw = 0;
}
void EmulatedController::ResetSystemButtons() {
std::unique_lock lock{mutex};
controller.home_button_state.home.Assign(false);
controller.capture_button_state.capture.Assign(false);
}
@@ -763,8 +759,7 @@ void EmulatedController::StartMotionCalibration() {
}
}
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
Common::UUID uuid) {
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) {
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
@@ -772,7 +767,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
return;
}
std::unique_lock lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = controller.button_values[index];
@@ -818,7 +812,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
controller.debug_pad_button_state.raw = 0;
controller.home_button_state.raw = 0;
controller.capture_button_state.raw = 0;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Button, false);
return;
}
@@ -922,8 +915,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
break;
}
lock.unlock();
if (!is_connected) {
if (npad_type == NpadStyleIndex::Handheld) {
if (npad_id_type == NpadIdType::Handheld) {
@@ -952,7 +943,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Stick, !is_configuring);
};
std::unique_lock lock{mutex};
const auto stick_value = TransformToStick(callback);
// Only read stick values that have the same uuid or are over the threshold to avoid flapping
@@ -1009,7 +999,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring);
};
std::unique_lock lock{mutex};
const auto trigger_value = TransformToTrigger(callback);
// Only read trigger values that have the same uuid or are pressed once
@@ -1057,7 +1046,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::Motion, !is_configuring);
};
std::unique_lock lock{mutex};
auto& raw_status = controller.motion_values[index].raw_status;
auto& emulated = controller.motion_values[index].emulated;
@@ -1093,7 +1081,6 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Color, !is_configuring);
};
std::unique_lock lock{mutex};
controller.color_values[index] = TransformToColor(callback);
if (is_configuring) {
@@ -1136,15 +1123,13 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
}
}
void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
std::size_t index) {
void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index) {
if (index >= controller.battery_values.size()) {
return;
}
SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::Battery, !is_configuring);
};
std::unique_lock lock{mutex};
controller.battery_values[index] = TransformToBattery(callback);
if (is_configuring) {
@@ -1209,47 +1194,33 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring);
};
std::unique_lock lock{mutex};
controller.camera_values = TransformToCamera(callback);
if (is_configuring) {
return;
if (!is_configuring) {
controller.camera_state.sample++;
controller.camera_state.format = Core::IrSensor::ImageTransferProcessorFormat(controller.camera_values.format);
controller.camera_state.data = controller.camera_values.data;
}
controller.camera_state.sample++;
controller.camera_state.format =
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
controller.camera_state.data = controller.camera_values.data;
}
void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::RingController, !is_configuring);
};
std::unique_lock lock{mutex};
const auto force_value = TransformToStick(callback);
controller.ring_analog_value = force_value.x;
if (is_configuring) {
return;
if (!is_configuring) {
controller.ring_analog_state.force = force_value.x.value;
}
controller.ring_analog_state.force = force_value.x.value;
}
void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring);
};
std::unique_lock lock{mutex};
controller.nfc_values = TransformToNfc(callback);
if (is_configuring) {
return;
if (!is_configuring) {
controller.nfc_state = controller.nfc_values;
}
controller.nfc_state = controller.nfc_values;
}
bool EmulatedController::SetVibration(bool should_vibrate) {
@@ -1258,7 +1229,6 @@ bool EmulatedController::SetVibration(bool should_vibrate) {
vibration_value.high_amplitude = 1.0f;
vibration_value.low_amplitude = 1.0f;
}
return SetVibration(DeviceIndex::Left, vibration_value);
}
@@ -1268,7 +1238,6 @@ bool EmulatedController::SetVibration(u32 slot, Core::HID::VibrationGcErmCommand
vibration_value.high_amplitude = 1.0f;
vibration_value.low_amplitude = 1.0f;
}
return SetVibration(DeviceIndex::Left, vibration_value);
}
@@ -1632,7 +1601,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Attempt to reconnect with the original type
if (npad_type != original_npad_type) {
Disconnect();
const auto current_npad_type = npad_type;
const auto current_npad_type = npad_type.load();
SetNpadStyleIndex(original_npad_type);
if (IsControllerSupported()) {
Connect();
@@ -1650,7 +1619,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Fullkey controllers to Pro controllers
if (IsControllerFullkey() && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load());
SetNpadStyleIndex(NpadStyleIndex::Fullkey);
Connect();
return;
@@ -1658,7 +1627,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Dual joycon controllers to Pro controllers
if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load());
SetNpadStyleIndex(NpadStyleIndex::Fullkey);
Connect();
return;
@@ -1666,19 +1635,16 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Pro controllers to Dual joycon
if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type.load());
SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
Connect();
return;
}
LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller",
npad_type);
LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", npad_type.load());
}
bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
std::unique_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load();
switch (type) {
case NpadStyleIndex::Fullkey:
case NpadStyleIndex::GameCube:
@@ -1693,39 +1659,26 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
}
bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
std::unique_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load();
switch (type) {
case NpadStyleIndex::Fullkey:
return supported_style_tag.fullkey.As<bool>();
case NpadStyleIndex::Handheld:
return supported_style_tag.handheld.As<bool>();
case NpadStyleIndex::JoyconDual:
return supported_style_tag.joycon_dual.As<bool>();
case NpadStyleIndex::JoyconLeft:
return supported_style_tag.joycon_left.As<bool>();
case NpadStyleIndex::JoyconRight:
return supported_style_tag.joycon_right.As<bool>();
case NpadStyleIndex::GameCube:
return supported_style_tag.gamecube.As<bool>();
case NpadStyleIndex::Pokeball:
return supported_style_tag.palma.As<bool>();
case NpadStyleIndex::NES:
return supported_style_tag.lark.As<bool>();
case NpadStyleIndex::SNES:
return supported_style_tag.lucia.As<bool>();
case NpadStyleIndex::N64:
return supported_style_tag.lagoon.As<bool>();
case NpadStyleIndex::SegaGenesis:
return supported_style_tag.lager.As<bool>();
default:
return false;
case NpadStyleIndex::Fullkey: return supported_style_tag.fullkey.As<bool>();
case NpadStyleIndex::Handheld: return supported_style_tag.handheld.As<bool>();
case NpadStyleIndex::JoyconDual: return supported_style_tag.joycon_dual.As<bool>();
case NpadStyleIndex::JoyconLeft: return supported_style_tag.joycon_left.As<bool>();
case NpadStyleIndex::JoyconRight: return supported_style_tag.joycon_right.As<bool>();
case NpadStyleIndex::GameCube: return supported_style_tag.gamecube.As<bool>();
case NpadStyleIndex::Pokeball: return supported_style_tag.palma.As<bool>();
case NpadStyleIndex::NES: return supported_style_tag.lark.As<bool>();
case NpadStyleIndex::SNES: return supported_style_tag.lucia.As<bool>();
case NpadStyleIndex::N64: return supported_style_tag.lagoon.As<bool>();
case NpadStyleIndex::SegaGenesis: return supported_style_tag.lager.As<bool>();
default: return false;
}
}
void EmulatedController::Connect(bool use_temporary_value) {
if (!IsControllerSupported(use_temporary_value)) {
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load();
LOG_ERROR(Service_HID, "Controller type {} is not supported", type);
return;
}
@@ -1733,12 +1686,10 @@ void EmulatedController::Connect(bool use_temporary_value) {
auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Connected, !is_configuring);
};
std::unique_lock lock1{connect_mutex}, lock2{mutex};
if (is_configuring) {
tmp_is_connected = true;
return;
}
if (is_connected) {
trigger_guard.Cancel();
return;
@@ -1750,12 +1701,10 @@ void EmulatedController::Disconnect() {
auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring);
};
std::unique_lock lock1{connect_mutex}, lock2{mutex};
if (is_configuring) {
tmp_is_connected = false;
return;
}
if (!is_connected) {
trigger_guard.Cancel();
return;
@@ -1764,19 +1713,16 @@ void EmulatedController::Disconnect() {
}
bool EmulatedController::IsConnected(bool get_temporary_value) const {
std::shared_lock lock{connect_mutex};
if (get_temporary_value && is_configuring)
return tmp_is_connected;
return is_connected;
}
NpadIdType EmulatedController::GetNpadIdType() const {
std::shared_lock lock{mutex};
return npad_id_type;
}
NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
std::shared_lock lock{npad_mutex};
if (get_temporary_value && is_configuring)
return tmp_npad_type;
return npad_type;
@@ -1786,8 +1732,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Type, !is_configuring);
};
std::unique_lock lock1{mutex}, lock2{npad_mutex};
if (is_configuring) {
if (tmp_npad_type == npad_type_) {
trigger_guard.Cancel();
@@ -1796,14 +1740,12 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
tmp_npad_type = npad_type_;
return;
}
if (npad_type == npad_type_) {
trigger_guard.Cancel();
return;
}
if (is_connected) {
LOG_WARNING(Service_HID, "Controller {} type changed while it's connected",
Service::HID::NpadIdTypeToIndex(npad_id_type));
LOG_WARNING(Service_HID, "Controller {} type changed while it's connected", Service::HID::NpadIdTypeToIndex(npad_id_type));
}
npad_type = npad_type_;
}
@@ -1832,37 +1774,30 @@ LedPattern EmulatedController::GetLedPattern() const {
}
ButtonValues EmulatedController::GetButtonsValues() const {
std::unique_lock lock{mutex};
return controller.button_values;
}
SticksValues EmulatedController::GetSticksValues() const {
std::unique_lock lock{mutex};
return controller.stick_values;
}
TriggerValues EmulatedController::GetTriggersValues() const {
std::unique_lock lock{mutex};
return controller.trigger_values;
}
ControllerMotionValues EmulatedController::GetMotionValues() const {
std::unique_lock lock{mutex};
return controller.motion_values;
}
ColorValues EmulatedController::GetColorsValues() const {
std::unique_lock lock{mutex};
return controller.color_values;
}
BatteryValues EmulatedController::GetBatteryValues() const {
std::unique_lock lock{mutex};
return controller.battery_values;
}
CameraValues EmulatedController::GetCameraValues() const {
std::unique_lock lock{mutex};
return controller.camera_values;
}
@@ -1871,72 +1806,54 @@ RingAnalogValue EmulatedController::GetRingSensorValues() const {
}
HomeButtonState EmulatedController::GetHomeButtons() const {
std::unique_lock lock{mutex};
if (is_configuring) {
if (is_configuring)
return {};
}
return controller.home_button_state;
}
CaptureButtonState EmulatedController::GetCaptureButtons() const {
std::unique_lock lock{mutex};
if (is_configuring) {
if (is_configuring)
return {};
}
return controller.capture_button_state;
}
NpadButtonState EmulatedController::GetNpadButtons() const {
std::unique_lock lock{mutex};
if (is_configuring) {
if (is_configuring)
return {};
}
return {controller.npad_button_state.raw & GetTurboButtonMask()};
}
DebugPadButton EmulatedController::GetDebugPadButtons() const {
std::unique_lock lock{mutex};
if (is_configuring) {
if (is_configuring)
return {};
}
return controller.debug_pad_button_state;
}
AnalogSticks EmulatedController::GetSticks() const {
std::unique_lock lock{mutex};
if (is_configuring) {
if (is_configuring)
return {};
}
return controller.analog_stick_state;
}
NpadGcTriggerState EmulatedController::GetTriggers() const {
std::unique_lock lock{mutex};
if (is_configuring) {
if (is_configuring)
return {};
}
return controller.gc_trigger_state;
}
MotionState EmulatedController::GetMotions() const {
std::unique_lock lock{mutex};
return controller.motion_state;
}
ControllerColors EmulatedController::GetColors() const {
std::unique_lock lock{mutex};
return controller.colors_state;
}
BatteryLevelState EmulatedController::GetBattery() const {
std::unique_lock lock{mutex};
return controller.battery_state;
}
const CameraState& EmulatedController::GetCamera() const {
std::unique_lock lock{mutex};
return controller.camera_state;
}
@@ -1945,15 +1862,14 @@ RingSensorForce EmulatedController::GetRingSensorForce() const {
}
const NfcState& EmulatedController::GetNfc() const {
std::unique_lock lock{mutex};
return controller.nfc_state;
}
NpadColor EmulatedController::GetNpadColor(u32 color) {
return {
.r = static_cast<u8>((color >> 16) & 0xFF),
.g = static_cast<u8>((color >> 8) & 0xFF),
.b = static_cast<u8>(color & 0xFF),
.r = u8((color >> 16) & 0xFF),
.g = u8((color >> 8) & 0xFF),
.b = u8(color & 0xFF),
.a = 0xff,
};
}
+13 -13
View File
@@ -11,8 +11,10 @@
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <ankerl/unordered_dense.h>
#include <vector>
#include <atomic>
#include <ankerl/unordered_dense.h>
#include "common/common_types.h"
#include "common/input.h"
@@ -576,13 +578,7 @@ private:
NpadButton GetTurboButtonMask() const;
const NpadIdType npad_id_type;
NpadStyleIndex npad_type{NpadStyleIndex::None};
NpadStyleIndex original_npad_type{NpadStyleIndex::None};
NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false};
bool is_configuring{false};
bool is_initialized{false};
bool system_buttons_enabled{true};
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
u32 turbo_button_state{0};
std::size_t nfc_handles{0};
@@ -591,9 +587,16 @@ private:
std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{};
std::array<bool, HIDCore::available_controllers> controller_connected{};
// Atomically synched values
std::atomic<HID::NpadStyleIndex> npad_type{HID::NpadStyleIndex::None};
std::atomic<HID::NpadStyleIndex> original_npad_type{HID::NpadStyleIndex::None};
// Temporary values to avoid doing changes while the controller is in configuring mode
NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
bool tmp_is_connected{false};
std::atomic<HID::NpadStyleIndex> tmp_npad_type{HID::NpadStyleIndex::None};
std::atomic<bool> tmp_is_connected{false};
std::atomic<bool> is_connected{false};
std::atomic<bool> is_configuring{false};
std::atomic<bool> is_initialized{false};
std::atomic<bool> system_buttons_enabled{true};
ButtonParams button_params;
StickParams stick_params;
@@ -632,10 +635,7 @@ private:
StickDevices virtual_stick_devices;
ControllerMotionDevices virtual_motion_devices;
mutable std::shared_mutex mutex;
mutable std::shared_mutex callback_mutex;
mutable std::shared_mutex npad_mutex;
mutable std::shared_mutex connect_mutex;
mutable std::mutex callback_mutex;
ankerl::unordered_dense::map<int, ControllerUpdateCallback> callback_list;
int last_callback_key = 0;
@@ -221,12 +221,17 @@ private:
Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR::Value& index) {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) {
const DescriptorIndex idx{ctx, index};
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
idx.Decorate(ctx, pointer);
const Id object{ctx.OpLoad(def.sampled_type, pointer)};
idx.Decorate(ctx, object);
return object;
if (Settings::values.legacy_descriptor_indices.GetValue()) {
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, ctx.Def(index))};
return ctx.OpLoad(def.sampled_type, pointer);
} else {
const DescriptorIndex idx{ctx, index};
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
idx.Decorate(ctx, pointer);
const Id object{ctx.OpLoad(def.sampled_type, pointer)};
idx.Decorate(ctx, object);
return object;
}
} else {
return ctx.OpLoad(def.sampled_type, def.id);
}
@@ -244,14 +249,20 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
} else {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) {
const DescriptorIndex idx{ctx, index};
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
idx.Decorate(ctx, ptr);
const Id object{ctx.OpLoad(def.sampled_type, ptr)};
idx.Decorate(ctx, object);
const Id image{ctx.OpImage(def.image_type, object)};
idx.Decorate(ctx, image);
return image;
if (Settings::values.legacy_descriptor_indices.GetValue()) {
const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)};
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)};
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, ptr));
} else {
const DescriptorIndex idx{ctx, index};
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
idx.Decorate(ctx, ptr);
const Id object{ctx.OpLoad(def.sampled_type, ptr)};
idx.Decorate(ctx, object);
const Id image{ctx.OpImage(def.image_type, object)};
idx.Decorate(ctx, image);
return image;
}
}
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id));
}
@@ -12,6 +12,7 @@
#include <limits>
#include <boost/container/small_vector.hpp>
#include "common/settings.h"
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/breadth_first_search.h"
@@ -461,7 +462,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
.secondary_offset = 0,
.secondary_shift_left = 0,
.dynamic_offset = dynamic_offset,
.count = DynamicDescriptorCount(base_offset, size_shift),
.count = Settings::values.legacy_descriptor_indices.GetValue() ? 8 : DynamicDescriptorCount(base_offset, size_shift),
.has_secondary = false,
};
}
@@ -733,9 +734,10 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
break;
}
u32 index;
const u32 size_shift{cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset)
: DESCRIPTOR_SIZE_SHIFT};
u32 count{cbuf.count};
u32 size_shift = cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset) : DESCRIPTOR_SIZE_SHIFT;
if (Settings::values.legacy_descriptor_indices.GetValue())
size_shift = DESCRIPTOR_SIZE_SHIFT;
u32 count = cbuf.count;
switch (inst->GetOpcode()) {
case IR::Opcode::ImageRead:
case IR::Opcode::ImageAtomicIAdd32:
@@ -821,8 +823,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
const auto insert_point{IR::Block::InstructionList::s_iterator_to(*inst)};
IR::IREmitter ir{*texture_inst.block, insert_point};
const IR::U32 shift{ir.Imm32(size_shift)};
inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift),
ir.Imm32(count - 1)));
inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift), ir.Imm32(count - 1)));
} else {
inst->SetArg(0, IR::Value{});
}
+2 -14
View File
@@ -11,17 +11,9 @@
#include <fstream>
#include <variant>
#ifdef ARCHITECTURE_x86_64
// xbyak hates human beings
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wshadow"
#endif
#ifdef __clang__
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wshadow"
#endif
#include <xbyak/xbyak.h>
#include "common/x64/xbyak.h"
#endif
#include "common/assert.h"
@@ -39,10 +31,6 @@
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/logging.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/xbyak_abi.h"
#include "common/x64/xbyak_util.h"
#endif
#include "video_core/engines/maxwell_3d.h"
namespace Tegra {
@@ -217,6 +217,12 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_UNORM_BLOCK, 0, ASTC_2D_6X5_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_SRGB_BLOCK, 0, ASTC_2D_6X5_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 0, E5B9G9R9_FLOAT) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, 0, ETC2_RGB_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, 0, ETC2_RGBA_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, 0, ETC2_RGB_PTA_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \
/* Depth formats */ \
SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \
SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \
@@ -253,8 +259,8 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
break;
}
}
// Transcode on hardware that doesn't support BCn natively
if (!device.IsOptimalBcnSupported() && VideoCore::Surface::IsPixelFormatBCn(pixel_format)) {
// Transcode on hardware that doesn't support BCn natively
if (pixel_format == PixelFormat::BC4_SNORM) {
tuple.format = VK_FORMAT_R8_SNORM;
} else if (pixel_format == PixelFormat::BC4_UNORM) {
@@ -270,6 +276,9 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
} else {
tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
}
} else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) {
// Transcode on hardware that doesn't support ETC2 natively
tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
}
bool const attachable = (tuple.usage & usage_attachable) != 0;
bool const storage = (tuple.usage & usage_storage) != 0;
+18 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
@@ -336,6 +336,20 @@ bool IsPixelFormatBCn(PixelFormat format) {
}
}
bool IsPixelFormatETC2(PixelFormat format) {
switch (format) {
case PixelFormat::ETC2_RGB_UNORM:
case PixelFormat::ETC2_RGBA_UNORM:
case PixelFormat::ETC2_RGB_PTA_UNORM:
case PixelFormat::ETC2_RGB_SRGB:
case PixelFormat::ETC2_RGBA_SRGB:
case PixelFormat::ETC2_RGB_PTA_SRGB:
return true;
default:
return false;
}
}
bool IsPixelFormatSRGB(PixelFormat format) {
switch (format) {
case PixelFormat::A8B8G8R8_SRGB:
@@ -344,6 +358,9 @@ bool IsPixelFormatSRGB(PixelFormat format) {
case PixelFormat::BC2_SRGB:
case PixelFormat::BC3_SRGB:
case PixelFormat::BC7_SRGB:
case PixelFormat::ETC2_RGB_SRGB:
case PixelFormat::ETC2_RGBA_SRGB:
case PixelFormat::ETC2_RGB_PTA_SRGB:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
+7 -2
View File
@@ -111,6 +111,12 @@ namespace VideoCore::Surface {
PIXEL_FORMAT_ELEM(ASTC_2D_6X5_UNORM, 6, 5, 128) \
PIXEL_FORMAT_ELEM(ASTC_2D_6X5_SRGB, 6, 5, 128) \
PIXEL_FORMAT_ELEM(E5B9G9R9_FLOAT, 1, 1, 32) \
PIXEL_FORMAT_ELEM(ETC2_RGB_UNORM, 4, 4, 64) \
PIXEL_FORMAT_ELEM(ETC2_RGBA_UNORM, 4, 4, 128) \
PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_UNORM, 4, 4, 64) \
PIXEL_FORMAT_ELEM(ETC2_RGB_SRGB, 4, 4, 64) \
PIXEL_FORMAT_ELEM(ETC2_RGBA_SRGB, 4, 4, 128) \
PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_SRGB, 4, 4, 64) \
/* Depth formats */ \
PIXEL_FORMAT_ELEM(D32_FLOAT, 1, 1, 32) \
PIXEL_FORMAT_ELEM(D16_UNORM, 1, 1, 16) \
@@ -181,8 +187,6 @@ constexpr u32 BitsPerBlock(PixelFormat format) noexcept {
}
}
#undef PIXEL_FORMAT_LIST
/// Returns the sizer in bytes of the specified pixel format
constexpr u32 BytesPerBlock(PixelFormat pixel_format) {
return BitsPerBlock(pixel_format) / CHAR_BIT;
@@ -198,6 +202,7 @@ SurfaceType GetFormatType(PixelFormat pixel_format);
bool HasAlpha(PixelFormat pixel_format);
bool IsPixelFormatASTC(PixelFormat format);
bool IsPixelFormatBCn(PixelFormat format);
bool IsPixelFormatETC2(PixelFormat format);
bool IsPixelFormatSRGB(PixelFormat format);
bool IsPixelFormatInteger(PixelFormat format);
bool IsPixelFormatSignedInteger(PixelFormat format);
@@ -191,6 +191,20 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::BC6H_SFLOAT;
case Hash(TextureFormat::BC6H_U16, FLOAT):
return PixelFormat::BC6H_UFLOAT;
/* ETC2 */
case Hash(TextureFormat::ETC2_RGB, UNORM, LINEAR):
return PixelFormat::ETC2_RGB_UNORM;
case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, LINEAR):
return PixelFormat::ETC2_RGB_PTA_UNORM;
case Hash(TextureFormat::ETC2_RGBA, UNORM, LINEAR):
return PixelFormat::ETC2_RGBA_UNORM;
case Hash(TextureFormat::ETC2_RGB, UNORM, SRGB):
return PixelFormat::ETC2_RGB_SRGB;
case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, SRGB):
return PixelFormat::ETC2_RGB_PTA_SRGB;
case Hash(TextureFormat::ETC2_RGBA, UNORM, SRGB):
return PixelFormat::ETC2_RGBA_SRGB;
/* ASTC */
case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR):
return PixelFormat::ASTC_2D_4X4_UNORM;
case Hash(TextureFormat::ASTC_2D_4X4, UNORM, SRGB):
+6 -204
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -17,210 +20,9 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
using VideoCore::Surface::PixelFormat;
const string_view name = [format] {
switch (format) {
case PixelFormat::A8B8G8R8_UNORM:
return "A8B8G8R8_UNORM";
case PixelFormat::A8B8G8R8_SNORM:
return "A8B8G8R8_SNORM";
case PixelFormat::A8B8G8R8_SINT:
return "A8B8G8R8_SINT";
case PixelFormat::A8B8G8R8_UINT:
return "A8B8G8R8_UINT";
case PixelFormat::R5G6B5_UNORM:
return "R5G6B5_UNORM";
case PixelFormat::B5G6R5_UNORM:
return "B5G6R5_UNORM";
case PixelFormat::A1R5G5B5_UNORM:
return "A1R5G5B5_UNORM";
case PixelFormat::A2B10G10R10_UNORM:
return "A2B10G10R10_UNORM";
case PixelFormat::A2B10G10R10_UINT:
return "A2B10G10R10_UINT";
case PixelFormat::A2R10G10B10_UNORM:
return "A2R10G10B10_UNORM";
case PixelFormat::A1B5G5R5_UNORM:
return "A1B5G5R5_UNORM";
case PixelFormat::A5B5G5R1_UNORM:
return "A5B5G5R1_UNORM";
case PixelFormat::R8_UNORM:
return "R8_UNORM";
case PixelFormat::R8_SNORM:
return "R8_SNORM";
case PixelFormat::R8_SINT:
return "R8_SINT";
case PixelFormat::R8_UINT:
return "R8_UINT";
case PixelFormat::R16G16B16A16_FLOAT:
return "R16G16B16A16_FLOAT";
case PixelFormat::R16G16B16A16_UNORM:
return "R16G16B16A16_UNORM";
case PixelFormat::R16G16B16A16_SNORM:
return "R16G16B16A16_SNORM";
case PixelFormat::R16G16B16A16_SINT:
return "R16G16B16A16_SINT";
case PixelFormat::R16G16B16A16_UINT:
return "R16G16B16A16_UINT";
case PixelFormat::B10G11R11_FLOAT:
return "B10G11R11_FLOAT";
case PixelFormat::R32G32B32A32_UINT:
return "R32G32B32A32_UINT";
case PixelFormat::BC1_RGBA_UNORM:
return "BC1_RGBA_UNORM";
case PixelFormat::BC2_UNORM:
return "BC2_UNORM";
case PixelFormat::BC3_UNORM:
return "BC3_UNORM";
case PixelFormat::BC4_UNORM:
return "BC4_UNORM";
case PixelFormat::BC4_SNORM:
return "BC4_SNORM";
case PixelFormat::BC5_UNORM:
return "BC5_UNORM";
case PixelFormat::BC5_SNORM:
return "BC5_SNORM";
case PixelFormat::BC7_UNORM:
return "BC7_UNORM";
case PixelFormat::BC6H_UFLOAT:
return "BC6H_UFLOAT";
case PixelFormat::BC6H_SFLOAT:
return "BC6H_SFLOAT";
case PixelFormat::ASTC_2D_4X4_UNORM:
return "ASTC_2D_4X4_UNORM";
case PixelFormat::B8G8R8A8_UNORM:
return "B8G8R8A8_UNORM";
case PixelFormat::R32G32B32A32_FLOAT:
return "R32G32B32A32_FLOAT";
case PixelFormat::R32G32B32A32_SINT:
return "R32G32B32A32_SINT";
case PixelFormat::R32G32_FLOAT:
return "R32G32_FLOAT";
case PixelFormat::R32G32_SINT:
return "R32G32_SINT";
case PixelFormat::R32_FLOAT:
return "R32_FLOAT";
case PixelFormat::R16_FLOAT:
return "R16_FLOAT";
case PixelFormat::R16_UNORM:
return "R16_UNORM";
case PixelFormat::R16_SNORM:
return "R16_SNORM";
case PixelFormat::R16_UINT:
return "R16_UINT";
case PixelFormat::R16_SINT:
return "R16_SINT";
case PixelFormat::R16G16_UNORM:
return "R16G16_UNORM";
case PixelFormat::R16G16_FLOAT:
return "R16G16_FLOAT";
case PixelFormat::R16G16_UINT:
return "R16G16_UINT";
case PixelFormat::R16G16_SINT:
return "R16G16_SINT";
case PixelFormat::R16G16_SNORM:
return "R16G16_SNORM";
case PixelFormat::R32G32B32_FLOAT:
return "R32G32B32_FLOAT";
case PixelFormat::A8B8G8R8_SRGB:
return "A8B8G8R8_SRGB";
case PixelFormat::R8G8_UNORM:
return "R8G8_UNORM";
case PixelFormat::R8G8_SNORM:
return "R8G8_SNORM";
case PixelFormat::R8G8_SINT:
return "R8G8_SINT";
case PixelFormat::R8G8_UINT:
return "R8G8_UINT";
case PixelFormat::R32G32_UINT:
return "R32G32_UINT";
case PixelFormat::R16G16B16X16_FLOAT:
return "R16G16B16X16_FLOAT";
case PixelFormat::R32_UINT:
return "R32_UINT";
case PixelFormat::R32_SINT:
return "R32_SINT";
case PixelFormat::ASTC_2D_8X8_UNORM:
return "ASTC_2D_8X8_UNORM";
case PixelFormat::ASTC_2D_8X5_UNORM:
return "ASTC_2D_8X5_UNORM";
case PixelFormat::ASTC_2D_5X4_UNORM:
return "ASTC_2D_5X4_UNORM";
case PixelFormat::B8G8R8A8_SRGB:
return "B8G8R8A8_SRGB";
case PixelFormat::BC1_RGBA_SRGB:
return "BC1_RGBA_SRGB";
case PixelFormat::BC2_SRGB:
return "BC2_SRGB";
case PixelFormat::BC3_SRGB:
return "BC3_SRGB";
case PixelFormat::BC7_SRGB:
return "BC7_SRGB";
case PixelFormat::A4B4G4R4_UNORM:
return "A4B4G4R4_UNORM";
case PixelFormat::G4R4_UNORM:
return "G4R4_UNORM";
case PixelFormat::ASTC_2D_4X4_SRGB:
return "ASTC_2D_4X4_SRGB";
case PixelFormat::ASTC_2D_8X8_SRGB:
return "ASTC_2D_8X8_SRGB";
case PixelFormat::ASTC_2D_8X5_SRGB:
return "ASTC_2D_8X5_SRGB";
case PixelFormat::ASTC_2D_5X4_SRGB:
return "ASTC_2D_5X4_SRGB";
case PixelFormat::ASTC_2D_5X5_UNORM:
return "ASTC_2D_5X5_UNORM";
case PixelFormat::ASTC_2D_5X5_SRGB:
return "ASTC_2D_5X5_SRGB";
case PixelFormat::ASTC_2D_10X8_UNORM:
return "ASTC_2D_10X8_UNORM";
case PixelFormat::ASTC_2D_10X8_SRGB:
return "ASTC_2D_10X8_SRGB";
case PixelFormat::ASTC_2D_6X6_UNORM:
return "ASTC_2D_6X6_UNORM";
case PixelFormat::ASTC_2D_6X6_SRGB:
return "ASTC_2D_6X6_SRGB";
case PixelFormat::ASTC_2D_10X6_UNORM:
return "ASTC_2D_10X6_UNORM";
case PixelFormat::ASTC_2D_10X6_SRGB:
return "ASTC_2D_10X6_SRGB";
case PixelFormat::ASTC_2D_10X5_UNORM:
return "ASTC_2D_10X5_UNORM";
case PixelFormat::ASTC_2D_10X5_SRGB:
return "ASTC_2D_10X5_SRGB";
case PixelFormat::ASTC_2D_10X10_UNORM:
return "ASTC_2D_10X10_UNORM";
case PixelFormat::ASTC_2D_10X10_SRGB:
return "ASTC_2D_10X10_SRGB";
case PixelFormat::ASTC_2D_12X10_UNORM:
return "ASTC_2D_12X10_UNORM";
case PixelFormat::ASTC_2D_12X10_SRGB:
return "ASTC_2D_12X10_SRGB";
case PixelFormat::ASTC_2D_12X12_UNORM:
return "ASTC_2D_12X12_UNORM";
case PixelFormat::ASTC_2D_12X12_SRGB:
return "ASTC_2D_12X12_SRGB";
case PixelFormat::ASTC_2D_8X6_UNORM:
return "ASTC_2D_8X6_UNORM";
case PixelFormat::ASTC_2D_8X6_SRGB:
return "ASTC_2D_8X6_SRGB";
case PixelFormat::ASTC_2D_6X5_UNORM:
return "ASTC_2D_6X5_UNORM";
case PixelFormat::ASTC_2D_6X5_SRGB:
return "ASTC_2D_6X5_SRGB";
case PixelFormat::E5B9G9R9_FLOAT:
return "E5B9G9R9_FLOAT";
case PixelFormat::D32_FLOAT:
return "D32_FLOAT";
case PixelFormat::D16_UNORM:
return "D16_UNORM";
case PixelFormat::X8_D24_UNORM:
return "X8_D24_UNORM";
case PixelFormat::S8_UINT:
return "S8_UINT";
case PixelFormat::D24_UNORM_S8_UINT:
return "D24_UNORM_S8_UINT";
case PixelFormat::S8_UINT_D24_UNORM:
return "S8_UINT_D24_UNORM";
case PixelFormat::D32_FLOAT_S8_UINT:
return "D32_FLOAT_S8_UINT";
#define PIXEL_FORMAT_ELEM(NAME, ...) case PixelFormat::NAME: return #NAME;
PIXEL_FORMAT_LIST
#undef PIXEL_FORMAT_ELEM
case PixelFormat::MaxDepthStencilFormat:
case PixelFormat::Invalid:
return "Invalid";
@@ -363,6 +363,11 @@ public:
return features.features.textureCompressionBC;
}
/// Returns true if ETC2 is natively supported.
bool IsOptimalEtc2Supported() const {
return features.features.textureCompressionETC2;
}
/// Returns true if descriptor aliasing is natively supported.
bool IsDescriptorAliasingSupported() const {
return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;