mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-15 13:16:43 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d942c5ae2 | |||
| 116377f6d9 | |||
| 7c32cf03a1 | |||
| ff7bbaea7d | |||
| c84d605426 | |||
| def03f6589 | |||
| 5ea24621cf | |||
| 251a3470dc | |||
| 08f65cbd01 | |||
| 8fd495f906 | |||
| 9d55875377 | |||
| 8a11bec55a | |||
| 4a11d5db2f |
Vendored
+5
@@ -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)
|
||||
|
||||
|
||||
Vendored
+6
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user