Compare commits

..

13 Commits

Author SHA1 Message Date
crueter 1d942c5ae2 [desktop] Restore metal layer search
This was cut out in #3916 by accident.

Signed-off-by: crueter <crueter@eden-emu.dev>
2026-05-30 20:38:59 -04:00
lizzie 116377f6d9 [vk] fix oversight with #3874 with invalid image ids (#4026)
oopsie, forgot we had a GC
fixes SSBU

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4026
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-30 21:59:18 +02:00
lizzie 7c32cf03a1 [core/core_timing] better MWAITX and WAITPKG delays (#3984)
This implements MWAITX and WAITPKG extensions (umonitor, mwait) for CPUs that support them.

Reduces wait times and bypasses the timing stuff from the OS that is slow (windows notably). generally it should answer within 0.2 to 0.5 microsecs (since most requests wait for that long).

Also does a general rework of static ctors and stuff

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3984
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-30 21:59:10 +02:00
Yang Liu ff7bbaea7d [dynarmic] bootstrap loongarch64 host build (#4015)
Minimal additions to make Eden compile.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4015
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-30 01:42:23 +02:00
MaranBr c84d605426 [buffer_cache] Fix buffer upload overwriting GPU-modified regions (#4000)
This fixes a bug in Super Mario Odyssey, in Bowser's Kingdom, where particles rapidly freeze and unfreeze in midair.

It also fixes vertex explosions in Super Mario Party Jamboree.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4000
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-05-29 14:01:06 +02:00
lizzie def03f6589 [video_core] fix redundant resize-copy overload and just use default-init resize, to reduce stutter on Mario BP (#3874)
before vs. after

Mario Brothership kept remaking vectors of sizes 256 AND 4095 (TIC) and 1215 AND 524287 (TSC) every single frame, which resulted in a noticeable overhead

the main cause was because of using `resize(n, c)` instead of `resize(n)` (also to aggressively resize for more room beforehand), the copy overload of resize does a copy of... well.. the value over the entire vector, additionally __append() keeps getting called because the capacity goes bonkers and all over the place

![image](/attachments/e3ba07fb-1c85-4d56-9b81-bb16a8150c15)
![image](/attachments/5c4eba26-015a-4c95-9b24-b41695a62e51)

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3874
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-05-29 03:28:47 +02:00
lizzie 5ea24621cf [vk] fix LM3 and TL:LTD multiple NonUniform annotations (#3997)
should fix AMD

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3997
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-05-29 03:28:10 +02:00
crueter 251a3470dc [desktop] Set max stdio limit to 2048 on MSVCRT environments (#4023)
Such as MINGW64.

I legitimately don't have an explanation for this.

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4023
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-05-28 21:22:28 +02:00
crueter 08f65cbd01 [frontend] Fix auto updater flavors on Windows (#4019)
Matches the build ID and compiler now.

Note that this could still use some work on the Windows side of things.
Ideally, it would just replace the executables in place; however, I
think using the setup files will be better.

Most of my concerns w.r.t this issue is that users will want to install
multiple in the same place; however, I think it's fair to just not
support the older versions at all for now. If users really want to do
that, they can use the portable versions and cry about it.

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4019
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-05-28 20:36:16 +02:00
lizzie 8fd495f906 [video_core] fix std::bitset<> dirty tracker OOB, fix slightly wrong index format estimate (#4006)
u8 may have been 0xff, (aka. 255), but bitset was only 255 elements, so doing bitset[255] is technically OOB

additionally the max size estimate for index formats was not correct, there can be up to 256 elements with a u8 format index, not just 255

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4006
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-05-27 00:04:27 +02:00
lizzie 9d55875377 [*] fix missing '#pragma once' on header files (#4014)
`find src -type f -name '*.h' -not -exec grep -q "#pragma once" {} \; -print`

- ignore qt with header guards already
- smaa too

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4014
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-05-26 06:37:59 +02:00
xbzk 8a11bec55a [vk,fence_manager] improvement for antiflicker showed bad for some games, so it was removed from balanced/accurate and kept for antiflicker toggle only (#4010)
in short:

unrelated to the toggle, we found a missing piece that improved antiflicker and shader issues in some games, and we integrated it to balanced/accurate.
now testers confirmed side effects in some other games, so we reverted the integration.

the toggle itself is innocent. and the missing piece was kept for the toggle.
now, when the toggle is off, things are as they were before it.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4010
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-05-26 01:41:11 +02:00
crueter 4a11d5db2f [file_sys] Increase max file limit to 8192 (#4011)
Now hopefully the Redditors can get off my ass for five seconds

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4011
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-05-25 18:53:47 +02:00
81 changed files with 1586 additions and 1092 deletions
+5
View File
@@ -82,6 +82,11 @@ if (ARCHITECTURE_riscv64)
AddJsonPackage(biscuit)
endif()
# Lagoon
if (ARCHITECTURE_loongarch64)
AddJsonPackage(lagoon)
endif()
# Vulkan stuff
AddDependentPackages(vulkan-headers vulkan-utility-libraries)
+6
View File
@@ -67,6 +67,12 @@
"tag": "v%VERSION%",
"hash": "9697e80a7d5d9bcb3ce51051a9a24962fb90ca79d215f1f03ae6b58da8ba13a63b5dda1b4dde3d26ac6445029696b8ef2883f4e5a777b342bba01283ed293856"
},
"lagoon": {
"repo": "loongson-community/lagoon",
"tag": "%VERSION%",
"version": "1.0.0",
"hash": "b9380f99c6effaeccc6d8f81d4942e852c11ad28613df637e155451556ae5826f93765bee57a5c87a9740d2bd1db463ad0f55a947772fe9d57eeabae3efa373e"
},
"libadrenotools": {
"repo": "eden-emulator/libadrenotools",
"sha": "8ba23b42d7",
+22 -1
View File
@@ -8,7 +8,7 @@
include_directories(.)
# Dynarmic
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64) AND NOT YUZU_STATIC_ROOM)
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITECTURE_loongarch64) AND NOT YUZU_STATIC_ROOM)
add_subdirectory(dynarmic)
add_library(dynarmic::dynarmic ALIAS dynarmic)
endif()
@@ -21,20 +21,41 @@ if (YUZU_STATIC_BUILD)
add_compile_definitions(QT_STATICPLUGIN)
endif()
# Build identifiers
if (NIGHTLY_BUILD)
add_compile_definitions(NIGHTLY_BUILD)
endif()
# Legacy (android only)
if (YUZU_LEGACY)
message(WARNING "Making legacy build. Performance may suffer.")
add_compile_definitions(YUZU_LEGACY)
endif()
# Genshin Spoof (android only)
if (GENSHIN_SPOOF)
message(WARNING "Making Genshin spoof build")
add_compile_definitions(GENSHIN_SPOOF)
endif()
# Build ID (mingw only right now)
# Pretty much just refers to the CI "target" for this build
if (NOT BUILD_ID)
if (ARCHITECTURE_x86_64)
set(BUILD_ID amd64)
elseif(ARCHITECTURE_arm64)
if (WIN32)
set(BUILD_ID arm64)
else()
set(BUILD_ID aarch64)
endif()
else()
set(BUILD_ID "${ARCHITECTURE}")
endif()
endif()
add_compile_definitions(BUILD_ID="${BUILD_ID}")
# Set compilation flags
if (MSVC AND NOT CXX_CLANG)
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
+4 -7
View File
@@ -141,12 +141,12 @@ add_library(
vector_math.h
virtual_buffer.cpp
virtual_buffer.h
wall_clock.cpp
wall_clock.h
zstd_compression.cpp
zstd_compression.h
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
fs/symlink.h fs/symlink.cpp
cpu_features.cpp
cpu_features.h
httplib.h
net/net.h net/net.cpp)
@@ -180,10 +180,7 @@ endif()
if(ARCHITECTURE_x86_64)
target_sources(
common
PRIVATE x64/cpu_detect.cpp
x64/cpu_detect.h
x64/cpu_wait.cpp
x64/cpu_wait.h
PRIVATE
x64/rdtsc.cpp
x64/rdtsc.h
x64/xbyak.h)
@@ -234,7 +231,7 @@ if(CXX_CLANG)
endif()
if (BOOST_NO_HEADERS)
target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool)
target_link_libraries(common PUBLIC Boost::algorithm Boost::heap Boost::icl Boost::pool)
else()
target_link_libraries(common PUBLIC Boost::headers)
endif()
+5
View File
@@ -1,7 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2005-2014 Daniel James
// SPDX-FileCopyrightText: 2016 Austin Appleby
// SPDX-License-Identifier: BSL-1.0
#pragma once
#include <array>
#include <climits>
#include <cstdint>
@@ -10,34 +10,32 @@
#include <fstream>
#include <iterator>
#include <optional>
#include <algorithm>
#include <string_view>
#include <thread>
#include <vector>
#include "common/bit_util.h"
#include "common/common_types.h"
#include "common/logging.h"
#include "common/x64/cpu_detect.h"
#include "common/x64/rdtsc.h"
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef _MSC_VER
#include <intrin.h>
static inline u64 xgetbv(u32 index) {
return _xgetbv(index);
}
#else
#if defined(__DragonFly__) || defined(__FreeBSD__)
// clang-format off
#elif defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/types.h>
#include <machine/cpufunc.h>
// clang-format on
#elif defined(__ANDROID__)
#include <sys/system_properties.h>
#endif
#include "common/steady_clock.h"
#include "common/uint128.h"
#include "common/bit_util.h"
#include "common/common_types.h"
#include "common/cpu_features.h"
#include "common/logging.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/rdtsc.h"
#ifdef _MSC_VER
#include <intrin.h>
static inline u64 xgetbv(u32 index) { return _xgetbv(index); }
#else
static inline void __cpuidex(int info[4], u32 function_id, u32 subfunction_id) {
#if defined(__DragonFly__) || defined(__FreeBSD__)
// Despite the name, this is just do_cpuid() with ECX as second input.
@@ -50,11 +48,7 @@ static inline void __cpuidex(int info[4], u32 function_id, u32 subfunction_id) {
: "a"(function_id), "c"(subfunction_id));
#endif
}
static inline void __cpuid(int info[4], u32 function_id) {
return __cpuidex(info, function_id, 0);
}
static inline void __cpuid(int info[4], u32 function_id) { return __cpuidex(info, function_id, 0); }
#define _XCR_XFEATURE_ENABLED_MASK 0
static inline u64 xgetbv(u32 index) {
u32 eax, edx;
@@ -62,9 +56,10 @@ static inline u64 xgetbv(u32 index) {
return ((u64)edx << 32) | eax;
}
#endif // _MSC_VER
#endif
namespace Common {
#ifdef ARCHITECTURE_x86_64
CPUCaps::Manufacturer CPUCaps::ParseManufacturer(std::string_view brand_string) {
if (brand_string == "GenuineIntel") {
return Manufacturer::Intel;
@@ -76,13 +71,53 @@ CPUCaps::Manufacturer CPUCaps::ParseManufacturer(std::string_view brand_string)
return Manufacturer::Unknown;
}
// Detects the various CPU features
static CPUCaps Detect() {
std::optional<int> GetProcessorCount() {
#if defined(_WIN32)
// Get the buffer length.
DWORD length = 0;
GetLogicalProcessorInformation(nullptr, &length);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
LOG_ERROR(Frontend, "Failed to query core count.");
return std::nullopt;
}
std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buffer(
length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
// Now query the core count.
if (!GetLogicalProcessorInformation(buffer.data(), &length)) {
LOG_ERROR(Frontend, "Failed to query core count.");
return std::nullopt;
}
return static_cast<int>(
std::count_if(buffer.cbegin(), buffer.cend(), [](const auto& proc_info) {
return proc_info.Relationship == RelationProcessorCore;
}));
#elif defined(__unix__)
const int thread_count = std::thread::hardware_concurrency();
std::ifstream smt("/sys/devices/system/cpu/smt/active");
char state = '0';
if (smt) {
smt.read(&state, sizeof(state));
}
switch (state) {
case '0':
return thread_count;
case '1':
return thread_count / 2;
default:
return std::nullopt;
}
#else
// Shame on you
return std::nullopt;
#endif
}
/// @brief Detects the various CPU features
const CPUCaps g_cpu_caps = [] {
CPUCaps caps = {};
// Assumes the CPU supports the CPUID instruction. Those that don't would likely not support
// yuzu at all anyway
int cpu_id[4];
// Detect CPU's CPUID capabilities and grab manufacturer string
@@ -183,9 +218,8 @@ static CPUCaps Detect() {
// https://github.com/torvalds/linux/blob/master/tools/power/x86/turbostat/turbostat.c#L5569
// but it's easier to just estimate the TSC tick rate for these cases.
if (caps.tsc_crystal_ratio_denominator) {
caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
caps.tsc_crystal_ratio_numerator /
caps.tsc_crystal_ratio_denominator;
caps.tsc_frequency = u64(caps.crystal_frequency)
* caps.tsc_crystal_ratio_numerator / caps.tsc_crystal_ratio_denominator;
} else {
caps.tsc_frequency = X64::EstimateRDTSCFrequency();
}
@@ -197,54 +231,196 @@ static CPUCaps Detect() {
caps.max_frequency = cpu_id[1];
caps.bus_frequency = cpu_id[2];
}
return caps;
}
}();
const CPUCaps& GetCPUCaps() {
static CPUCaps caps = Detect();
return caps;
}
std::optional<int> GetProcessorCount() {
#if defined(_WIN32)
// Get the buffer length.
DWORD length = 0;
GetLogicalProcessorInformation(nullptr, &length);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
LOG_ERROR(Frontend, "Failed to query core count.");
return std::nullopt;
}
std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buffer(
length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
// Now query the core count.
if (!GetLogicalProcessorInformation(buffer.data(), &length)) {
LOG_ERROR(Frontend, "Failed to query core count.");
return std::nullopt;
}
return static_cast<int>(
std::count_if(buffer.cbegin(), buffer.cend(), [](const auto& proc_info) {
return proc_info.Relationship == RelationProcessorCore;
}));
#elif defined(__unix__)
const int thread_count = std::thread::hardware_concurrency();
std::ifstream smt("/sys/devices/system/cpu/smt/active");
char state = '0';
if (smt) {
smt.read(&state, sizeof(state));
}
switch (state) {
case '0':
return thread_count;
case '1':
return thread_count / 2;
default:
return std::nullopt;
}
#else
// Shame on you
return std::nullopt;
#endif
#if defined(ARCHITECTURE_x86_64)
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept
: rdtsc_frequency{rdtsc_frequency_}
, ns_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_) : 0}
, us_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_) : 0}
, ms_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_) : 0}
, rdtsc_ns_factor{invariant_ ? GetFixedPoint64Factor(rdtsc_frequency_, NsRatio::den) : 1}
, cntpct_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_) : 0}
, gputick_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_) : 0}
, invariant{invariant_}
{}
std::chrono::nanoseconds WallClock::GetTimeNS() const {
if (!invariant)
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_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::steady_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::steady_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 {
return invariant;
}
u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const {
return invariant ? MultiplyHigh(ns.count(), rdtsc_ns_factor) : ns.count();
}
#elif defined(HAS_NCE)
namespace {
[[nodiscard]] Common::WallClock::FactorType GetFixedPointFactor(u64 num, u64 den) noexcept {
return (Common::WallClock::FactorType(num) << 64) / den;
}
[[nodiscard]] u64 MultiplyHigh(u64 m, Common::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
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);
cntfrq_ns_factor = GetFixedPointFactor(host_cntfrq, NsRatio::den);
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;
}
u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const {
return MultiplyHigh(ns.count(), cntfrq_ns_factor);
}
#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::steady_clock::now().time_since_epoch());
}
std::chrono::microseconds WallClock::GetTimeUS() const {
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch());
}
std::chrono::milliseconds WallClock::GetTimeMS() const {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_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;
}
u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const {
return ns.count();
}
#endif
// Wall clock MUST be initialized AFTER g_cpu_caps
// C++ only guarantees ctor init in the order they appear in TU
const WallClock g_wall_clock = [] {
#if defined(ARCHITECTURE_x86_64)
auto const& caps = Common::g_cpu_caps;
return WallClock(caps.invariant_tsc && caps.tsc_frequency >= std::nano::den, caps.tsc_frequency);
#elif defined(HAS_NCE)
return WallClock(false, 1);
#else
return WallClock(true, 1);
#endif
}();
} // namespace Common
@@ -1,11 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <string_view>
#include <chrono>
#include <memory>
#include <ratio>
@@ -43,6 +46,9 @@ public:
/// @returns Whether the clock directly uses the host's hardware clock.
bool IsNative() const;
// @returns Nanoseconds to native ticks
u64 NsToTicks(std::chrono::nanoseconds ns) const;
static inline u64 NSToCNTPCT(u64 ns) {
return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
}
@@ -69,7 +75,6 @@ public:
return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den;
}
protected:
using NsRatio = std::nano;
using UsRatio = std::micro;
using MsRatio = std::milli;
@@ -87,31 +92,96 @@ protected:
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 rdtsc_ns_factor;
u64 cntpct_rdtsc_factor;
u64 gputick_rdtsc_factor;
bool invariant;
#elif defined(HAS_NCE)
public:
using FactorType = unsigned __int128;
FactorType GetGuestCNTFRQFactor() const {
[[nodiscard]] inline FactorType GetGuestCNTFRQFactor() const {
return guest_cntfrq_factor;
}
protected:
FactorType ns_cntfrq_factor;
FactorType us_cntfrq_factor;
FactorType ms_cntfrq_factor;
FactorType cntfrq_ns_factor;
FactorType guest_cntfrq_factor;
FactorType gputick_cntfrq_factor;
#else
#endif
};
[[nodiscard]] WallClock CreateOptimalClock() noexcept;
#ifdef ARCHITECTURE_x86_64
/// x86/x64 CPU capabilities that may be detected by this module
struct CPUCaps {
enum class Manufacturer : u8 {
Unknown = 0,
Intel = 1,
AMD = 2,
Hygon = 3,
};
static Manufacturer ParseManufacturer(std::string_view brand_string);
Manufacturer manufacturer;
char brand_string[13];
char cpu_string[48];
u32 base_frequency;
u32 max_frequency;
u32 bus_frequency;
u32 tsc_crystal_ratio_denominator;
u32 tsc_crystal_ratio_numerator;
u32 crystal_frequency;
u64 tsc_frequency; // Derived from the above three values
bool sse3 : 1;
bool ssse3 : 1;
bool sse4_1 : 1;
bool sse4_2 : 1;
bool avx : 1;
bool avx2 : 1;
bool avx512f : 1;
bool avx512dq : 1;
bool avx512cd : 1;
bool avx512bw : 1;
bool avx512vl : 1;
bool avx512vbmi : 1;
bool avx512bitalg : 1;
bool aes : 1;
bool bmi1 : 1;
bool bmi2 : 1;
bool f16c : 1;
bool fma : 1;
bool gfni : 1;
bool invariant_tsc : 1;
bool lzcnt : 1;
bool monitorx : 1;
bool movbe : 1;
bool pclmulqdq : 1;
bool popcnt : 1;
bool sha : 1;
bool waitpkg : 1;
};
#else
struct CPUCaps {
bool padding;
};
#endif
/// Detects CPU core count
std::optional<int> GetProcessorCount();
/// @brief Global cpu caps
extern const CPUCaps g_cpu_caps;
/// @brief Global wall clock
extern const WallClock g_wall_clock;
} // namespace Common
+13 -9
View File
@@ -62,16 +62,20 @@ std::vector<Asset> Release::GetPlatformAssets() const {
#ifdef _WIN32
#ifdef ARCHITECTURE_x86_64
find_asset("Standard", {"amd64-msvc-standard.exe", "amd64-msvc-standard.zip", "mingw-amd64-gcc-standard.exe", "mingw-amd64-gcc-standard.zip"});
find_asset("PGO", {"mingw-amd64-clang-pgo.exe", "mingw-amd64-clang-pgo.zip"});
#ifdef _MSC_VER
find_asset("Standard", {"amd64-msvc-standard.exe", "amd64-msvc-standard.zip"});
#else // _MSC_VER
find_asset("Standard", {BUILD_ID "-gcc-standard.exe", BUILD_ID "-gcc-standard.zip"});
find_asset("PGO", {BUILD_ID "-clang-pgo.exe", BUILD_ID "-clang-pgo.zip"});
#endif // _MSC_VER
#elif defined(ARCHITECTURE_arm64)
find_asset("Standard", {"mingw-arm64-clang-standard.exe", "mingw-arm64-clang-standard.zip"});
find_asset("PGO", {"mingw-arm64-clang-pgo.exe", "mingw-arm64-clang-pgo.zip"});
#endif
find_asset("Standard", {"arm64-clang-standard.exe", "arm64-clang-standard.zip"});
find_asset("PGO", {"arm64-clang-pgo.exe", "arm64-clang-pgo.zip"});
#endif // ARCHITECTURE_arm64
#elif defined(__APPLE__)
#ifdef ARCHITECTURE_arm64
find_asset("Standard", {".dmg", ".tar.gz"});
#endif
#endif // ARCHITECTURE_arm64
#elif defined(__ANDROID__)
#ifdef ARCHITECTURE_x86_64
find_asset("Standard", {"chromeos.apk"});
@@ -82,9 +86,9 @@ std::vector<Asset> Release::GetPlatformAssets() const {
find_asset("Standard", {"optimized.apk"});
#else
find_asset("Standard", {"standard.apk"});
#endif
#endif
#endif
#endif // GENSHIN_SPOOF
#endif // ARCHITECTURE_arm64
#endif // __APPLE__
return found_assets;
}
+20 -18
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,10 +20,14 @@
namespace Common {
struct SlotId {
static constexpr u32 TAGGED_MASK = 0x7fffffff;
static constexpr u32 TAGGED_VALUE = 0x80000000;
static constexpr u32 INVALID_INDEX = (std::numeric_limits<u32>::max)();
constexpr u32 Value() const noexcept {
return index & (~TAGGED_VALUE);
}
constexpr auto operator<=>(const SlotId&) const noexcept = default;
constexpr explicit operator bool() const noexcept {
return index != INVALID_INDEX;
}
@@ -47,12 +51,12 @@ public:
Iterator& operator++() noexcept {
const u64* const bitset = slot_vector->stored_bitset.data();
const u32 size = static_cast<u32>(slot_vector->stored_bitset.size()) * 64;
if (id.index < size) {
if (id.Value() < size) {
do {
++id.index;
} while (id.index < size && !IsValid(bitset));
if (id.index == size) {
id.index = SlotId::INVALID_INDEX;
} while (id.Value() < size && !IsValid(bitset));
if (id.Value() == size) {
id = SlotId{};
}
}
return *this;
@@ -85,7 +89,7 @@ public:
: slot_vector{slot_vector_}, id{id_} {}
bool IsValid(const u64* bitset) const noexcept {
return ((bitset[id.index / 64] >> (id.index % 64)) & 1) != 0;
return ((bitset[id.Value() / 64] >> (id.Value() % 64)) & 1) != 0;
}
SlotVector<T>* slot_vector;
@@ -107,12 +111,12 @@ public:
[[nodiscard]] T& operator[](SlotId id) noexcept {
ValidateIndex(id);
return values[id.index].object;
return values[id.Value()].object;
}
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
ValidateIndex(id);
return values[id.index].object;
return values[id.Value()].object;
}
template <typename... Args>
@@ -125,9 +129,9 @@ public:
}
void erase(SlotId id) noexcept {
values[id.index].object.~T();
free_list.push_back(id.index);
ResetStorageBit(id.index);
values[id.Value()].object.~T();
free_list.push_back(id.Value());
ResetStorageBit(id.Value());
}
[[nodiscard]] Iterator begin() noexcept {
@@ -141,7 +145,7 @@ public:
}
[[nodiscard]] Iterator end() noexcept {
return Iterator(this, SlotId{SlotId::INVALID_INDEX});
return Iterator(this, SlotId{});
}
[[nodiscard]] size_t size() const noexcept {
@@ -175,8 +179,8 @@ private:
void ValidateIndex(SlotId id) const noexcept {
DEBUG_ASSERT(id);
DEBUG_ASSERT(id.index / 64 < stored_bitset.size());
DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0);
DEBUG_ASSERT(id.Value() / 64 < stored_bitset.size());
DEBUG_ASSERT(((stored_bitset[id.Value() / 64] >> (id.Value() % 64)) & 1) != 0);
}
[[nodiscard]] u32 FreeValueIndex() noexcept {
@@ -208,9 +212,7 @@ private:
const size_t old_free_size = free_list.size();
free_list.resize(old_free_size + (new_capacity - values_capacity));
std::iota(free_list.begin() + old_free_size, free_list.end(),
static_cast<u32>(values_capacity));
std::iota(free_list.begin() + old_free_size, free_list.end(), u32(values_capacity));
delete[] values;
values = new_values;
values_capacity = new_capacity;
+104 -2
View File
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <limits>
#include <string>
#include <thread>
@@ -18,24 +20,35 @@
#elif defined(_WIN32)
#include <windows.h>
#include "common/string_util.h"
#include "common/windows/timer_resolution.h"
#else
#if defined(__FreeBSD__)
#include <sys/cpuset.h>
#include <sys/_cpuset.h>
#include <pthread_np.h>
// Compatibility with CPUset
#define cpu_set_t cpuset_t
#elif defined(__DragonFly__) || defined(__OpenBSD__) || defined(__Bitrig__)
#include <pthread_np.h>
#endif
#include <pthread.h>
#include <sched.h>
#endif
#ifndef _WIN32
#include <unistd.h>
#endif
#ifdef __FreeBSD__
# define cpu_set_t cpuset_t
#include "common/cpu_features.h"
#ifdef ARCHITECTURE_x86_64
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
#include "common/x64/rdtsc.h"
#endif
#include "core/core_timing.h"
namespace Common {
@@ -144,4 +157,93 @@ void PinCurrentThreadToPerformanceCore(size_t core_id) {
}
}
#ifdef ARCHITECTURE_x86_64
// On Linux and UNIX systems, a futex would nominally be used to cover the costs
// the idea is that it's intuitivelly cheaper to use a direct instruction as opposed to a full futex call
// the underlying libc++ implementation uses pthread_cond_timedwait which MAY invoke a futex
// Let's pretend the OS is too expensive to jump into, and avoid ANY context switches
// this should *IN THEORY* lower CPU usage while just waiting for stuff effectively
// For windows the minimal quanta resolution is about 500us, and normal CRT cond var is 1.5ms(?)
// so may as well avoid that too
// Let's just give ALL platforms the same mechanisms (almost) for when they have umonitor OR waitpkg
#ifdef __clang__
__attribute__((target("waitpkg,mwaitx")))
#elif defined(__GNUC__)
#pragma GCC target("waitpkg")
#pragma GCC target("mwaitx")
#endif
bool Event::WaitFor(const std::chrono::nanoseconds time) {
#ifdef _WIN32
auto const start = Common::X64::FencedRDTSC();
auto const& caps = Common::g_cpu_caps;
[[maybe_unused]] auto const end = start + Common::g_wall_clock.NsToTicks(time);
if (caps.monitorx) {
while (true) {
// Armed monitor, as per manual, MWAITX must be conditional if the condition isn't satisfied
// to prevent a race condition.
_mm_monitorx(reinterpret_cast<u64*>(std::addressof(is_set)), 0, 0);
if (!is_set.load()) {
// RDTSC may be fenced here due to atomic load
auto const now = _rdtsc();
if (end > now) {
u32 const cycles = std::min<u32>((std::numeric_limits<u32>::max)(), s64(end) - s64(now));
// See here: https://github.com/torvalds/linux/blob/948a64995aca6820abefd17f1a4258f5835c5ad9/arch/x86/lib/delay.c#L93
// MWAITX accepts a 32-bit input timer which determines the total number of cycles to wait for
// NOT THE TOTAL ABSOLUTE TSC VALUE, it's just a delta
// BIT[1] = use a timer
// Hint = 0: Use C1 state when sleepy (means slower wakeup but better savings)
_mm_mwaitx(1 << 1, 0u, cycles);
if (!is_set.load())
return false;
} else
return false; //timeout
}
bool expected = true;
if (is_set.compare_exchange_weak(expected, false, std::memory_order_release))
return true;
}
} else if (caps.waitpkg) {
// #UD If CPUID.7.0:ECX.WAITPKG[bit 5]=0.
while (true) {
_umonitor(std::addressof(is_set));
if (!is_set.load() && !_umwait(0, end)) //umwait is absolute time!!!
return false;
bool expected = true;
if (is_set.compare_exchange_weak(expected, false, std::memory_order_release))
return true;
}
} else {
while (!is_set.load() && end > _rdtsc())
Common::Windows::SleepForOneTick();
if (is_set.load())
Reset();
return true;
}
#else
std::unique_lock lk{mutex};
if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
#endif
}
#else
bool Event::WaitFor(const std::chrono::nanoseconds time) {
#ifdef _WIN32
auto const end = Common::g_wall_clock.GetTimeNS() + time;
while (!is_set.load() && end > Common::g_wall_clock.GetTimeNS())
Common::Windows::SleepForOneTick();
if (is_set.load())
Reset();
return true;
#else
std::unique_lock lk{mutex};
if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
#endif
}
#endif
} // namespace Common
+4 -10
View File
@@ -34,16 +34,10 @@ public:
is_set = false;
}
bool WaitFor(const std::chrono::nanoseconds& time) {
std::unique_lock lk{mutex};
if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
}
bool WaitFor(const std::chrono::nanoseconds time);
template <class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
template<class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration> time) {
std::unique_lock lk{mutex};
if (!condvar.wait_until(lk, time, [this] { return is_set.load(); }))
return false;
@@ -63,9 +57,9 @@ public:
}
private:
alignas(64) std::atomic<bool> is_set{false};
std::condition_variable condvar;
std::mutex mutex;
std::atomic_bool is_set{false};
};
class Barrier {
-197
View File
@@ -1,197 +0,0 @@
// 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/rdtsc.h"
#endif
namespace Common {
#if defined(ARCHITECTURE_x86_64)
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept
: invariant{invariant_}
, rdtsc_frequency{rdtsc_frequency_}
, ns_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_)}
, us_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_)}
, ms_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_)}
, cntpct_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_)}
, gputick_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_)}
{}
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)
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
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), caps.tsc_frequency);
#elif defined(HAS_NCE)
return WallClock(false, 1);
#else
return WallClock(true, 1);
#endif
}
} // namespace Common
-82
View File
@@ -1,82 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <string_view>
#include "common/common_types.h"
namespace Common {
/// x86/x64 CPU capabilities that may be detected by this module
struct CPUCaps {
enum class Manufacturer : u8 {
Unknown = 0,
Intel = 1,
AMD = 2,
Hygon = 3,
};
static Manufacturer ParseManufacturer(std::string_view brand_string);
Manufacturer manufacturer;
char brand_string[13];
char cpu_string[48];
u32 base_frequency;
u32 max_frequency;
u32 bus_frequency;
u32 tsc_crystal_ratio_denominator;
u32 tsc_crystal_ratio_numerator;
u32 crystal_frequency;
u64 tsc_frequency; // Derived from the above three values
bool sse3 : 1;
bool ssse3 : 1;
bool sse4_1 : 1;
bool sse4_2 : 1;
bool avx : 1;
bool avx2 : 1;
bool avx512f : 1;
bool avx512dq : 1;
bool avx512cd : 1;
bool avx512bw : 1;
bool avx512vl : 1;
bool avx512vbmi : 1;
bool avx512bitalg : 1;
bool aes : 1;
bool bmi1 : 1;
bool bmi2 : 1;
bool f16c : 1;
bool fma : 1;
bool gfni : 1;
bool invariant_tsc : 1;
bool lzcnt : 1;
bool monitorx : 1;
bool movbe : 1;
bool pclmulqdq : 1;
bool popcnt : 1;
bool sha : 1;
bool waitpkg : 1;
};
/**
* Gets the supported capabilities of the host CPU
* @return Reference to a CPUCaps struct with the detected host CPU capabilities
*/
const CPUCaps& GetCPUCaps();
/// Detects CPU core count
std::optional<int> GetProcessorCount();
} // namespace Common
-75
View File
@@ -1,75 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <thread>
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include "common/x64/cpu_detect.h"
#include "common/x64/cpu_wait.h"
#include "common/x64/rdtsc.h"
namespace Common::X64 {
namespace {
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
// For reference:
// At 1 GHz, 100K cycles is 100us
// At 2 GHz, 100K cycles is 50us
// At 4 GHz, 100K cycles is 25us
constexpr auto PauseCycles = 100'000U;
} // Anonymous namespace
#if defined(_MSC_VER) && !defined(__clang__)
__forceinline static void TPAUSE() {
static constexpr auto RequestC02State = 0U;
_tpause(RequestC02State, FencedRDTSC() + PauseCycles);
}
__forceinline static void MWAITX() {
static constexpr auto EnableWaitTimeFlag = 1U << 1;
static constexpr auto RequestC1State = 0U;
// monitor_var should be aligned to a cache line.
alignas(64) u64 monitor_var{};
_mm_monitorx(&monitor_var, 0, 0);
_mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles);
}
#else
static void TPAUSE() {
static constexpr auto RequestC02State = 0U;
const auto tsc = FencedRDTSC() + PauseCycles;
const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF);
const auto edx = static_cast<u32>(tsc >> 32);
asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax));
}
static void MWAITX() {
static constexpr auto EnableWaitTimeFlag = 1U << 1;
static constexpr auto RequestC1State = 0U;
// monitor_var should be aligned to a cache line.
alignas(64) u64 monitor_var{};
asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0));
asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag));
}
#endif
void MicroSleep() {
static const bool has_waitpkg = GetCPUCaps().waitpkg;
static const bool has_monitorx = GetCPUCaps().monitorx;
if (has_waitpkg) {
TPAUSE();
} else if (has_monitorx) {
MWAITX();
} else {
std::this_thread::yield();
}
}
} // namespace Common::X64
-10
View File
@@ -1,10 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
namespace Common::X64 {
void MicroSleep();
} // namespace Common::X64
+1 -1
View File
@@ -1246,7 +1246,7 @@ if (HAS_NCE)
target_link_libraries(core PRIVATE merry::oaknut)
endif()
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64)
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITECTURE_loongarch64)
target_sources(core PRIVATE
arm/dynarmic/arm_dynarmic.h
arm/dynarmic/arm_dynarmic_64.cpp
+3 -1
View File
@@ -1,9 +1,11 @@
// 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 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <dynarmic/interface/halt_reason.h>
#include "core/arm/arm_interface.h"
+3 -1
View File
@@ -1,9 +1,11 @@
// 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 Skyline Team and Contributors
// SPDX-License-Identifier: MPL-2.0
#pragma once
#include "common/bit_field.h"
#include "common/common_types.h"
+1 -1
View File
@@ -3,7 +3,7 @@
#include <numeric>
#include <bit>
#include "common/wall_clock.h"
#include "common/cpu_features.h"
#include "common/alignment.h"
#include "common/literals.h"
#include "core/arm/nce/arm_nce.h"
+10 -28
View File
@@ -8,15 +8,13 @@
#include <mutex>
#include <string>
#include <tuple>
#include "common/cpu_features.h"
#include "common/cpu_features.h"
#ifdef _WIN32
#include "common/windows/timer_resolution.h"
#endif
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_wait.h"
#endif
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hardware_properties.h"
@@ -47,8 +45,7 @@ struct CoreTiming::Event {
}
};
CoreTiming::CoreTiming() : clock{Common::CreateOptimalClock()} {}
CoreTiming::CoreTiming() = default;
CoreTiming::~CoreTiming() {
Reset();
}
@@ -64,31 +61,16 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
on_thread_init();
has_started = true;
// base frequency in MHz: 1ns (10^-9) = 1GHz (10^9)
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();
auto const 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
@@ -226,7 +208,7 @@ void CoreTiming::ResetTicks() {
}
u64 CoreTiming::GetClockTicks() const {
u64 fres = is_multicore ? clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
u64 fres = is_multicore ? Common::g_wall_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)));
}
@@ -240,7 +222,7 @@ u64 CoreTiming::GetClockTicks() const {
u64 CoreTiming::GetGPUTicks() const {
return is_multicore
? clock.GetGPUTick()
? Common::g_wall_clock.GetGPUTick()
: Common::WallClock::CPUTickToGPUTick(cpu_ticks);
}
@@ -317,14 +299,14 @@ void CoreTiming::Reset() {
/// @brief Returns current time in nanoseconds.
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const noexcept {
return is_multicore
? clock.GetTimeNS()
? Common::g_wall_clock.GetTimeNS()
: std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)};
}
/// @brief Returns current time in microseconds.
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const noexcept {
return is_multicore
? clock.GetTimeUS()
? Common::g_wall_clock.GetTimeUS()
: std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
}
+9 -18
View File
@@ -19,7 +19,7 @@
#include "common/common_types.h"
#include "common/thread.h"
#include "common/wall_clock.h"
#include "common/cpu_features.h"
namespace Core::Timing {
@@ -142,37 +142,28 @@ public:
void Reset();
Common::WallClock clock;
using heap_t = boost::heap::fibonacci_heap<CoreTiming::Event, boost::heap::compare<std::greater<>>>;
heap_t event_queue;
s64 global_timer = 0;
#ifdef _WIN32
s64 timer_resolution_ns;
#endif
using heap_t =
boost::heap::fibonacci_heap<CoreTiming::Event, boost::heap::compare<std::greater<>>>;
heap_t event_queue;
u64 event_fifo_id = 0;
s64 pause_end_time{};
/// Cycle timing
u64 cpu_ticks{};
s64 downcount{};
Common::Event event{};
Common::Event pause_event{};
std::function<void()> on_thread_init{};
std::jthread timer_thread;
mutable std::mutex basic_lock;
std::mutex advance_lock;
std::jthread timer_thread;
std::atomic<bool> paused{};
std::atomic<bool> paused_set{};
std::atomic<bool> wait_set{};
std::atomic<bool> has_started{};
std::function<void()> on_thread_init{};
bool is_multicore{};
s64 pause_end_time{};
/// Cycle timing
u64 cpu_ticks{};
s64 downcount{};
};
/// Creates a core timing event with the given name and callback.
+1 -1
View File
@@ -33,7 +33,7 @@ namespace FS = Common::FS;
namespace {
constexpr size_t MaxOpenFiles = 512;
constexpr size_t MaxOpenFiles = 8192;
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
switch (mode) {
+5
View File
@@ -1,6 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/typed_address.h"
namespace Kernel {
@@ -10,6 +10,7 @@
#include "common/assert.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/cpu_features.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
@@ -28,7 +29,6 @@ BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext&
Service::Nvidia::NvCore::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");
@@ -488,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 = Common::g_wall_clock.GetTimeNS().count();
slots[slot].fence = fence;
item.slot = slot;
@@ -14,7 +14,7 @@
#include <mutex>
#include "common/common_funcs.h"
#include "common/wall_clock.h"
#include "common/cpu_features.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvnflinger/binder.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
@@ -89,7 +89,6 @@ private:
s32 next_callback_ticket{};
s32 current_callback_ticket{};
std::condition_variable_any callback_condition;
Common::WallClock clock;
Service::Nvidia::NvCore::NvMap& nvmap;
};
@@ -1,6 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ankerl/unordered_dense.h>
#include "common/uuid.h"
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
@@ -1,6 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/service.h"
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "common/uuid.h"
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
+2 -2
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 2023 yuzu Emulator Project
@@ -13,7 +13,7 @@
#include "common/common_types.h"
#include "common/intrusive_list.h"
#include "common/uuid.h"
#include "common/wall_clock.h"
#include "common/cpu_features.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/psc/time/errors.h"
+5
View File
@@ -1,6 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/result.h"
namespace Service::RO {
+2
View File
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
@@ -1,6 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <set>
@@ -1,9 +1,11 @@
// 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 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/math_util.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/nvnflinger/ui/fence.h"
+4
View File
@@ -130,6 +130,10 @@ if ("riscv64" IN_LIST ARCHITECTURE)
find_package(biscuit 0.9.1 REQUIRED)
endif()
if ("loongarch64" IN_LIST ARCHITECTURE)
find_package(lagoon REQUIRED)
endif()
if ("x86_64" IN_LIST ARCHITECTURE)
find_package(xbyak 7 CONFIG)
endif()
+14
View File
@@ -295,6 +295,20 @@ if ("riscv64" IN_LIST ARCHITECTURE)
message(WARNING "TODO: Incomplete frontend for this host architecture")
endif()
if ("loongarch64" IN_LIST ARCHITECTURE)
target_link_libraries(dynarmic PRIVATE lagoon::lagoon)
target_sources(dynarmic PRIVATE
backend/loongarch64/exclusive_monitor.cpp
backend/loongarch64/a32_interface.cpp
backend/loongarch64/a64_interface.cpp
backend/loongarch64/code_block.h
common/spin_lock_loongarch64.cpp
)
message(WARNING "TODO: Incomplete frontend for this host architecture")
endif()
if (WIN32)
target_sources(dynarmic PRIVATE backend/exception_handler_windows.cpp)
elseif (APPLE)
@@ -6,6 +6,7 @@
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <cstddef>
namespace oaknut {
@@ -26,6 +26,10 @@ class CodeBlock;
namespace Dynarmic::Backend::RV64 {
class CodeBlock;
} // namespace Dynarmic::Backend::RV64
#elif defined(ARCHITECTURE_loongarch64)
namespace Dynarmic::Backend::LoongArch64 {
class CodeBlock;
} // namespace Dynarmic::Backend::LoongArch64
#else
# error "Invalid architecture"
#endif
@@ -45,6 +49,10 @@ struct FakeCall {
struct FakeCall {
u64 call_sepc;
};
#elif defined(ARCHITECTURE_loongarch64)
struct FakeCall {
u64 call_pc;
};
#else
# error "Invalid architecture"
#endif
@@ -60,6 +68,8 @@ public:
void Register(oaknut::CodeBlock& mem, std::size_t mem_size);
#elif defined(ARCHITECTURE_riscv64)
void Register(RV64::CodeBlock& mem, std::size_t mem_size);
#elif defined(ARCHITECTURE_loongarch64)
void Register(LoongArch64::CodeBlock& mem, std::size_t mem_size);
#else
# error "Invalid architecture"
#endif
@@ -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
/* This file is part of the dynarmic project.
@@ -28,6 +28,10 @@ void ExceptionHandler::Register(oaknut::CodeBlock&, std::size_t) {
void ExceptionHandler::Register(RV64::CodeBlock&, std::size_t) {
// Do nothing
}
#elif defined(ARCHITECTURE_loongarch64)
void ExceptionHandler::Register(LoongArch64::CodeBlock&, std::size_t) {
// Do nothing
}
#else
# error "Invalid architecture"
#endif
@@ -6,20 +6,25 @@
* SPDX-License-Identifier: 0BSD
*/
#include <signal.h>
#include <algorithm>
#include <bit>
#include <cstring>
#include <functional>
#include <algorithm>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <optional>
#include <bit>
#include <fmt/format.h>
#include <shared_mutex>
#include <ankerl/unordered_dense.h>
#include "dynarmic/backend/exception_handler.h"
#include <fmt/format.h>
#include <sys/mman.h>
#include "common/assert.h"
#include "dynarmic/common/context.h"
#include "common/common_types.h"
#include "dynarmic/backend/exception_handler.h"
#include "dynarmic/common/context.h"
#if defined(ARCHITECTURE_x86_64)
# include "dynarmic/backend/x64/block_of_code.h"
#elif defined(ARCHITECTURE_arm64)
@@ -27,6 +32,8 @@
# include "dynarmic/backend/arm64/abi.h"
#elif defined(ARCHITECTURE_riscv64)
# include "dynarmic/backend/riscv64/code_block.h"
#elif defined(ARCHITECTURE_loongarch64)
# include "dynarmic/backend/loongarch64/code_block.h"
#else
# error "Invalid architecture"
#endif
@@ -150,6 +157,16 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) {
}
}
fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_SEPC);
#elif defined(ARCHITECTURE_loongarch64)
{
std::shared_lock guard(sig_handler->code_block_infos_mutex);
if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) {
FakeCall fc = iter->second.cb(CTX_PC);
CTX_PC = fc.call_pc;
return;
}
}
fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
#else
# error "Invalid architecture"
#endif
@@ -209,6 +226,10 @@ void ExceptionHandler::Register(oaknut::CodeBlock& mem, std::size_t size) {
void ExceptionHandler::Register(RV64::CodeBlock& mem, std::size_t size) {
impl = std::make_unique<Impl>(std::bit_cast<u64>(mem.ptr<u64>()), size);
}
#elif defined(ARCHITECTURE_loongarch64)
void ExceptionHandler::Register(LoongArch64::CodeBlock& mem, std::size_t size) {
impl = std::make_unique<Impl>(std::bit_cast<u64>(mem.ptr<u64>()), size);
}
#else
# error "Invalid architecture"
#endif
@@ -0,0 +1,170 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <memory>
#include <string>
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
#include "dynarmic/interface/A32/a32.h"
namespace Dynarmic::A32 {
struct Jit::Impl final {
explicit Impl(UserConfig conf_) : conf(std::move(conf_)) {}
HaltReason Run() {
UNIMPLEMENTED();
return halt_reason;
}
HaltReason Step() {
UNIMPLEMENTED();
return halt_reason | HaltReason::Step;
}
void ClearCache() {
HaltExecution(HaltReason::CacheInvalidation);
}
void InvalidateCacheRange(u32, std::size_t) {
HaltExecution(HaltReason::CacheInvalidation);
}
void Reset() {
regs = {};
ext_regs = {};
cpsr = 0;
fpscr = 0;
halt_reason = {};
}
void HaltExecution(HaltReason hr) {
halt_reason |= hr;
}
void ClearHalt(HaltReason hr) {
halt_reason &= ~hr;
}
std::array<u32, 16>& Regs() {
return regs;
}
const std::array<u32, 16>& Regs() const {
return regs;
}
std::array<u32, 64>& ExtRegs() {
return ext_regs;
}
const std::array<u32, 64>& ExtRegs() const {
return ext_regs;
}
u32 Cpsr() const {
return cpsr;
}
void SetCpsr(u32 value) {
cpsr = value;
}
u32 Fpscr() const {
return fpscr;
}
void SetFpscr(u32 value) {
fpscr = value;
}
void ClearExclusiveState() {}
std::string Disassemble() const {
return {};
}
UserConfig conf;
std::array<u32, 16> regs{};
std::array<u32, 64> ext_regs{};
u32 cpsr = 0;
u32 fpscr = 0;
HaltReason halt_reason{};
};
Jit::Jit(UserConfig conf) : impl(std::make_unique<Impl>(std::move(conf))) {}
Jit::~Jit() = default;
HaltReason Jit::Run() {
return impl->Run();
}
HaltReason Jit::Step() {
return impl->Step();
}
void Jit::ClearCache() {
impl->ClearCache();
}
void Jit::InvalidateCacheRange(u32 start_address, std::size_t length) {
impl->InvalidateCacheRange(start_address, length);
}
void Jit::Reset() {
impl->Reset();
}
void Jit::HaltExecution(HaltReason hr) {
impl->HaltExecution(hr);
}
void Jit::ClearHalt(HaltReason hr) {
impl->ClearHalt(hr);
}
std::array<u32, 16>& Jit::Regs() {
return impl->Regs();
}
const std::array<u32, 16>& Jit::Regs() const {
return impl->Regs();
}
std::array<u32, 64>& Jit::ExtRegs() {
return impl->ExtRegs();
}
const std::array<u32, 64>& Jit::ExtRegs() const {
return impl->ExtRegs();
}
u32 Jit::Cpsr() const {
return impl->Cpsr();
}
void Jit::SetCpsr(u32 value) {
impl->SetCpsr(value);
}
u32 Jit::Fpscr() const {
return impl->Fpscr();
}
void Jit::SetFpscr(u32 value) {
impl->SetFpscr(value);
}
void Jit::ClearExclusiveState() {
impl->ClearExclusiveState();
}
std::string Jit::Disassemble() const {
return impl->Disassemble();
}
} // namespace Dynarmic::A32
@@ -0,0 +1,268 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <memory>
#include <string>
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
#include "dynarmic/interface/A64/a64.h"
namespace Dynarmic::A64 {
struct Jit::Impl final {
explicit Impl(UserConfig conf_) : conf(std::move(conf_)) {}
HaltReason Run() {
UNIMPLEMENTED();
return halt_reason;
}
HaltReason Step() {
UNIMPLEMENTED();
return halt_reason | HaltReason::Step;
}
void ClearCache() {
HaltExecution(HaltReason::CacheInvalidation);
}
void InvalidateCacheRange(u64, std::size_t) {
HaltExecution(HaltReason::CacheInvalidation);
}
void Reset() {
regs = {};
vectors = {};
sp = 0;
pc = 0;
fpcr = 0;
fpsr = 0;
pstate = 0;
halt_reason = {};
}
void HaltExecution(HaltReason hr) {
halt_reason |= hr;
}
void ClearHalt(HaltReason hr) {
halt_reason &= ~hr;
}
u64 GetSP() const {
return sp;
}
void SetSP(u64 value) {
sp = value;
}
u64 GetPC() const {
return pc;
}
void SetPC(u64 value) {
pc = value;
}
u64 GetRegister(std::size_t index) const {
return index == 31 ? sp : regs.at(index);
}
void SetRegister(std::size_t index, u64 value) {
if (index == 31) {
sp = value;
return;
}
regs.at(index) = value;
}
std::array<u64, 31> GetRegisters() const {
return regs;
}
void SetRegisters(const std::array<u64, 31>& value) {
regs = value;
}
Vector GetVector(std::size_t index) const {
return vectors.at(index);
}
void SetVector(std::size_t index, Vector value) {
vectors.at(index) = value;
}
std::array<Vector, 32> GetVectors() const {
return vectors;
}
void SetVectors(const std::array<Vector, 32>& value) {
vectors = value;
}
u32 GetFpcr() const {
return fpcr;
}
void SetFpcr(u32 value) {
fpcr = value;
}
u32 GetFpsr() const {
return fpsr;
}
void SetFpsr(u32 value) {
fpsr = value;
}
u32 GetPstate() const {
return pstate;
}
void SetPstate(u32 value) {
pstate = value;
}
void ClearExclusiveState() {}
bool IsExecuting() const {
return false;
}
std::string Disassemble() const {
return {};
}
UserConfig conf;
std::array<u64, 31> regs{};
std::array<Vector, 32> vectors{};
u64 sp = 0;
u64 pc = 0;
u32 fpcr = 0;
u32 fpsr = 0;
u32 pstate = 0;
HaltReason halt_reason{};
};
Jit::Jit(UserConfig conf) : impl(std::make_unique<Impl>(std::move(conf))) {}
Jit::~Jit() = default;
HaltReason Jit::Run() {
return impl->Run();
}
HaltReason Jit::Step() {
return impl->Step();
}
void Jit::ClearCache() {
impl->ClearCache();
}
void Jit::InvalidateCacheRange(u64 start_address, std::size_t length) {
impl->InvalidateCacheRange(start_address, length);
}
void Jit::Reset() {
impl->Reset();
}
void Jit::HaltExecution(HaltReason hr) {
impl->HaltExecution(hr);
}
void Jit::ClearHalt(HaltReason hr) {
impl->ClearHalt(hr);
}
u64 Jit::GetSP() const {
return impl->GetSP();
}
void Jit::SetSP(u64 value) {
impl->SetSP(value);
}
u64 Jit::GetPC() const {
return impl->GetPC();
}
void Jit::SetPC(u64 value) {
impl->SetPC(value);
}
u64 Jit::GetRegister(std::size_t index) const {
return impl->GetRegister(index);
}
void Jit::SetRegister(std::size_t index, u64 value) {
impl->SetRegister(index, value);
}
std::array<u64, 31> Jit::GetRegisters() const {
return impl->GetRegisters();
}
void Jit::SetRegisters(const std::array<u64, 31>& value) {
impl->SetRegisters(value);
}
Vector Jit::GetVector(std::size_t index) const {
return impl->GetVector(index);
}
void Jit::SetVector(std::size_t index, Vector value) {
impl->SetVector(index, value);
}
std::array<Vector, 32> Jit::GetVectors() const {
return impl->GetVectors();
}
void Jit::SetVectors(const std::array<Vector, 32>& value) {
impl->SetVectors(value);
}
u32 Jit::GetFpcr() const {
return impl->GetFpcr();
}
void Jit::SetFpcr(u32 value) {
impl->SetFpcr(value);
}
u32 Jit::GetFpsr() const {
return impl->GetFpsr();
}
void Jit::SetFpsr(u32 value) {
impl->SetFpsr(value);
}
u32 Jit::GetPstate() const {
return impl->GetPstate();
}
void Jit::SetPstate(u32 value) {
impl->SetPstate(value);
}
void Jit::ClearExclusiveState() {
impl->ClearExclusiveState();
}
bool Jit::IsExecuting() const {
return impl->IsExecuting();
}
std::string Jit::Disassemble() const {
return impl->Disassemble();
}
} // namespace Dynarmic::A64
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstdint>
#include "common/common_types.h"
namespace Dynarmic::Backend::LoongArch64 {
class CodeBlock {
public:
template<typename T>
T ptr() const noexcept {
return reinterpret_cast<T>(mem);
}
private:
u8* mem = nullptr;
};
} // namespace Dynarmic::Backend::LoongArch64
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "dynarmic/interface/exclusive_monitor.h"
#include <algorithm>
namespace Dynarmic {
ExclusiveMonitor::ExclusiveMonitor(std::size_t processor_count)
: exclusive_addresses(processor_count, INVALID_EXCLUSIVE_ADDRESS), exclusive_values(processor_count) {}
size_t ExclusiveMonitor::GetProcessorCount() const {
return exclusive_addresses.size();
}
void ExclusiveMonitor::Lock() {
lock.Lock();
}
void ExclusiveMonitor::Unlock() {
lock.Unlock();
}
bool ExclusiveMonitor::CheckAndClear(std::size_t processor_id, VAddr address) {
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
Lock();
if (exclusive_addresses[processor_id] != masked_address) {
Unlock();
return false;
}
for (VAddr& other_address : exclusive_addresses) {
if (other_address == masked_address) {
other_address = INVALID_EXCLUSIVE_ADDRESS;
}
}
return true;
}
void ExclusiveMonitor::Clear() {
Lock();
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
Unlock();
}
void ExclusiveMonitor::ClearProcessor(std::size_t processor_id) {
Lock();
exclusive_addresses[processor_id] = INVALID_EXCLUSIVE_ADDRESS;
Unlock();
}
} // namespace Dynarmic
@@ -348,11 +348,7 @@ void A32EmitX64::EmitA32SetRegister(A32EmitContext& ctx, IR::Inst* inst) {
code.mov(MJitStateReg(reg), args[1].GetImmediateU32());
} else if (args[1].IsInXmm(ctx.reg_alloc)) {
const Xbyak::Xmm to_store = ctx.reg_alloc.UseXmm(code, args[1]);
if (code.HasHostFeature(HostFeature::AVX)) {
code.vmovd(MJitStateReg(reg), to_store);
} else {
code.movd(MJitStateReg(reg), to_store);
}
code.movd(MJitStateReg(reg), to_store);
} else {
const Xbyak::Reg32 to_store = ctx.reg_alloc.UseGpr(code, args[1]).cvt32();
code.mov(MJitStateReg(reg), to_store);
@@ -645,11 +641,7 @@ void A32EmitX64::EmitA32OrQFlag(A32EmitContext& ctx, IR::Inst* inst) {
void A32EmitX64::EmitA32GetGEFlags(A32EmitContext& ctx, IR::Inst* inst) {
const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code);
if (code.HasHostFeature(HostFeature::AVX)) {
code.vmovd(result, dword[code.ABI_JIT_PTR + offsetof(A32JitState, cpsr_ge)]);
} else {
code.movd(result, dword[code.ABI_JIT_PTR + offsetof(A32JitState, cpsr_ge)]);
}
code.movd(result, dword[code.ABI_JIT_PTR + offsetof(A32JitState, cpsr_ge)]);
ctx.reg_alloc.DefineValue(code, inst, result);
}
@@ -659,11 +651,7 @@ void A32EmitX64::EmitA32SetGEFlags(A32EmitContext& ctx, IR::Inst* inst) {
if (args[0].IsInXmm(ctx.reg_alloc)) {
const Xbyak::Xmm to_store = ctx.reg_alloc.UseXmm(code, args[0]);
if (code.HasHostFeature(HostFeature::AVX)) {
code.vmovd(dword[code.ABI_JIT_PTR + offsetof(A32JitState, cpsr_ge)], to_store);
} else {
code.movd(dword[code.ABI_JIT_PTR + offsetof(A32JitState, cpsr_ge)], to_store);
}
code.movd(dword[code.ABI_JIT_PTR + offsetof(A32JitState, cpsr_ge)], to_store);
} else {
const Xbyak::Reg32 to_store = ctx.reg_alloc.UseGpr(code, args[0]).cvt32();
code.mov(dword[code.ABI_JIT_PTR + offsetof(A32JitState, cpsr_ge)], to_store);
@@ -339,11 +339,7 @@ void A64EmitX64::EmitA64GetS(A64EmitContext& ctx, IR::Inst* inst) {
const auto addr = qword[code.ABI_JIT_PTR + offsetof(A64JitState, vec) + sizeof(u64) * 2 * static_cast<size_t>(vec)];
const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code);
if (code.HasHostFeature(HostFeature::AVX)) {
code.vmovd(result, addr);
} else {
code.movd(result, addr);
}
code.movd(result, addr);
ctx.reg_alloc.DefineValue(code, inst, result);
}
@@ -352,11 +348,7 @@ void A64EmitX64::EmitA64GetD(A64EmitContext& ctx, IR::Inst* inst) {
const auto addr = qword[code.ABI_JIT_PTR + offsetof(A64JitState, vec) + sizeof(u64) * 2 * static_cast<size_t>(vec)];
const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code);
if (code.HasHostFeature(HostFeature::AVX)) {
code.vmovq(result, addr);
} else {
code.movq(result, addr);
}
code.movq(result, addr);
ctx.reg_alloc.DefineValue(code, inst, result);
}
@@ -226,22 +226,13 @@ void EmitPostProcessNaNs(BlockOfCode& code, Xbyak::Xmm result, Xbyak::Xmm op1, X
// op1 == Inf && op2 == QNaN
// op1 == QNaN && op2 == SNaN <<< The problematic case
// op1 == QNaN && op2 == Inf
if (code.HasHostFeature(HostFeature::AVX)) {
if constexpr (fsize == 32) {
code.movd(tmp.cvt32(), op2);
code.shl(tmp.cvt32(), 32 - mantissa_msb_bit);
} else {
code.movq(tmp, op2);
code.shl(tmp, 64 - mantissa_msb_bit);
}
if constexpr (fsize == 32) {
code.movd(tmp.cvt32(), op2);
code.shl(tmp.cvt32(), 32 - mantissa_msb_bit);
} else {
if constexpr (fsize == 32) {
code.vmovd(tmp.cvt32(), op2);
code.shl(tmp.cvt32(), 32 - mantissa_msb_bit);
} else {
code.vmovq(tmp, op2);
code.shl(tmp, 64 - mantissa_msb_bit);
}
code.movq(tmp, op2);
code.shl(tmp, 64 - mantissa_msb_bit);
}
// If op2 is a SNaN, CF = 0 and ZF = 0.
code.jna(end, code.T_NEAR);
@@ -486,18 +477,10 @@ static inline void EmitFPMinMaxNumeric(BlockOfCode& code, EmitContext& ctx, IR::
tmp.setBit(fsize);
const auto move_to_tmp = [=, &code](const Xbyak::Xmm& xmm) {
if (code.HasHostFeature(HostFeature::AVX)) {
if constexpr (fsize == 32) {
code.vmovd(tmp.cvt32(), xmm);
} else {
code.vmovq(tmp.cvt64(), xmm);
}
if constexpr (fsize == 32) {
code.movd(tmp.cvt32(), xmm);
} else {
if constexpr (fsize == 32) {
code.movd(tmp.cvt32(), xmm);
} else {
code.movq(tmp.cvt64(), xmm);
}
code.movq(tmp.cvt64(), xmm);
}
};
@@ -1173,11 +1156,7 @@ static void EmitFPRSqrtEstimate(BlockOfCode& code, EmitContext& ctx, IR::Inst* i
code.L(*bad_values);
if constexpr (fsize == 32) {
if (code.HasHostFeature(HostFeature::AVX)) {
code.vmovd(tmp, operand);
} else {
code.movd(tmp, operand);
}
code.movd(tmp, operand);
if (!ctx.FPCR().FZ()) {
if (ctx.FPCR().DN()) {
@@ -1207,12 +1186,7 @@ static void EmitFPRSqrtEstimate(BlockOfCode& code, EmitContext& ctx, IR::Inst* i
}
code.L(default_nan);
if (code.HasHostFeature(HostFeature::AVX)) {
code.vmovd(result, code.Const(xword, 0x7FC00000));
} else {
code.movd(result, code.Const(xword, 0x7FC00000));
}
code.movd(result, code.Const(xword, 0x7FC00000));
code.jmp(*end, code.T_NEAR);
} else {
Xbyak::Label nan, zero;
@@ -227,11 +227,7 @@ void EmitX64::EmitVectorGetElement32(EmitContext& ctx, IR::Inst* inst) {
} else {
auto const source = ctx.reg_alloc.UseScratchXmm(code, args[0]);
code.pshufd(source, source, index);
if (code.HasHostFeature(HostFeature::AVX)) {
code.vmovd(dest, source);
} else {
code.movd(dest, source);
}
code.movd(dest, source);
}
ctx.reg_alloc.DefineValue(code, inst, dest);
@@ -11,7 +11,6 @@
#include <tuple>
#include <type_traits>
#include <utility>
#include <smmintrin.h>
#include "common/assert.h"
#include "dynarmic/mcl/function_info.hpp"
@@ -1653,23 +1652,25 @@ static void EmitFPVectorRoundIntThunk(VectorArray<FPT>& output, const VectorArra
template<size_t fsize>
void EmitFPVectorRoundInt(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
//auto args = ctx.reg_alloc.GetArgumentInfo(inst);
const auto rounding = FP::RoundingMode(inst->GetArg(1).GetU8());
const bool exact = inst->GetArg(2).GetU1();
if constexpr (fsize != 16) {
if (code.HasHostFeature(HostFeature::SSE41) && rounding != FP::RoundingMode::ToNearest_TieAwayFromZero && !exact) {
const u8 round_imm = [rounding]() -> u8 {
const u8 round_imm = [&]() -> u8 {
switch (rounding) {
case FP::RoundingMode::ToNearest_TieEven: return _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC;
case FP::RoundingMode::TowardsPlusInfinity: return _MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC;
case FP::RoundingMode::TowardsMinusInfinity: return _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC;
case FP::RoundingMode::TowardsZero: return _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC;
case FP::RoundingMode::ToNearest_TieEven: return 0b00;
case FP::RoundingMode::TowardsPlusInfinity: return 0b10;
case FP::RoundingMode::TowardsMinusInfinity: return 0b01;
case FP::RoundingMode::TowardsZero: return 0b11;
default: UNREACHABLE();
}
}();
EmitTwoOpVectorOperation<fsize, DefaultIndexer, 3>(code, ctx, inst, [&code, round_imm](const Xbyak::Xmm result, const Xbyak::Xmm xmm_a) {
EmitTwoOpVectorOperation<fsize, DefaultIndexer, 3>(code, ctx, inst, [&](const Xbyak::Xmm& result, const Xbyak::Xmm& xmm_a) {
FCODE(roundp)(result, xmm_a, round_imm);
});
return;
}
}
@@ -1677,21 +1678,33 @@ void EmitFPVectorRoundInt(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
// Do not make a LUT out of this, let the compiler do it's thing
using FPT = mcl::unsigned_integer_of_size<fsize>;
switch (rounding) {
#define ROUND_LIST \
ROUND_ELEM(ToNearest_TieEven) \
ROUND_ELEM(TowardsPlusInfinity) \
ROUND_ELEM(TowardsMinusInfinity) \
ROUND_ELEM(TowardsZero) \
ROUND_ELEM(ToNearest_TieAwayFromZero)
#define ROUND_ELEM(name) \
case FP::RoundingMode::name: \
return exact \
? EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::name, true>) \
: EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::name, false>);
ROUND_LIST
#undef ROUND_ELEM
#undef ROUND_LIST
default: UNREACHABLE();
case FP::RoundingMode::ToNearest_TieEven:
exact
? EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::ToNearest_TieEven, true>)
: EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::ToNearest_TieEven, false>);
break;
case FP::RoundingMode::TowardsPlusInfinity:
exact
? EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::TowardsPlusInfinity, true>)
: EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::TowardsPlusInfinity, false>);
break;
case FP::RoundingMode::TowardsMinusInfinity:
exact
? EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::TowardsMinusInfinity, true>)
: EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::TowardsMinusInfinity, false>);
break;
case FP::RoundingMode::TowardsZero:
exact
? EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::TowardsZero, true>)
: EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::TowardsZero, false>);
break;
case FP::RoundingMode::ToNearest_TieAwayFromZero:
exact
? EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::ToNearest_TieAwayFromZero, true>)
: EmitTwoOpFallback<3>(code, ctx, inst, EmitFPVectorRoundIntThunk<FPT, FP::RoundingMode::ToNearest_TieAwayFromZero, false>);
break;
default:
UNREACHABLE();
}
}
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "dynarmic/common/spin_lock.h"
namespace Dynarmic {
void SpinLock::Lock() noexcept {
}
void SpinLock::Unlock() noexcept {
}
} // namespace Dynarmic
@@ -9,6 +9,7 @@
#pragma once
#include <functional>
#include <tuple>
#include "common/assert.h"
@@ -31,6 +32,10 @@ public:
, expected{expected}
{}
constexpr Matcher(std::tuple<T, T> t) noexcept
: Matcher(std::get<0>(t), std::get<1>(t))
{}
/// @brief Gets the mask for this instruction.
constexpr inline T GetMask() const noexcept {
return mask;
@@ -486,27 +486,17 @@ void TouchResource::ReadTouchInput() {
SanitizeInput(current_touch_state);
std::scoped_lock lock{*input_mutex};
if (current_touch_state.entry_count == previous_touch_state.entry_count) {
if (current_touch_state.entry_count < 1) {
return;
}
if (current_touch_state.entry_count == previous_touch_state.entry_count && current_touch_state.entry_count >= 1) {
bool has_moved = false;
for (std::size_t i = 0; i < static_cast<std::size_t>(current_touch_state.entry_count);
i++) {
s32 delta_x = std::abs(static_cast<s32>(current_touch_state.states[i].position.x) -
static_cast<s32>(previous_touch_state.states[i].position.x));
s32 delta_y = std::abs(static_cast<s32>(current_touch_state.states[i].position.y) -
static_cast<s32>(previous_touch_state.states[i].position.y));
if (delta_x > 1 || delta_y > 1) {
has_moved = true;
}
for (std::size_t i = 0; !has_moved && i < std::size_t(current_touch_state.entry_count); i++) {
s32 delta_x = std::abs(s32(current_touch_state.states[i].position.x) - s32(previous_touch_state.states[i].position.x));
s32 delta_y = std::abs(s32(current_touch_state.states[i].position.y) - s32(previous_touch_state.states[i].position.y));
has_moved |= (delta_x > 1 || delta_y > 1);
}
if (!has_moved) {
return;
if (has_moved) {
input_event->Signal();
}
}
input_event->Signal();
}
void TouchResource::OnTouchUpdate(s64 timestamp) {
+32 -6
View File
@@ -13,12 +13,9 @@
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "common/scm_rev.h"
#include "common/cpu_features.h"
#include "core/memory.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
#endif
#include <QGuiApplication>
#include <QStringLiteral>
#include "core/frontend/emu_window.h"
@@ -90,8 +87,37 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
// Our Win32 Qt external doesn't have the private API.
wsi.render_surface = reinterpret_cast<void*>(window->winId());
#elif defined(__APPLE__)
wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)(
id layer = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
// In Qt 6, the layer of the NSView might be a QContainerLayer.
// MoltenVK needs a CAMetalLayer. We search for it in sublayers.
Class metal_layer_class = objc_getClass("CAMetalLayer");
id metal_layer = nullptr;
if (layer) {
if (reinterpret_cast<bool (*)(id, SEL, Class)>(objc_msgSend)(
layer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
metal_layer = layer;
} else {
id sublayers = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
layer, sel_registerName("sublayers"));
if (sublayers) {
unsigned long count = reinterpret_cast<unsigned long (*)(id, SEL)>(objc_msgSend)(
sublayers, sel_registerName("count"));
for (unsigned long i = 0; i < count; ++i) {
id sublayer = reinterpret_cast<id (*)(id, SEL, unsigned long)>(objc_msgSend)(
sublayers, sel_registerName("objectAtIndex:"), i);
if (reinterpret_cast<bool (*)(id, SEL, Class)>(objc_msgSend)(
sublayer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
metal_layer = sublayer;
break;
}
}
}
}
}
wsi.render_surface = reinterpret_cast<void*>(metal_layer ? metal_layer : layer);
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
@@ -214,7 +240,7 @@ void Init(QWidget* root) {
LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version);
LogRuntimes();
#ifdef ARCHITECTURE_x86_64
const auto& caps = Common::GetCPUCaps();
const auto& caps = Common::g_cpu_caps;
std::string cpu_string = caps.cpu_string;
if (caps.avx || caps.avx2 || caps.avx512f) {
cpu_string += " | AVX";
@@ -462,6 +462,13 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
ctx.AddCapability(spv::Capability::ImageGatherExtended);
ctx.AddCapability(spv::Capability::ImageQuery);
ctx.AddCapability(spv::Capability::SampledBuffer);
// TODO: this usage needs to be tracked properly
if (ctx.profile.support_sampled_image_array_nonuniform_indexing) {
if (ctx.profile.supported_spirv < 0x00010400)
ctx.AddExtension("SPV_EXT_descriptor_indexing");
ctx.AddCapability(spv::Capability::ShaderNonUniform);
ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing);
}
}
void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
@@ -14,37 +14,10 @@
namespace Shader::Backend::SPIRV {
namespace {
class DescriptorIndex {
public:
explicit DescriptorIndex(EmitContext& ctx, const IR::Value& index)
: id{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)},
is_non_uniform{ctx.profile.support_sampled_image_array_nonuniform_indexing &&
!index.IsImmediate()} {
if (!is_non_uniform) {
return;
}
if (ctx.profile.supported_spirv < 0x00010400) {
ctx.AddExtension("SPV_EXT_descriptor_indexing");
}
ctx.AddCapability(spv::Capability::ShaderNonUniform);
ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing);
Decorate(ctx, id);
}
Id Value() const {
return id;
}
void Decorate(EmitContext& ctx, Id object) const {
if (is_non_uniform) {
ctx.Decorate(object, spv::Decoration::NonUniform);
}
}
private:
Id id;
bool is_non_uniform;
};
[[nodiscard]] bool IsNonUniformDescriptor(EmitContext& ctx, const IR::Value& index) noexcept {
return ctx.profile.support_sampled_image_array_nonuniform_indexing && !index.IsImmediate();
}
class ImageOperands {
public:
@@ -221,11 +194,13 @@ 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);
auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index);
if (!ctx.non_uniform_ids.contains(idx.value) && IsNonUniformDescriptor(ctx, index)) {
ctx.Decorate(idx, spv::Decoration::NonUniform);
ctx.non_uniform_ids.insert(idx.value);
}
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx)};
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,13 +219,14 @@ 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);
auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index);
if (!ctx.non_uniform_ids.contains(idx.value) && IsNonUniformDescriptor(ctx, index)) {
ctx.Decorate(idx, spv::Decoration::NonUniform);
ctx.non_uniform_ids.insert(idx.value);
}
const Id ptr = ctx.OpAccessChain(def.pointer_type, def.id, idx);
const Id object = ctx.OpLoad(def.sampled_type, ptr);
const Id image = ctx.OpImage(def.image_type, object);
return image;
}
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id));
@@ -9,6 +9,7 @@
#include <array>
#include <sirit/sirit.h>
#include <ankerl/unordered_dense.h>
#include "shader_recompiler/backend/bindings.h"
#include "shader_recompiler/frontend/ir/program.h"
@@ -366,6 +367,9 @@ public:
Id load_const_func_u32x2{};
Id load_const_func_u32x4{};
// Sirit::Id doesn't play nice with *::set<>
ankerl::unordered_dense::set<u32> non_uniform_ids;
private:
void DefineCommonTypes(const Info& info);
void DefineCommonConstants();
@@ -733,9 +733,8 @@ 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;
u32 count = cbuf.count;
switch (inst->GetOpcode()) {
case IR::Opcode::ImageRead:
case IR::Opcode::ImageAtomicIAdd32:
+29 -16
View File
@@ -754,7 +754,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
}
}
if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
const u32 new_offset = offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
const u32 new_offset = offset + draw_state.index_buffer.first * u32(draw_state.index_buffer.FormatSizeInBytes());
runtime.BindIndexBuffer(buffer, new_offset, size);
} else {
buffer.MarkUsage(offset, size);
@@ -1252,8 +1252,7 @@ void BufferCache<P>::UpdateIndexBuffer() {
const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress();
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
const u32 draw_size =
(index_buffer_ref.count + index_buffer_ref.first) * index_buffer_ref.FormatSizeInBytes();
const u32 draw_size = (index_buffer_ref.count + index_buffer_ref.first) * u32(index_buffer_ref.FormatSizeInBytes());
const u32 size = (std::min)(address_size, draw_size);
if (size == 0 || !device_addr) {
channel_state->index_buffer = NULL_BINDING;
@@ -1620,24 +1619,38 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
template <class P>
bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 size) {
upload_copies.clear();
u64 total_size_bytes = 0;
u64 staging_offset = 0;
u64 largest_copy = 0;
const DAddr buffer_start = buffer.cpu_addr_cached;
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
upload_copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
.dst_offset = device_addr_out - buffer_start,
.size = range_size,
DAddr buffer_start = buffer.CpuAddr();
auto push = [&](u64 start, u64 end) {
if (start >= end) {
return;
}
u64 sz = end - start;
upload_copies.push_back({
.src_offset = staging_offset,
.dst_offset = start - buffer_start,
.size = sz
});
total_size_bytes += range_size;
largest_copy = (std::max)(largest_copy, range_size);
staging_offset += sz;
largest_copy = (std::max)(largest_copy, sz);
};
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 addr, u64 range_size) {
u64 start = addr;
u64 end = addr + range_size;
gpu_modified_ranges.ForEachInRange(start, range_size, [&](u64 gstart, u64 gsize) {
u64 gend = gstart + gsize;
push(start, gstart);
start = (std::max)(start, gend);
});
push(start, end);
ClearDownload(addr, range_size);
gpu_modified_ranges.Subtract(addr, range_size);
});
if (total_size_bytes == 0) {
if (upload_copies.empty()) {
return true;
}
const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size());
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
any_buffer_uploaded = true;
UploadMemory(buffer, staging_offset, largest_copy, std::span(upload_copies));
return false;
}
+3 -3
View File
@@ -181,12 +181,12 @@ void DmaPusher::CallMethod(u32 argument) const {
});
} else {
auto subchannel = subchannels[dma_state.subchannel];
if (!subchannel->execution_mask[dma_state.method]) {
subchannel->method_sink.emplace_back(dma_state.method, argument);
} else {
if (subchannel->execution_mask[dma_state.method]) {
subchannel->ConsumeSink();
subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset;
subchannel->CallMethod(dma_state.method, argument, dma_state.is_last_call);
} else {
subchannel->method_sink.emplace_back(dma_state.method, argument);
}
}
}
+10 -23
View File
@@ -270,31 +270,20 @@ u32 Maxwell3D::GetMaxCurrentVertices() {
size_t Maxwell3D::EstimateIndexBufferSize() {
GPUVAddr start_address = regs.index_buffer.StartAddress();
GPUVAddr end_address = regs.index_buffer.EndAddress();
static constexpr std::array<size_t, 3> max_sizes = {(std::numeric_limits<u8>::max)(),
(std::numeric_limits<u16>::max)(),
(std::numeric_limits<u32>::max)()};
const size_t byte_size = regs.index_buffer.FormatSizeInBytes();
const size_t log2_byte_size = Common::Log2Ceil64(byte_size);
const size_t cap{GetMaxCurrentVertices() * 4 * byte_size};
const size_t lower_cap =
std::min<size_t>(static_cast<size_t>(end_address - start_address), cap);
return std::min<size_t>(
memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) /
byte_size,
lower_cap);
auto const byte_size = regs.index_buffer.FormatSizeInBytes();
auto const max_size = 1ull << (byte_size * CHAR_BIT);
auto const upper_cap = GetMaxCurrentVertices() * 4 * byte_size;
auto const lower_cap = std::min<size_t>(size_t(end_address - start_address), upper_cap);
return std::min<size_t>(memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_size) / byte_size, lower_cap);
}
u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
// Keep track of the register value in shadow_state when requested.
const auto control = shadow_state.shadow_ram_control;
if (control == Regs::ShadowRamControl::Track ||
control == Regs::ShadowRamControl::TrackWithFilter) {
shadow_state.reg_array[method] = argument;
return argument;
}
if (control == Regs::ShadowRamControl::Replay) {
auto const c = shadow_state.shadow_ram_control;
if (c == Regs::ShadowRamControl::Track || c == Regs::ShadowRamControl::TrackWithFilter)
return shadow_state.reg_array[method] = argument;
else if (c == Regs::ShadowRamControl::Replay)
return shadow_state.reg_array[method];
}
return argument;
}
@@ -317,10 +306,8 @@ void Maxwell3D::ConsumeSinkImpl() {
void Maxwell3D::ProcessDirtyRegisters(u32 method, u32 argument) {
regs.reg_array[method] = argument;
for (const auto& table : dirty.tables) {
for (auto const& table : dirty.tables)
dirty.flags[table[method]] = true;
}
}
void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, bool is_last_call) {
+4 -4
View File
@@ -2215,7 +2215,7 @@ public:
u32 first;
u32 count;
unsigned FormatSizeInBytes() const {
size_t FormatSizeInBytes() const {
switch (format) {
case IndexFormat::UnsignedByte:
return 1;
@@ -2224,7 +2224,7 @@ public:
case IndexFormat::UnsignedInt:
return 4;
}
ASSERT(false);
UNREACHABLE();
return 1;
}
@@ -3148,9 +3148,9 @@ public:
}
struct DirtyState {
using Flags = std::bitset<(std::numeric_limits<u8>::max)()>;
using Flags = std::bitset<(std::numeric_limits<u8>::max)() + 1>;
using Table = std::array<u8, Regs::NUM_REGS>;
using Tables = std::array<Table, 2>;
using Tables = std::array<std::array<u8, Regs::NUM_REGS>, 2>;
Flags flags;
Tables tables{};
+5 -2
View File
@@ -76,9 +76,12 @@ public:
TryReleasePendingFences<false>();
}
const bool should_flush = ShouldFlush();
const bool delay_fence = Settings::values.antiflicker.GetValue() || !Settings::IsGPULevelLow();
const bool antiflicker_toggled = Settings::values.antiflicker.GetValue();
const bool delay_fence = Settings::IsGPULevelHigh() ||
(Settings::IsGPULevelMedium() && should_flush) ||
antiflicker_toggled;
CommitAsyncFlushes();
TFence new_fence = CreateFence(!should_flush && !delay_fence);
TFence new_fence = CreateFence(!should_flush && !antiflicker_toggled);
if constexpr (can_async_check) {
guard.lock();
}
+3 -4
View File
@@ -39,7 +39,7 @@ extern "C" {
#include "video_core/textures/decoders.h"
#if defined(ARCHITECTURE_x86_64)
#include "common/x64/cpu_detect.h"
#include "common/cpu_features.h"
#endif
#if defined(ARCHITECTURE_x86_64) \
@@ -55,9 +55,8 @@ namespace Tegra::Host1x {
namespace {
static bool HasSSE41() {
#if defined(ARCHITECTURE_x86_64)
static bool has_sse41 = Common::GetCPUCaps().sse4_1;
return has_sse41;
#ifdef ARCHITECTURE_x86_64
return Common::g_cpu_caps.sse4_1;
#else
return false;
#endif
@@ -90,7 +90,7 @@ void ComputePipeline::Configure() {
desc.is_written);
++ssbo_index;
}
texture_cache.SynchronizeComputeDescriptors();
texture_cache.SynchronizeDescriptors(true);
boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
boost::container::static_vector<VideoCommon::SamplerId, MAX_TEXTURES> samplers;
@@ -148,14 +148,14 @@ void ComputePipeline::Configure() {
const auto handle{read_handle(desc, index)};
views.push_back({handle.first});
VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second);
VideoCommon::SamplerId sampler = texture_cache.GetSamplerId(handle.second, true);
samplers.push_back(sampler);
}
}
for (const auto& desc : info.image_descriptors) {
add_image(desc, desc.is_written);
}
texture_cache.FillComputeImageViews(std::span(views.data(), views.size()));
texture_cache.FillImageViews(std::span(views.data(), views.size()), true);
if (!is_built) {
WaitForBuild();
@@ -283,7 +283,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
size_t views_index{};
size_t samplers_index{};
texture_cache.SynchronizeGraphicsDescriptors();
texture_cache.SynchronizeDescriptors(false);
buffer_cache.SetUniformBuffersState(enabled_uniform_buffer_masks, &uniform_buffer_sizes);
buffer_cache.runtime.SetBaseUniformBindings(base_uniform_bindings);
@@ -354,7 +354,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
const auto handle{read_handle(desc, index)};
views[views_index++] = {handle.first};
VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)};
VideoCommon::SamplerId sampler{texture_cache.GetSamplerId(handle.second, false)};
samplers[samplers_index++] = sampler;
}
}
@@ -379,7 +379,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
if constexpr (Spec::enabled_stages[4]) {
config_stage(4);
}
texture_cache.FillGraphicsImageViews<Spec::has_images>(std::span(views.data(), views_index));
texture_cache.FillImageViews(std::span(views.data(), views_index), false, Spec::has_images);
texture_cache.UpdateRenderTargets(false);
state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle());
@@ -353,13 +353,13 @@ void RasterizerOpenGL::DrawTexture() {
gpu.TickWork();
};
texture_cache.SynchronizeGraphicsDescriptors();
texture_cache.SynchronizeDescriptors(false);
texture_cache.UpdateRenderTargets(false);
SyncState();
const auto& draw_texture_state = maxwell3d->draw_manager.draw_texture_state;
const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler);
const auto& sampler = texture_cache.GetSampler(draw_texture_state.src_sampler, true);
const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture);
const auto Scale = [&](auto dim) -> s32 {
@@ -125,7 +125,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
++ssbo_index;
}
texture_cache.SynchronizeComputeDescriptors();
texture_cache.SynchronizeDescriptors(true);
boost::container::small_vector<VideoCommon::ImageViewInOut, 64> views;
boost::container::small_vector<VideoCommon::SamplerId, 64> samplers;
@@ -173,14 +173,14 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
const auto handle{read_handle(desc, index)};
views.push_back({handle.first});
VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second);
VideoCommon::SamplerId sampler = texture_cache.GetSamplerId(handle.second, true);
samplers.push_back(sampler);
}
}
for (const auto& desc : info.image_descriptors) {
add_image(desc, desc.is_written);
}
texture_cache.FillComputeImageViews(std::span(views.data(), views.size()));
texture_cache.FillImageViews(std::span(views.data(), views.size()), true);
buffer_cache.UnbindComputeTextureBuffers();
size_t index{};
@@ -49,7 +49,6 @@ using VideoCore::Surface::PixelFormatFromDepthFormat;
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
constexpr size_t NUM_STAGES = Maxwell::MaxShaderStage;
constexpr size_t INLINE_IMAGE_ELEMENTS = 64;
DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span<const Shader::Info> infos) {
DescriptorLayoutBuilder builder{device};
@@ -314,12 +313,12 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) {
template <typename Spec>
bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
small_vector<VideoCommon::ImageViewInOut, INLINE_IMAGE_ELEMENTS> views;
small_vector<VideoCommon::SamplerId, INLINE_IMAGE_ELEMENTS> samplers;
boost::container::small_vector<VideoCommon::ImageViewInOut, 64> views;
boost::container::small_vector<VideoCommon::SamplerId, 64> samplers;
views.reserve(num_image_elements);
samplers.reserve(num_textures);
texture_cache.SynchronizeGraphicsDescriptors();
texture_cache.SynchronizeDescriptors(false);
buffer_cache.SetUniformBuffersState(enabled_uniform_buffer_masks, &uniform_buffer_sizes);
@@ -384,7 +383,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
const auto handle{read_handle(desc, index)};
views.push_back({handle.first});
VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)};
VideoCommon::SamplerId sampler{texture_cache.GetSamplerId(handle.second, false)};
samplers.push_back(sampler);
}
}
@@ -413,7 +412,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
}
ASSERT(views.size() == num_image_elements);
ASSERT(samplers.size() == num_textures);
texture_cache.FillGraphicsImageViews<Spec::has_images>(std::span(views.data(), views.size()));
texture_cache.FillImageViews(std::span(views.data(), views.size()), false, Spec::has_images);
VideoCommon::ImageViewInOut* texture_buffer_it{views.data()};
const auto bind_stage_info{[&](size_t stage) LAMBDA_FORCEINLINE {
@@ -351,7 +351,7 @@ void RasterizerVulkan::DrawTexture() {
FlushWork();
std::scoped_lock l{texture_cache.mutex};
texture_cache.SynchronizeGraphicsDescriptors();
texture_cache.SynchronizeDescriptors(false);
texture_cache.UpdateRenderTargets(false);
UpdateDynamicStates();
@@ -359,7 +359,7 @@ void RasterizerVulkan::DrawTexture() {
query_cache.NotifySegment(true);
query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable);
const auto& draw_texture_state = maxwell3d->draw_manager.draw_texture_state;
const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler);
const auto& sampler = texture_cache.GetSampler(draw_texture_state.src_sampler, false);
const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture);
const auto* framebuffer = texture_cache.GetFramebuffer();
@@ -267,8 +267,8 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT
| VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, WRITE_BARRIER);
upload_cmdbuf.End();
cmdbuf.End();
@@ -24,11 +24,8 @@ using namespace Dirty;
using namespace VideoCommon::Dirty;
using Tegra::Engines::Maxwell3D;
using Regs = Maxwell3D::Regs;
using Tables = Maxwell3D::DirtyState::Tables;
using Table = Maxwell3D::DirtyState::Table;
using Flags = Maxwell3D::DirtyState::Flags;
Flags MakeInvalidationFlags() {
Maxwell3D::DirtyState::Flags MakeInvalidationFlags() {
static constexpr int INVALIDATION_FLAGS[]{
Viewports,
Scissors,
@@ -68,7 +65,7 @@ Flags MakeInvalidationFlags() {
LineStippleEnable,
LineStippleParams,
};
Flags flags{};
Maxwell3D::DirtyState::Flags flags{};
for (const int flag : INVALIDATION_FLAGS) {
flags[flag] = true;
}
@@ -84,7 +81,7 @@ Flags MakeInvalidationFlags() {
return flags;
}
void SetupDirtyViewports(Tables& tables) {
void SetupDirtyViewports(Maxwell3D::DirtyState::Tables& tables) {
FillBlock(tables[0], OFF(viewport_transform), NUM(viewport_transform), Viewports);
FillBlock(tables[0], OFF(viewports), NUM(viewports), Viewports);
FillBlock(tables[1], OFF(surface_clip), NUM(surface_clip), Viewports);
@@ -92,26 +89,26 @@ void SetupDirtyViewports(Tables& tables) {
tables[1][OFF(window_origin)] = Viewports;
}
void SetupDirtyScissors(Tables& tables) {
void SetupDirtyScissors(Maxwell3D::DirtyState::Tables& tables) {
FillBlock(tables[0], OFF(scissor_test), NUM(scissor_test), Scissors);
}
void SetupDirtyDepthBias(Tables& tables) {
void SetupDirtyDepthBias(Maxwell3D::DirtyState::Tables& tables) {
auto& table = tables[0];
table[OFF(depth_bias)] = DepthBias;
table[OFF(depth_bias_clamp)] = DepthBias;
table[OFF(slope_scale_depth_bias)] = DepthBias;
}
void SetupDirtyBlendConstants(Tables& tables) {
void SetupDirtyBlendConstants(Maxwell3D::DirtyState::Tables& tables) {
FillBlock(tables[0], OFF(blend_color), NUM(blend_color), BlendConstants);
}
void SetupDirtyDepthBounds(Tables& tables) {
void SetupDirtyDepthBounds(Maxwell3D::DirtyState::Tables& tables) {
FillBlock(tables[0], OFF(depth_bounds), NUM(depth_bounds), DepthBounds);
}
void SetupDirtyStencilProperties(Tables& tables) {
void SetupDirtyStencilProperties(Maxwell3D::DirtyState::Tables& tables) {
const auto setup = [&](size_t position, u8 flag) {
tables[0][position] = flag;
tables[1][position] = StencilProperties;
@@ -125,18 +122,18 @@ void SetupDirtyStencilProperties(Tables& tables) {
setup(OFF(stencil_back_func_mask), StencilCompare);
}
void SetupDirtyLineWidth(Tables& tables) {
void SetupDirtyLineWidth(Maxwell3D::DirtyState::Tables& tables) {
tables[0][OFF(line_width_smooth)] = LineWidth;
tables[0][OFF(line_width_aliased)] = LineWidth;
}
void SetupDirtyCullMode(Tables& tables) {
void SetupDirtyCullMode(Maxwell3D::DirtyState::Tables& tables) {
auto& table = tables[0];
table[OFF(gl_cull_face)] = CullMode;
table[OFF(gl_cull_test_enabled)] = CullMode;
}
void SetupDirtyStateEnable(Tables& tables) {
void SetupDirtyStateEnable(Maxwell3D::DirtyState::Tables& tables) {
const auto setup = [&](size_t position, u8 flag) {
tables[0][position] = flag;
tables[1][position] = StateEnable;
@@ -157,17 +154,17 @@ void SetupDirtyStateEnable(Tables& tables) {
setup(OFF(anti_alias_alpha_control.alpha_to_one), AlphaToOneEnable);
}
void SetupDirtyDepthCompareOp(Tables& tables) {
void SetupDirtyDepthCompareOp(Maxwell3D::DirtyState::Tables& tables) {
tables[0][OFF(depth_test_func)] = DepthCompareOp;
}
void SetupDirtyFrontFace(Tables& tables) {
void SetupDirtyFrontFace(Maxwell3D::DirtyState::Tables& tables) {
auto& table = tables[0];
table[OFF(gl_front_face)] = FrontFace;
table[OFF(window_origin)] = FrontFace;
}
void SetupDirtyStencilOp(Tables& tables) {
void SetupDirtyStencilOp(Maxwell3D::DirtyState::Tables& tables) {
auto& table = tables[0];
table[OFF(stencil_front_op.fail)] = StencilOp;
table[OFF(stencil_front_op.zfail)] = StencilOp;
@@ -182,7 +179,7 @@ void SetupDirtyStencilOp(Tables& tables) {
tables[1][OFF(stencil_two_side_enable)] = StencilOp;
}
void SetupDirtyBlending(Tables& tables) {
void SetupDirtyBlending(Maxwell3D::DirtyState::Tables& tables) {
tables[0][OFF(color_mask_common)] = Blending;
tables[1][OFF(color_mask_common)] = ColorMask;
tables[0][OFF(blend_per_target_enabled)] = Blending;
@@ -196,11 +193,11 @@ void SetupDirtyBlending(Tables& tables) {
FillBlock(tables[1], OFF(blend_per_target), NUM(blend_per_target), BlendEquations);
}
void SetupDirtySpecialOps(Tables& tables) {
void SetupDirtySpecialOps(Maxwell3D::DirtyState::Tables& tables) {
tables[0][OFF(logic_op.op)] = LogicOp;
}
void SetupDirtyViewportSwizzles(Tables& tables) {
void SetupDirtyViewportSwizzles(Maxwell3D::DirtyState::Tables& tables) {
static constexpr size_t swizzle_offset = 6;
for (size_t index = 0; index < Regs::NumViewports; ++index) {
tables[1][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
@@ -208,7 +205,7 @@ void SetupDirtyViewportSwizzles(Tables& tables) {
}
}
void SetupDirtyVertexAttributes(Tables& tables) {
void SetupDirtyVertexAttributes(Maxwell3D::DirtyState::Tables& tables) {
for (size_t i = 0; i < Regs::NumVertexAttributes; ++i) {
const size_t offset = OFF(vertex_attrib_format) + i * NUM(vertex_attrib_format[0]);
FillBlock(tables[0], offset, NUM(vertex_attrib_format[0]), VertexAttribute0 + i);
@@ -216,7 +213,7 @@ void SetupDirtyVertexAttributes(Tables& tables) {
FillBlock(tables[1], OFF(vertex_attrib_format), Regs::NumVertexAttributes, VertexInput);
}
void SetupDirtyVertexBindings(Tables& tables) {
void SetupDirtyVertexBindings(Maxwell3D::DirtyState::Tables& tables) {
// Do NOT include stride here, it's implicit in VertexBuffer
static constexpr size_t divisor_offset = 3;
for (size_t i = 0; i < Regs::NumVertexArrays; ++i) {
@@ -228,7 +225,7 @@ void SetupDirtyVertexBindings(Tables& tables) {
}
}
void SetupRasterModes(Tables &tables) {
void SetupRasterModes(Maxwell3D::DirtyState::Tables &tables) {
auto& table = tables[0];
table[OFF(line_stipple_params)] = LineStippleParams;
+2 -1
View File
@@ -254,11 +254,12 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
static constexpr u64 SELF_BRANCH_A = 0xE2400FFFFF87000FULL;
static constexpr u64 SELF_BRANCH_B = 0xE2400FFFFF07000FULL;
code.resize(MAXIMUM_SIZE / INST_SIZE);
GPUVAddr guest_addr{program_base + start_address};
size_t offset{0};
size_t size{BLOCK_SIZE};
while (size <= MAXIMUM_SIZE) {
code.resize(size / INST_SIZE);
u64* const data = code.data() + offset / INST_SIZE;
gpu_memory->ReadBlock(guest_addr, data, BLOCK_SIZE);
for (size_t index = 0; index < BLOCK_SIZE; index += INST_SIZE) {
+29 -34
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
@@ -6,37 +9,39 @@
#include <algorithm>
#include <vector>
#include "common/alignment.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "common/assert.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
namespace VideoCommon {
template <typename Descriptor>
template <typename T>
class DescriptorTable {
public:
explicit DescriptorTable(Tegra::MemoryManager& gpu_memory_) : gpu_memory{gpu_memory_} {}
[[nodiscard]] bool Synchronize(GPUVAddr gpu_addr, u32 limit) {
[[likely]] if (current_gpu_addr == gpu_addr && current_limit == limit) { return false; }
Refresh(gpu_addr, limit);
return true;
[[nodiscard]] bool Synchronize(GPUVAddr gpu_addr, u32 limit) noexcept {
bool ret = !(current_gpu_addr == gpu_addr && current_limit == limit);
if (ret) {
Refresh(gpu_addr, limit);
}
return ret;
}
void Invalidate() noexcept {
std::ranges::fill(read_descriptors, 0);
}
[[nodiscard]] std::pair<Descriptor, bool> Read(u32 index) {
[[nodiscard]] std::pair<T, bool> Read(Tegra::MemoryManager const& gpu_memory, u32 index) noexcept {
DEBUG_ASSERT(index <= current_limit);
const GPUVAddr gpu_addr = current_gpu_addr + index * sizeof(Descriptor);
std::pair<Descriptor, bool> result;
gpu_memory.ReadBlockUnsafe(gpu_addr, &result.first, sizeof(Descriptor));
if (IsDescriptorRead(index)) {
const GPUVAddr gpu_addr = current_gpu_addr + index * sizeof(T);
std::pair<T, bool> result;
gpu_memory.ReadBlockUnsafe(gpu_addr, std::addressof(result.first), sizeof(T));
if ((read_descriptors[index / 64] & (1ULL << (index % 64))) != 0) {
result.second = result.first != descriptors[index];
} else {
MarkDescriptorAsRead(index);
read_descriptors[index / 64] |= 1ULL << (index % 64);
result.second = true;
}
if (result.second) {
@@ -45,34 +50,24 @@ public:
return result;
}
[[nodiscard]] u32 Limit() const noexcept {
return current_limit;
}
private:
void Refresh(GPUVAddr gpu_addr, u32 limit) {
void Refresh(GPUVAddr gpu_addr, u32 limit) noexcept {
current_gpu_addr = gpu_addr;
current_limit = limit;
const size_t num_descriptors = static_cast<size_t>(limit) + 1;
read_descriptors.clear();
read_descriptors.resize(Common::DivCeil(num_descriptors, 64U), 0);
// Mario Brothership reallocates a lot of times, so use aggressive pre-alloc sizes
// std::vector<T> by default uses quadratic growth, but that isn't even enough to satisfy brothership
const size_t num_descriptors = ((limit + 0x80000) & (~0x7ffff)) + 1;
size_t old_size = read_descriptors.size();
read_descriptors.resize(Common::DivCeil(num_descriptors, 64U));
old_size = (std::min)(old_size, read_descriptors.size());
std::fill(read_descriptors.begin(), read_descriptors.begin() + old_size, 0);
//
descriptors.resize(num_descriptors);
}
void MarkDescriptorAsRead(u32 index) noexcept {
read_descriptors[index / 64] |= 1ULL << (index % 64);
}
[[nodiscard]] bool IsDescriptorRead(u32 index) const noexcept {
return (read_descriptors[index / 64] & (1ULL << (index % 64))) != 0;
}
Tegra::MemoryManager& gpu_memory;
std::vector<u64> read_descriptors;
std::vector<T> descriptors;
GPUVAddr current_gpu_addr{};
u32 current_limit{};
std::vector<u64> read_descriptors;
std::vector<Descriptor> descriptors;
};
} // namespace VideoCommon
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -7,8 +10,8 @@
namespace VideoCommon {
TextureCacheChannelInfo::TextureCacheChannelInfo(Tegra::Control::ChannelState& state) noexcept
: ChannelInfo(state), graphics_image_table{gpu_memory}, graphics_sampler_table{gpu_memory},
compute_image_table{gpu_memory}, compute_sampler_table{gpu_memory} {}
: ChannelInfo(state)
{}
template class VideoCommon::ChannelSetupCaches<VideoCommon::TextureCacheChannelInfo>;
+123 -194
View File
@@ -14,6 +14,7 @@
#include "common/alignment.h"
#include "common/settings.h"
#include "common/slot_vector.h"
#include "video_core/control/channel_state.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/kepler_compute.h"
@@ -204,8 +205,8 @@ typename P::ImageView& TextureCache<P>::GetImageView(ImageViewId id) noexcept {
template <class P>
typename P::ImageView& TextureCache<P>::GetImageView(u32 index) noexcept {
const auto image_view_id = VisitImageView(channel_state->graphics_image_table,
channel_state->graphics_image_view_ids, index);
// Not compute!
const auto image_view_id = VisitImageView(index, false);
return slot_image_views[image_view_id];
}
@@ -215,16 +216,25 @@ void TextureCache<P>::MarkModification(ImageId id) noexcept {
}
template <class P>
template <bool has_blacklists>
void TextureCache<P>::FillGraphicsImageViews(std::span<ImageViewInOut> views) {
FillImageViews<has_blacklists>(channel_state->graphics_image_table,
channel_state->graphics_image_view_ids, views);
}
template <class P>
void TextureCache<P>::FillComputeImageViews(std::span<ImageViewInOut> views) {
FillImageViews<true>(channel_state->compute_image_table, channel_state->compute_image_view_ids,
views);
void TextureCache<P>::FillImageViews(std::span<ImageViewInOut> views, bool compute, bool blacklist) {
bool has_blacklisted = false;
do {
has_deleted_images = false;
if (blacklist) {
has_blacklisted = false;
}
for (ImageViewInOut& view : views) {
view.id = VisitImageView(view.index, compute);
if (blacklist) {
if (view.blacklist && view.id != NULL_IMAGE_VIEW_ID) {
const ImageViewBase& image_view = slot_image_views[view.id];
auto& image = slot_images[image_view.image_id];
has_blacklisted |= ScaleDown(image);
image.scale_rating = 0;
}
}
}
} while (has_deleted_images || (blacklist && has_blacklisted));
}
template <class P>
@@ -292,41 +302,25 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) {
}
template <class P>
typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) {
return &slot_samplers[GetGraphicsSamplerId(index)];
typename P::Sampler* TextureCache<P>::GetSampler(u32 index, bool compute) {
return &slot_samplers[GetSamplerId(index, compute)];
}
template <class P>
typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) {
return &slot_samplers[GetComputeSamplerId(index)];
}
template <class P>
SamplerId TextureCache<P>::GetGraphicsSamplerId(u32 index) {
if (index > channel_state->graphics_sampler_table.Limit()) {
SamplerId TextureCache<P>::GetSamplerId(u32 index, bool compute) {
auto& table = compute ? channel_state->compute_sampler_table : channel_state->graphics_sampler_table;
if (index > table.current_limit) {
LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index);
return NULL_SAMPLER_ID;
}
const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index);
SamplerId& id = channel_state->graphics_sampler_ids[index];
auto const map_index = index | (compute ? Common::SlotId::TAGGED_VALUE : 0);
auto const [descriptor, is_new] = table.Read(*gpu_memory, index);
if (is_new) {
id = FindSampler(descriptor);
auto const id = FindSampler(descriptor, compute);
channel_state->sampler_ids.insert_or_assign(map_index, id);
return id;
}
return id;
}
template <class P>
SamplerId TextureCache<P>::GetComputeSamplerId(u32 index) {
if (index > channel_state->compute_sampler_table.Limit()) {
LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index);
return NULL_SAMPLER_ID;
}
const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index);
SamplerId& id = channel_state->compute_sampler_ids[index];
if (is_new) {
id = FindSampler(descriptor);
}
return id;
return channel_state->sampler_ids.find(map_index)->second;
}
template <class P>
@@ -340,45 +334,31 @@ typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) noexcept {
}
template <class P>
void TextureCache<P>::SynchronizeGraphicsDescriptors() {
using SamplerBinding = Tegra::Engines::Maxwell3D::Regs::SamplerBinding;
const bool linked_tsc = maxwell3d->regs.sampler_binding == SamplerBinding::ViaHeaderBinding;
const u32 tic_limit = maxwell3d->regs.tex_header.limit;
const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d->regs.tex_sampler.limit;
bool bindings_changed = false;
if (channel_state->graphics_sampler_table.Synchronize(maxwell3d->regs.tex_sampler.Address(),
tsc_limit)) {
channel_state->graphics_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
bindings_changed = true;
}
if (channel_state->graphics_image_table.Synchronize(maxwell3d->regs.tex_header.Address(),
tic_limit)) {
channel_state->graphics_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
bindings_changed = true;
}
if (bindings_changed) {
++texture_bindings_serial;
}
}
template <class P>
void TextureCache<P>::SynchronizeComputeDescriptors() {
const bool linked_tsc = kepler_compute->launch_description.linked_tsc;
const u32 tic_limit = kepler_compute->regs.tic.limit;
const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute->regs.tsc.limit;
const GPUVAddr tsc_gpu_addr = kepler_compute->regs.tsc.Address();
bool bindings_changed = false;
if (channel_state->compute_sampler_table.Synchronize(tsc_gpu_addr, tsc_limit)) {
channel_state->compute_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
bindings_changed = true;
}
if (channel_state->compute_image_table.Synchronize(kepler_compute->regs.tic.Address(),
tic_limit)) {
channel_state->compute_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
bindings_changed = true;
}
if (bindings_changed) {
++texture_bindings_serial;
void TextureCache<P>::SynchronizeDescriptors(bool compute) {
if (compute) {
const bool linked_tsc = kepler_compute->launch_description.linked_tsc;
const u32 tic_limit = kepler_compute->regs.tic.limit;
const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute->regs.tsc.limit;
bool bindings_changed = false;
if (channel_state->compute_sampler_table.Synchronize(kepler_compute->regs.tsc.Address(), tsc_limit))
bindings_changed = true;
if (channel_state->compute_image_table.Synchronize(kepler_compute->regs.tic.Address(), tic_limit))
bindings_changed = true;
if (bindings_changed) {
++texture_bindings_serial;
}
} else {
const bool linked_tsc = maxwell3d->regs.sampler_binding == Tegra::Engines::Maxwell3D::Regs::SamplerBinding::ViaHeaderBinding;
const u32 tic_limit = maxwell3d->regs.tex_header.limit;
const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d->regs.tex_sampler.limit;
bool bindings_changed = false;
if (channel_state->graphics_sampler_table.Synchronize(maxwell3d->regs.tex_sampler.Address(), tsc_limit))
bindings_changed = true;
if (channel_state->graphics_image_table.Synchronize(maxwell3d->regs.tex_header.Address(), tic_limit))
bindings_changed = true;
if (bindings_changed) {
++texture_bindings_serial;
}
}
}
@@ -557,47 +537,32 @@ typename P::Framebuffer* TextureCache<P>::GetFramebuffer() {
}
template <class P>
template <bool has_blacklists>
void TextureCache<P>::FillImageViews(DescriptorTable<TICEntry>& table,
std::span<ImageViewId> cached_image_view_ids,
std::span<ImageViewInOut> views) {
bool has_blacklisted = false;
do {
has_deleted_images = false;
if constexpr (has_blacklists) {
has_blacklisted = false;
}
for (ImageViewInOut& view : views) {
view.id = VisitImageView(table, cached_image_view_ids, view.index);
if constexpr (has_blacklists) {
if (view.blacklist && view.id != NULL_IMAGE_VIEW_ID) {
const ImageViewBase& image_view{slot_image_views[view.id]};
auto& image = slot_images[image_view.image_id];
has_blacklisted |= ScaleDown(image);
image.scale_rating = 0;
}
}
}
} while (has_deleted_images || (has_blacklists && has_blacklisted));
}
template <class P>
ImageViewId TextureCache<P>::VisitImageView(DescriptorTable<TICEntry>& table,
std::span<ImageViewId> cached_image_view_ids,
u32 index) {
if (index > table.Limit()) {
ImageViewId TextureCache<P>::VisitImageView(u32 index, bool compute) {
auto& table = compute ? channel_state->compute_image_table : channel_state->graphics_image_table;
if (index > table.current_limit) {
LOG_DEBUG(HW_GPU, "Invalid image view index={}", index);
return NULL_IMAGE_VIEW_ID;
}
const auto [descriptor, is_new] = table.Read(index);
ImageViewId& image_view_id = cached_image_view_ids[index];
auto const map_index = index | (compute ? Common::SlotId::TAGGED_VALUE : 0);
// Is new (on the tegra engine side)?
auto const [descriptor, is_new] = table.Read(*gpu_memory, index);
if (is_new) {
image_view_id = FindImageView(descriptor);
if (IsValidEntry(*gpu_memory, descriptor)) {
// Is new (registered view) on the texture cache side?
const auto [pair, is_new_tc] = channel_state->image_views.try_emplace(descriptor);
if (is_new_tc)
pair->second = CreateImageView(descriptor);
PrepareImageView(pair->second, false, false);
channel_state->image_view_ids.insert_or_assign(map_index, pair->second);
return pair->second;
}
channel_state->image_view_ids.insert_or_assign(map_index, NULL_IMAGE_VIEW_ID);
return NULL_IMAGE_VIEW_ID;
}
if (image_view_id != NULL_IMAGE_VIEW_ID) {
PrepareImageView(image_view_id, false, false);
}
return image_view_id;
auto const it = channel_state->image_view_ids.find(map_index);
if (it->second != NULL_IMAGE_VIEW_ID)
PrepareImageView(it->second, false, false);
return it->second;
}
template <class P>
@@ -1196,19 +1161,6 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
}
}
template <class P>
ImageViewId TextureCache<P>::FindImageView(const TICEntry& config) {
if (!IsValidEntry(*gpu_memory, config)) {
return NULL_IMAGE_VIEW_ID;
}
const auto [pair, is_new] = channel_state->image_views.try_emplace(config);
ImageViewId& image_view_id = pair->second;
if (is_new) {
image_view_id = CreateImageView(config);
}
return image_view_id;
}
template <class P>
ImageViewId TextureCache<P>::CreateImageView(const TICEntry& config) {
const ImageInfo info(config);
@@ -1350,10 +1302,10 @@ void TextureCache<P>::InvalidateScale(Image& image) {
image.image_view_infos.clear();
for (size_t c : active_channel_ids) {
auto& channel_info = channel_storage[c];
if constexpr (ENABLE_VALIDATION) {
std::ranges::fill(channel_info.graphics_image_view_ids, CORRUPT_ID);
std::ranges::fill(channel_info.compute_image_view_ids, CORRUPT_ID);
}
if constexpr (ENABLE_VALIDATION)
for (auto& e : channel_info.image_view_ids)
e.second = CORRUPT_ID;
channel_info.graphics_image_table.Invalidate();
channel_info.compute_image_table.Invalidate();
}
@@ -1918,7 +1870,7 @@ std::pair<u32, u32> TextureCache<P>::PrepareDmaImage(ImageId dst_id, GPUVAddr ba
}
template <class P>
SamplerId TextureCache<P>::FindSampler(const TSCEntry& config) {
SamplerId TextureCache<P>::FindSampler(const TSCEntry& config, bool compute) {
if (std::ranges::all_of(config.raw, [](u64 value) { return value == 0; })) {
return NULL_SAMPLER_ID;
}
@@ -1941,69 +1893,48 @@ std::optional<size_t> TextureCache<P>::QuerySamplerBudget() const {
template <class P>
void TextureCache<P>::EnforceSamplerBudget() {
const auto budget = QuerySamplerBudget();
if (!budget) {
return;
if (auto const budget = QuerySamplerBudget(); budget) {
if (slot_samplers.size() < *budget) {
return;
}
if (!channel_state) {
return;
}
if (last_sampler_gc_frame == frame_tick) {
return;
}
last_sampler_gc_frame = frame_tick;
TrimInactiveSamplers(*budget);
}
if (slot_samplers.size() < *budget) {
return;
}
if (!channel_state) {
return;
}
if (last_sampler_gc_frame == frame_tick) {
return;
}
last_sampler_gc_frame = frame_tick;
TrimInactiveSamplers(*budget);
}
template <class P>
void TextureCache<P>::TrimInactiveSamplers(size_t budget) {
if (channel_state->samplers.empty()) {
return;
}
constexpr size_t SAMPLER_GC_SLACK = 1024;
auto mark_active = [](auto& set, SamplerId id) {
if (!id || id == CORRUPT_ID || id == NULL_SAMPLER_ID) {
return;
if (channel_state->samplers.size() > 0) {
constexpr size_t SAMPLER_GC_SLACK = 1024;
ankerl::unordered_dense::set<SamplerId> active_sampler_ids;
for (auto const& e : channel_state->sampler_ids)
active_sampler_ids.insert(e.second);
// Elements in the map must be necesarily valid
size_t removed = 0;
for (auto it = channel_state->samplers.begin(); it != channel_state->samplers.end();) {
const SamplerId sampler_id = it->second;
if (!sampler_id || sampler_id == CORRUPT_ID) {
it = channel_state->samplers.erase(it);
} else if (std::ranges::find(active_sampler_ids, sampler_id) != active_sampler_ids.end()) {
++it;
} else {
slot_samplers.erase(sampler_id);
it = channel_state->samplers.erase(it);
++removed;
if (slot_samplers.size() + SAMPLER_GC_SLACK <= budget) {
break;
}
}
}
set.insert(id);
};
ankerl::unordered_dense::set<SamplerId> active;
active.reserve(channel_state->graphics_sampler_ids.size() +
channel_state->compute_sampler_ids.size());
for (const SamplerId id : channel_state->graphics_sampler_ids) {
mark_active(active, id);
}
for (const SamplerId id : channel_state->compute_sampler_ids) {
mark_active(active, id);
}
size_t removed = 0;
auto& sampler_map = channel_state->samplers;
for (auto it = sampler_map.begin(); it != sampler_map.end();) {
const SamplerId sampler_id = it->second;
if (!sampler_id || sampler_id == CORRUPT_ID) {
it = sampler_map.erase(it);
continue;
if (removed != 0) {
LOG_WARNING(HW_GPU, "Sampler cache exceeded {} entries on this driver; reclaimed {} inactive samplers", budget, removed);
}
if (active.find(sampler_id) != active.end()) {
++it;
continue;
}
slot_samplers.erase(sampler_id);
it = sampler_map.erase(it);
++removed;
if (slot_samplers.size() + SAMPLER_GC_SLACK <= budget) {
break;
}
}
if (removed != 0) {
LOG_WARNING(HW_GPU,
"Sampler cache exceeded {} entries on this driver; reclaimed {} inactive samplers",
budget, removed);
}
}
@@ -2243,8 +2174,7 @@ ImageViewId TextureCache<P>::FindOrEmplaceImageView(ImageId image_id, const Imag
if (const ImageViewId image_view_id = image.FindView(info); image_view_id) {
return image_view_id;
}
const ImageViewId image_view_id =
slot_image_views.insert(runtime, info, image_id, image, slot_images);
const ImageViewId image_view_id = slot_image_views.insert(runtime, info, image_id, image, slot_images);
image.InsertView(info, image_view_id);
return image_view_id;
}
@@ -2504,10 +2434,9 @@ void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) {
}
for (size_t c : active_channel_ids) {
auto& channel_info = channel_storage[c];
if constexpr (ENABLE_VALIDATION) {
std::ranges::fill(channel_info.graphics_image_view_ids, CORRUPT_ID);
std::ranges::fill(channel_info.compute_image_view_ids, CORRUPT_ID);
}
if constexpr (ENABLE_VALIDATION)
for (auto& e : channel_info.image_view_ids)
e.second = CORRUPT_ID;
channel_info.graphics_image_table.Invalidate();
channel_info.compute_image_table.Invalidate();
}
@@ -17,6 +17,7 @@
#include <ankerl/unordered_dense.h>
#include <vector>
#include <boost/container/small_vector.hpp>
#include <boost/container/static_vector.hpp>
#include <queue>
#include "common/common_types.h"
@@ -76,22 +77,20 @@ public:
TextureCacheChannelInfo(const TextureCacheChannelInfo& state) = delete;
TextureCacheChannelInfo& operator=(const TextureCacheChannelInfo&) = delete;
DescriptorTable<TICEntry> graphics_image_table{gpu_memory};
DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory};
std::vector<SamplerId> graphics_sampler_ids;
std::vector<ImageViewId> graphics_image_view_ids;
DescriptorTable<TICEntry> compute_image_table{gpu_memory};
DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory};
std::vector<SamplerId> compute_sampler_ids;
std::vector<ImageViewId> compute_image_view_ids;
DescriptorTable<TICEntry> graphics_image_table;
DescriptorTable<TSCEntry> graphics_sampler_table;
DescriptorTable<TICEntry> compute_image_table;
DescriptorTable<TSCEntry> compute_sampler_table;
// TODO: still relies on bad iterators :(
std::unordered_map<TICEntry, ImageViewId> image_views;
std::unordered_map<TSCEntry, SamplerId> samplers;
TextureCacheGPUMap* gpu_page_table;
TextureCacheGPUMap* sparse_page_table;
ankerl::unordered_dense::map<u32, SamplerId> sampler_ids;
ankerl::unordered_dense::map<u32, ImageViewId> image_view_ids;
TextureCacheGPUMap* gpu_page_table = nullptr;
TextureCacheGPUMap* sparse_page_table = nullptr;
};
template <class P>
@@ -167,27 +166,17 @@ public:
/// Mark an image as modified from the GPU
void MarkModification(ImageId id) noexcept;
/// Fill image_view_ids with the graphics images in indices
template <bool has_blacklists>
void FillGraphicsImageViews(std::span<ImageViewInOut> views);
/// Fill image_view_ids with the compute images in indices
void FillComputeImageViews(std::span<ImageViewInOut> views);
/// Fill image_view_ids with the graphics/compute images in indices
void FillImageViews(std::span<ImageViewInOut> views, bool compute, bool blacklist = true);
/// Handle feedback loops during draws.
void CheckFeedbackLoop(std::span<const ImageViewInOut> views);
/// Get the sampler from the graphics descriptor table in the specified index
Sampler* GetGraphicsSampler(u32 index);
/// Get the sampler from the graphics/compute descriptor table in the specified index
Sampler* GetSampler(u32 index, bool compute);
/// Get the sampler from the compute descriptor table in the specified index
Sampler* GetComputeSampler(u32 index);
/// Get the sampler id from the graphics descriptor table in the specified index
SamplerId GetGraphicsSamplerId(u32 index);
/// Get the sampler id from the compute descriptor table in the specified index
SamplerId GetComputeSamplerId(u32 index);
/// Get the sampler id from the graphics/compute descriptor table in the specified index
SamplerId GetSamplerId(u32 index, bool compute);
/// Return a constant reference to the given sampler id
[[nodiscard]] const Sampler& GetSampler(SamplerId id) const noexcept;
@@ -195,11 +184,8 @@ public:
/// Return a reference to the given sampler id
[[nodiscard]] Sampler& GetSampler(SamplerId id) noexcept;
/// Refresh the state for graphics image view and sampler descriptors
void SynchronizeGraphicsDescriptors();
/// Refresh the state for compute image view and sampler descriptors
void SynchronizeComputeDescriptors();
/// Refresh the state for graphics/compute image view and sampler descriptors
void SynchronizeDescriptors(bool compute);
/// Updates the Render Targets if they can be rescaled
/// @retval True if the Render Targets have been rescaled.
@@ -310,15 +296,8 @@ private:
/// Runs the Garbage Collector.
void RunGarbageCollector();
/// Fills image_view_ids in the image views in indices
template <bool has_blacklists>
void FillImageViews(DescriptorTable<TICEntry>& table,
std::span<ImageViewId> cached_image_view_ids,
std::span<ImageViewInOut> views);
/// Find or create an image view in the guest descriptor table
ImageViewId VisitImageView(DescriptorTable<TICEntry>& table,
std::span<ImageViewId> cached_image_view_ids, u32 index);
ImageViewId VisitImageView(u32 index, bool compute);
/// Find or create a framebuffer with the given render target parameters
FramebufferId GetFramebufferId(const RenderTargets& key);
@@ -330,9 +309,6 @@ private:
template <typename StagingBuffer>
void UploadImageContents(Image& image, StagingBuffer& staging_buffer);
/// Find or create an image view from a guest descriptor
[[nodiscard]] ImageViewId FindImageView(const TICEntry& config);
/// Create a new image view from a guest descriptor
[[nodiscard]] ImageViewId CreateImageView(const TICEntry& config);
@@ -360,7 +336,7 @@ private:
const Tegra::Engines::Fermi2D::Config& copy);
/// Find or create a sampler from a guest descriptor sampler
[[nodiscard]] SamplerId FindSampler(const TSCEntry& config);
[[nodiscard]] SamplerId FindSampler(const TSCEntry& config, bool compute);
/// Find or create an image view for the given color buffer index
[[nodiscard]] ImageViewId FindColorBuffer(size_t index);
+7 -1
View File
@@ -107,8 +107,14 @@ int main(int argc, char* argv[]) {
QCoreApplication::setApplicationName(QStringLiteral("eden"));
#ifdef _WIN32
// Increases the maximum open file limit to 8192
// Increases the maximum open file limit.
// MSVCRT limits this to 2048 for some inexplicable (and likely arcane) reason,
// so we have to account for that as well.
#ifdef __MSVCRT__
_setmaxstdio(2048);
#else
_setmaxstdio(8192);
#endif
#elif defined(__APPLE__)
// If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
// But since we require the working directory to be the executable path for the location of
+2 -2
View File
@@ -124,7 +124,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "common/string_util.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
#include "common/cpu_features.h"
#endif
// Core //
@@ -3119,7 +3119,7 @@ void MainWindow::OnMenuReportCompatibility() {
tr("Compatibility list reporting is currently disabled. Check back later!"));
// #if defined(ARCHITECTURE_x86_64) && !defined(__APPLE__)
// const auto& caps = Common::GetCPUCaps();
// const auto& caps = g_cpu_caps;
// const bool has_fma = caps.fma;
// const auto processor_count = std::thread::hardware_concurrency();
// const bool has_4threads = processor_count == 0 || processor_count >= 4;