mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-29 18:08:08 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a165d08d9 |
Vendored
-5
@@ -82,11 +82,6 @@ if (ARCHITECTURE_riscv64)
|
||||
AddJsonPackage(biscuit)
|
||||
endif()
|
||||
|
||||
# Lagoon
|
||||
if (ARCHITECTURE_loongarch64)
|
||||
AddJsonPackage(lagoon)
|
||||
endif()
|
||||
|
||||
# Vulkan stuff
|
||||
AddDependentPackages(vulkan-headers vulkan-utility-libraries)
|
||||
|
||||
|
||||
Vendored
-6
@@ -67,12 +67,6 @@
|
||||
"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",
|
||||
|
||||
+1
-22
@@ -8,7 +8,7 @@
|
||||
include_directories(.)
|
||||
|
||||
# Dynarmic
|
||||
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITECTURE_loongarch64) AND NOT YUZU_STATIC_ROOM)
|
||||
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64) AND NOT YUZU_STATIC_ROOM)
|
||||
add_subdirectory(dynarmic)
|
||||
add_library(dynarmic::dynarmic ALIAS dynarmic)
|
||||
endif()
|
||||
@@ -21,41 +21,20 @@ 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)
|
||||
|
||||
@@ -458,8 +458,6 @@
|
||||
<string name="renderer_resolution">Разрешение (портативное/в док-станции)</string>
|
||||
<string name="renderer_vsync">Режим верт. синхронизации</string>
|
||||
<string name="renderer_scaling_filter">Фильтр адаптации окна</string>
|
||||
<string name="fsr_sharpness">Резкость FSR/SGSR</string>
|
||||
<string name="fsr_sharpness_description">Определяет, насколько чётким будет изображение при использовании динамического контраста FSR или SGSR фильтров</string>
|
||||
<string name="renderer_anti_aliasing">Метод сглаживания</string>
|
||||
|
||||
|
||||
@@ -499,8 +497,6 @@
|
||||
<string name="fast_gpu_time_description">Принудительно запускает большинство игр в их максимальном нативном разрешении. Используйте значение 256 для максимальной производительности и 512 для максимального качества графики.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Пропустить внутреннюю инвалидацию ЦП</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх.</string>
|
||||
<string name="antiflicker">Анти-мерцание</string>
|
||||
<string name="antiflicker_description">Принудительно заставляет обратные вызовы ГПУ-фильтра ожидать выполнения отправленных задач на ГПУ. Используйте с Быстрым режимом ГПУ, что бы избежать мерцаний с меньшим влиянием на производительность.</string>
|
||||
<string name="fix_bloom_effects">Исправить эффекты размытия</string>
|
||||
<string name="fix_bloom_effects_description">Частично убирает размытие в LA/EOW (Adreno A6XX - A7XX/ Turnip), полностью отключает его в Burnout. Внимание: может вызывать графические артефакты в других играх.</string>
|
||||
<string name="emulate_bgr565">Эмулировать BGR565</string>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -177,8 +177,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
|
||||
queued_buffers.store(0);
|
||||
release_cv.notify_one();
|
||||
}
|
||||
|
||||
static constexpr std::array<s16, 6> silence{};
|
||||
for (size_t i = 0; i < num_frames; i++)
|
||||
std::memset(&output_buffer[i * frame_size], 0, frame_size_bytes);
|
||||
std::memcpy(&output_buffer[i * frame_size], silence.data(), frame_size_bytes);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 +180,10 @@ endif()
|
||||
if(ARCHITECTURE_x86_64)
|
||||
target_sources(
|
||||
common
|
||||
PRIVATE
|
||||
PRIVATE x64/cpu_detect.cpp
|
||||
x64/cpu_detect.h
|
||||
x64/cpu_wait.cpp
|
||||
x64/cpu_wait.h
|
||||
x64/rdtsc.cpp
|
||||
x64/rdtsc.h
|
||||
x64/xbyak.h)
|
||||
@@ -231,7 +234,7 @@ if(CXX_CLANG)
|
||||
endif()
|
||||
|
||||
if (BOOST_NO_HEADERS)
|
||||
target_link_libraries(common PUBLIC Boost::algorithm Boost::heap Boost::icl Boost::pool)
|
||||
target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool)
|
||||
else()
|
||||
target_link_libraries(common PUBLIC Boost::headers)
|
||||
endif()
|
||||
|
||||
+9
-13
@@ -62,20 +62,16 @@ std::vector<Asset> Release::GetPlatformAssets() const {
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#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
|
||||
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"});
|
||||
#elif defined(ARCHITECTURE_arm64)
|
||||
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
|
||||
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
|
||||
#elif defined(__APPLE__)
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
find_asset("Standard", {".dmg", ".tar.gz"});
|
||||
#endif // ARCHITECTURE_arm64
|
||||
#endif
|
||||
#elif defined(__ANDROID__)
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
find_asset("Standard", {"chromeos.apk"});
|
||||
@@ -86,9 +82,9 @@ std::vector<Asset> Release::GetPlatformAssets() const {
|
||||
find_asset("Standard", {"optimized.apk"});
|
||||
#else
|
||||
find_asset("Standard", {"standard.apk"});
|
||||
#endif // GENSHIN_SPOOF
|
||||
#endif // ARCHITECTURE_arm64
|
||||
#endif // __APPLE__
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
return found_assets;
|
||||
}
|
||||
|
||||
|
||||
+18
-20
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -20,14 +20,10 @@
|
||||
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;
|
||||
}
|
||||
@@ -51,12 +47,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.Value() < size) {
|
||||
if (id.index < size) {
|
||||
do {
|
||||
++id.index;
|
||||
} while (id.Value() < size && !IsValid(bitset));
|
||||
if (id.Value() == size) {
|
||||
id = SlotId{};
|
||||
} while (id.index < size && !IsValid(bitset));
|
||||
if (id.index == size) {
|
||||
id.index = SlotId::INVALID_INDEX;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
@@ -89,7 +85,7 @@ public:
|
||||
: slot_vector{slot_vector_}, id{id_} {}
|
||||
|
||||
bool IsValid(const u64* bitset) const noexcept {
|
||||
return ((bitset[id.Value() / 64] >> (id.Value() % 64)) & 1) != 0;
|
||||
return ((bitset[id.index / 64] >> (id.index % 64)) & 1) != 0;
|
||||
}
|
||||
|
||||
SlotVector<T>* slot_vector;
|
||||
@@ -111,12 +107,12 @@ public:
|
||||
|
||||
[[nodiscard]] T& operator[](SlotId id) noexcept {
|
||||
ValidateIndex(id);
|
||||
return values[id.Value()].object;
|
||||
return values[id.index].object;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
|
||||
ValidateIndex(id);
|
||||
return values[id.Value()].object;
|
||||
return values[id.index].object;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
@@ -129,9 +125,9 @@ public:
|
||||
}
|
||||
|
||||
void erase(SlotId id) noexcept {
|
||||
values[id.Value()].object.~T();
|
||||
free_list.push_back(id.Value());
|
||||
ResetStorageBit(id.Value());
|
||||
values[id.index].object.~T();
|
||||
free_list.push_back(id.index);
|
||||
ResetStorageBit(id.index);
|
||||
}
|
||||
|
||||
[[nodiscard]] Iterator begin() noexcept {
|
||||
@@ -145,7 +141,7 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] Iterator end() noexcept {
|
||||
return Iterator(this, SlotId{});
|
||||
return Iterator(this, SlotId{SlotId::INVALID_INDEX});
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t size() const noexcept {
|
||||
@@ -179,8 +175,8 @@ private:
|
||||
|
||||
void ValidateIndex(SlotId id) const noexcept {
|
||||
DEBUG_ASSERT(id);
|
||||
DEBUG_ASSERT(id.Value() / 64 < stored_bitset.size());
|
||||
DEBUG_ASSERT(((stored_bitset[id.Value() / 64] >> (id.Value() % 64)) & 1) != 0);
|
||||
DEBUG_ASSERT(id.index / 64 < stored_bitset.size());
|
||||
DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 FreeValueIndex() noexcept {
|
||||
@@ -212,7 +208,9 @@ 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(), u32(values_capacity));
|
||||
std::iota(free_list.begin() + old_free_size, free_list.end(),
|
||||
static_cast<u32>(values_capacity));
|
||||
|
||||
delete[] values;
|
||||
values = new_values;
|
||||
values_capacity = new_capacity;
|
||||
|
||||
+2
-113
@@ -4,8 +4,6 @@
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
@@ -20,35 +18,24 @@
|
||||
#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
|
||||
|
||||
#include "common/cpu_features.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#ifdef __FreeBSD__
|
||||
# define cpu_set_t cpuset_t
|
||||
#endif
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -157,102 +144,4 @@ 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
|
||||
#ifdef _MSC_VER
|
||||
auto const now = __rdtsc();
|
||||
#else
|
||||
auto const now = _rdtsc();
|
||||
#endif
|
||||
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 {
|
||||
#ifdef _MSC_VER
|
||||
while (!is_set.load() && end > __rdtsc())
|
||||
Common::Windows::SleepForOneTick();
|
||||
#else
|
||||
while (!is_set.load() && end > _rdtsc())
|
||||
Common::Windows::SleepForOneTick();
|
||||
#endif
|
||||
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
|
||||
|
||||
+10
-4
@@ -34,10 +34,16 @@ public:
|
||||
is_set = false;
|
||||
}
|
||||
|
||||
bool WaitFor(const std::chrono::nanoseconds time);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -57,9 +63,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 {
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
// 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,14 +1,11 @@
|
||||
// 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-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <ratio>
|
||||
@@ -46,9 +43,6 @@ 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;
|
||||
}
|
||||
@@ -75,6 +69,7 @@ public:
|
||||
return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
protected:
|
||||
using NsRatio = std::nano;
|
||||
using UsRatio = std::micro;
|
||||
using MsRatio = std::milli;
|
||||
@@ -92,96 +87,31 @@ public:
|
||||
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;
|
||||
[[nodiscard]] inline FactorType GetGuestCNTFRQFactor() const {
|
||||
|
||||
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;
|
||||
#endif
|
||||
};
|
||||
|
||||
#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;
|
||||
[[nodiscard]] WallClock CreateOptimalClock() noexcept;
|
||||
|
||||
} // namespace Common
|
||||
@@ -10,32 +10,34 @@
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(__DragonFly__) || defined(__FreeBSD__)
|
||||
#include <sys/types.h>
|
||||
#include <machine/cpufunc.h>
|
||||
#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/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); }
|
||||
|
||||
static inline u64 xgetbv(u32 index) {
|
||||
return _xgetbv(index);
|
||||
}
|
||||
#else
|
||||
|
||||
#if defined(__DragonFly__) || defined(__FreeBSD__)
|
||||
// clang-format off
|
||||
#include <sys/types.h>
|
||||
#include <machine/cpufunc.h>
|
||||
// clang-format on
|
||||
#endif
|
||||
|
||||
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.
|
||||
@@ -48,7 +50,11 @@ 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;
|
||||
@@ -56,10 +62,9 @@ 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;
|
||||
@@ -71,53 +76,13 @@ CPUCaps::Manufacturer CPUCaps::ParseManufacturer(std::string_view brand_string)
|
||||
return Manufacturer::Unknown;
|
||||
}
|
||||
|
||||
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 = [] {
|
||||
// Detects the various CPU features
|
||||
static CPUCaps Detect() {
|
||||
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
|
||||
@@ -218,8 +183,9 @@ const CPUCaps g_cpu_caps = [] {
|
||||
// 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 = u64(caps.crystal_frequency)
|
||||
* caps.tsc_crystal_ratio_numerator / caps.tsc_crystal_ratio_denominator;
|
||||
caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
|
||||
caps.tsc_crystal_ratio_numerator /
|
||||
caps.tsc_crystal_ratio_denominator;
|
||||
} else {
|
||||
caps.tsc_frequency = X64::EstimateRDTSCFrequency();
|
||||
}
|
||||
@@ -231,196 +197,54 @@ const CPUCaps g_cpu_caps = [] {
|
||||
caps.max_frequency = cpu_id[1];
|
||||
caps.bus_frequency = cpu_id[2];
|
||||
}
|
||||
|
||||
return caps;
|
||||
}();
|
||||
|
||||
#else
|
||||
|
||||
#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)};
|
||||
const CPUCaps& GetCPUCaps() {
|
||||
static CPUCaps caps = Detect();
|
||||
return caps;
|
||||
}
|
||||
|
||||
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));
|
||||
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;
|
||||
}
|
||||
return cntfrq_el0;
|
||||
#else
|
||||
asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
|
||||
return cntfrq_el0;
|
||||
// Shame on you
|
||||
return std::nullopt;
|
||||
#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
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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
|
||||
@@ -0,0 +1,10 @@
|
||||
// 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 OR ARCHITECTURE_loongarch64)
|
||||
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64)
|
||||
target_sources(core PRIVATE
|
||||
arm/dynarmic/arm_dynarmic.h
|
||||
arm/dynarmic/arm_dynarmic_64.cpp
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <numeric>
|
||||
#include <bit>
|
||||
#include "common/cpu_features.h"
|
||||
#include "common/wall_clock.h"
|
||||
#include "common/alignment.h"
|
||||
#include "common/literals.h"
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
|
||||
+28
-10
@@ -8,13 +8,15 @@
|
||||
#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"
|
||||
@@ -45,7 +47,8 @@ struct CoreTiming::Event {
|
||||
}
|
||||
};
|
||||
|
||||
CoreTiming::CoreTiming() = default;
|
||||
CoreTiming::CoreTiming() : clock{Common::CreateOptimalClock()} {}
|
||||
|
||||
CoreTiming::~CoreTiming() {
|
||||
Reset();
|
||||
}
|
||||
@@ -61,16 +64,31 @@ 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 const wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
auto wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
if (wait_time > 0) {
|
||||
#ifdef _WIN32
|
||||
while (!paused && !event.IsSet() && wait_time > 0) {
|
||||
wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
if (wait_time >= timer_resolution_ns) {
|
||||
Common::Windows::SleepForOneTick();
|
||||
} else {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
Common::X64::MicroSleep();
|
||||
#else
|
||||
std::this_thread::yield();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (event.IsSet())
|
||||
event.Reset();
|
||||
#else
|
||||
event.WaitFor(std::chrono::nanoseconds(wait_time));
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// Queue is empty, wait until another event is scheduled and signals us to
|
||||
@@ -208,7 +226,7 @@ void CoreTiming::ResetTicks() {
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetClockTicks() const {
|
||||
u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
u64 fres = is_multicore ? clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) {
|
||||
fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock)));
|
||||
}
|
||||
@@ -222,7 +240,7 @@ u64 CoreTiming::GetClockTicks() const {
|
||||
|
||||
u64 CoreTiming::GetGPUTicks() const {
|
||||
return is_multicore
|
||||
? Common::g_wall_clock.GetGPUTick()
|
||||
? clock.GetGPUTick()
|
||||
: Common::WallClock::CPUTickToGPUTick(cpu_ticks);
|
||||
}
|
||||
|
||||
@@ -299,14 +317,14 @@ void CoreTiming::Reset() {
|
||||
/// @brief Returns current time in nanoseconds.
|
||||
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const noexcept {
|
||||
return is_multicore
|
||||
? Common::g_wall_clock.GetTimeNS()
|
||||
? 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
|
||||
? Common::g_wall_clock.GetTimeUS()
|
||||
? clock.GetTimeUS()
|
||||
: std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
|
||||
}
|
||||
|
||||
|
||||
+18
-9
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/cpu_features.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
@@ -142,28 +142,37 @@ public:
|
||||
|
||||
void Reset();
|
||||
|
||||
using heap_t = boost::heap::fibonacci_heap<CoreTiming::Event, boost::heap::compare<std::greater<>>>;
|
||||
heap_t event_queue;
|
||||
Common::WallClock clock;
|
||||
|
||||
s64 global_timer = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
s64 timer_resolution_ns;
|
||||
#endif
|
||||
|
||||
using heap_t =
|
||||
boost::heap::fibonacci_heap<CoreTiming::Event, boost::heap::compare<std::greater<>>>;
|
||||
|
||||
heap_t event_queue;
|
||||
u64 event_fifo_id = 0;
|
||||
s64 pause_end_time{};
|
||||
/// Cycle timing
|
||||
u64 cpu_ticks{};
|
||||
s64 downcount{};
|
||||
|
||||
Common::Event event{};
|
||||
Common::Event pause_event{};
|
||||
std::function<void()> on_thread_init{};
|
||||
std::jthread timer_thread;
|
||||
mutable std::mutex basic_lock;
|
||||
std::mutex advance_lock;
|
||||
std::jthread timer_thread;
|
||||
std::atomic<bool> paused{};
|
||||
std::atomic<bool> paused_set{};
|
||||
std::atomic<bool> wait_set{};
|
||||
std::atomic<bool> has_started{};
|
||||
std::function<void()> on_thread_init{};
|
||||
|
||||
bool is_multicore{};
|
||||
s64 pause_end_time{};
|
||||
|
||||
/// Cycle timing
|
||||
u64 cpu_ticks{};
|
||||
s64 downcount{};
|
||||
};
|
||||
|
||||
/// Creates a core timing event with the given name and callback.
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
@@ -375,10 +372,6 @@ bool LifecycleManager::UpdateRequestedFocusState() {
|
||||
// Mark the focus state as ready for update.
|
||||
m_requested_focus_state = new_state;
|
||||
|
||||
if (m_is_application) {
|
||||
m_has_focus_state_changed = true;
|
||||
}
|
||||
|
||||
// We changed the focus state.
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#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"
|
||||
@@ -29,6 +28,7 @@ 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 = Common::g_wall_clock.GetTimeNS().count();
|
||||
slots[slot].presentation_time = clock.GetTimeNS().count();
|
||||
slots[slot].fence = fence;
|
||||
|
||||
item.slot = slot;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/cpu_features.h"
|
||||
#include "common/wall_clock.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,6 +89,7 @@ private:
|
||||
s32 next_callback_ticket{};
|
||||
s32 current_callback_ticket{};
|
||||
std::condition_variable_any callback_condition;
|
||||
Common::WallClock clock;
|
||||
Service::Nvidia::NvCore::NvMap& nvmap;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 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/cpu_features.h"
|
||||
#include "common/wall_clock.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/psc/time/errors.h"
|
||||
|
||||
@@ -130,10 +130,6 @@ 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,20 +295,6 @@ 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)
|
||||
|
||||
@@ -24,7 +24,7 @@ using DoNotFastmemMarker = std::tuple<IR::LocationDescriptor, unsigned>;
|
||||
constexpr std::size_t xmrx(std::size_t x) noexcept {
|
||||
x ^= x >> 32;
|
||||
x *= 0xff51afd7ed558ccd;
|
||||
x ^= std::rotr(x, 47) ^ std::rotr(x, 23);
|
||||
x ^= mcl::bit::rotate_right(x, 47) ^ mcl::bit::rotate_right(x, 23);
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,6 @@ 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
|
||||
@@ -49,10 +45,6 @@ struct FakeCall {
|
||||
struct FakeCall {
|
||||
u64 call_sepc;
|
||||
};
|
||||
#elif defined(ARCHITECTURE_loongarch64)
|
||||
struct FakeCall {
|
||||
u64 call_pc;
|
||||
};
|
||||
#else
|
||||
# error "Invalid architecture"
|
||||
#endif
|
||||
@@ -68,8 +60,6 @@ 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 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -28,10 +28,6 @@ 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,25 +6,20 @@
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <optional>
|
||||
#include <bit>
|
||||
#include <fmt/format.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include "dynarmic/backend/exception_handler.h"
|
||||
#include "common/assert.h"
|
||||
#include "dynarmic/common/context.h"
|
||||
#include "common/common_types.h"
|
||||
#if defined(ARCHITECTURE_x86_64)
|
||||
# include "dynarmic/backend/x64/block_of_code.h"
|
||||
#elif defined(ARCHITECTURE_arm64)
|
||||
@@ -32,8 +27,6 @@
|
||||
# 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
|
||||
@@ -157,16 +150,6 @@ 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
|
||||
@@ -226,10 +209,6 @@ 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
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
// 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
|
||||
@@ -1,268 +0,0 @@
|
||||
// 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
|
||||
@@ -1,23 +0,0 @@
|
||||
// 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
|
||||
@@ -1,54 +0,0 @@
|
||||
// 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
|
||||
@@ -1532,7 +1532,7 @@ void EmitX64::EmitFPSingleToHalf(EmitContext& ctx, IR::Inst* inst) {
|
||||
if (ctx.FPCR().DN()) {
|
||||
ForceToDefaultNaN<32>(code, result);
|
||||
}
|
||||
code.vcvtps2ph(result, result, u8(*round_imm));
|
||||
code.vcvtps2ph(result, result, static_cast<u8>(*round_imm));
|
||||
|
||||
ctx.reg_alloc.DefineValue(code, inst, result);
|
||||
return;
|
||||
@@ -1540,7 +1540,7 @@ void EmitX64::EmitFPSingleToHalf(EmitContext& ctx, IR::Inst* inst) {
|
||||
|
||||
ctx.reg_alloc.HostCall(code, inst, args[0]);
|
||||
code.mov(code.ABI_PARAM2.cvt32(), ctx.FPCR().Value());
|
||||
code.mov(code.ABI_PARAM3.cvt32(), u32(rounding_mode));
|
||||
code.mov(code.ABI_PARAM3.cvt32(), static_cast<u32>(rounding_mode));
|
||||
code.lea(code.ABI_PARAM4, code.ptr[code.ABI_JIT_PTR + code.GetJitStateInfo().offsetof_fpsr_exc]);
|
||||
code.CallFunction(&FP::FPConvert<u16, u32>);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ void EmitSignedSaturatedOp(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst)
|
||||
Xbyak::Reg addend = ctx.reg_alloc.UseGpr(code, args[1]).changeBit(size);
|
||||
Xbyak::Reg overflow = ctx.reg_alloc.ScratchGpr(code).changeBit(size);
|
||||
|
||||
constexpr u64 int_max = u64((std::numeric_limits<std::make_signed_t<mcl::unsigned_integer_of_size<size>>>::max)());
|
||||
constexpr u64 int_max = static_cast<u64>((std::numeric_limits<mcl::signed_integer_of_size<size>>::max)());
|
||||
if constexpr (size < 64) {
|
||||
code.xor_(overflow.cvt32(), overflow.cvt32());
|
||||
code.bt(result.cvt32(), size - 1);
|
||||
|
||||
@@ -3553,7 +3553,7 @@ void EmitX64::EmitVectorPopulationCount(EmitContext& ctx, IR::Inst* inst) {
|
||||
|
||||
EmitOneArgumentFallback(code, ctx, inst, [](VectorArray<u8>& result, const VectorArray<u8>& a) {
|
||||
std::transform(a.begin(), a.end(), result.begin(), [](u8 val) {
|
||||
return static_cast<u8>(std::popcount(val));
|
||||
return static_cast<u8>(mcl::bit::count_ones(val));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1657,10 +1657,20 @@ void EmitFPVectorRoundInt(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
|
||||
|
||||
if constexpr (fsize != 16) {
|
||||
if (code.HasHostFeature(HostFeature::SSE41) && rounding != FP::RoundingMode::ToNearest_TieAwayFromZero && !exact) {
|
||||
const auto round_imm = ConvertRoundingModeToX64Immediate(rounding);
|
||||
const u8 round_imm = [&]() -> u8 {
|
||||
switch (rounding) {
|
||||
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, [&](const Xbyak::Xmm& result, const Xbyak::Xmm& xmm_a) {
|
||||
FCODE(roundp)(result, xmm_a, *round_imm);
|
||||
FCODE(roundp)(result, xmm_a, round_imm);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1992,7 +2002,19 @@ void EmitFPVectorToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
const Xbyak::Xmm src = ctx.reg_alloc.UseScratchXmm(code, args[0]);
|
||||
MaybeStandardFPSCRValue(code, ctx, fpcr_controlled, [&] {
|
||||
const auto round_imm = ConvertRoundingModeToX64Immediate(rounding);
|
||||
const int round_imm = [&] {
|
||||
switch (rounding) {
|
||||
case FP::RoundingMode::ToNearest_TieEven:
|
||||
default:
|
||||
return 0b00;
|
||||
case FP::RoundingMode::TowardsPlusInfinity:
|
||||
return 0b10;
|
||||
case FP::RoundingMode::TowardsMinusInfinity:
|
||||
return 0b01;
|
||||
case FP::RoundingMode::TowardsZero:
|
||||
return 0b11;
|
||||
}
|
||||
}();
|
||||
const auto perform_conversion = [&code, &ctx](const Xbyak::Xmm& src) {
|
||||
// MSVC doesn't allow us to use a [&] capture, so we have to do this instead.
|
||||
(void)ctx;
|
||||
@@ -2024,7 +2046,7 @@ void EmitFPVectorToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
|
||||
FCODE(mulp)(src, GetVectorOf<fsize>(code, scale_factor));
|
||||
}
|
||||
|
||||
FCODE(roundp)(src, src, u8(*round_imm));
|
||||
FCODE(roundp)(src, src, u8(round_imm));
|
||||
const Xbyak::Xmm nan_mask = xmm0;
|
||||
if (code.HasHostFeature(HostFeature::AVX512_OrthoFloat)) {
|
||||
static constexpr u32 nan_to_zero = FixupLUT(FpFixup::PosZero, FpFixup::PosZero);
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// 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
|
||||
@@ -52,7 +52,7 @@ struct TranslatorVisitor final {
|
||||
u32 imm32 = imm8.ZeroExtend();
|
||||
auto carry_out = carry_in;
|
||||
if (rotate) {
|
||||
imm32 = std::rotr<u32>(imm8.ZeroExtend(), rotate * 2);
|
||||
imm32 = mcl::bit::rotate_right<u32>(imm8.ZeroExtend(), rotate * 2);
|
||||
carry_out = ir.Imm1(mcl::bit::get_bit<31>(imm32));
|
||||
}
|
||||
return {imm32, carry_out};
|
||||
@@ -81,7 +81,7 @@ struct TranslatorVisitor final {
|
||||
}();
|
||||
return {imm32, carry_in};
|
||||
}
|
||||
const u32 imm32 = std::rotr<u32>((1 << 7) | imm12.Bits<0, 6>(), imm12.Bits<7, 11>());
|
||||
const u32 imm32 = mcl::bit::rotate_right<u32>((1 << 7) | imm12.Bits<0, 6>(), imm12.Bits<7, 11>());
|
||||
return {imm32, ir.Imm1(mcl::bit::get_bit<31>(imm32))};
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ bool TranslatorVisitor::asimd_VDUP_scalar(bool D, Imm<4> imm4, size_t Vd, bool Q
|
||||
return UndefinedInstruction();
|
||||
}
|
||||
|
||||
const size_t imm4_lsb = std::countr_zero(imm4.ZeroExtend());
|
||||
const size_t imm4_lsb = mcl::bit::lowest_set_bit(imm4.ZeroExtend());
|
||||
const size_t esize = 8u << imm4_lsb;
|
||||
const size_t index = imm4.ZeroExtend() >> (imm4_lsb + 1);
|
||||
const auto d = ToVector(Q, Vd, D);
|
||||
|
||||
@@ -799,7 +799,7 @@ static bool LDMHelper(A32::IREmitter& ir, bool W, Reg n, RegList list, IR::U32 s
|
||||
|
||||
// LDM <Rn>{!}, <reg_list>
|
||||
bool TranslatorVisitor::arm_LDM(Cond cond, bool W, Reg n, RegList list) {
|
||||
if (n == Reg::PC || std::popcount(list) < 1) {
|
||||
if (n == Reg::PC || mcl::bit::count_ones(list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
if (W && mcl::bit::get_bit(static_cast<size_t>(n), list)) {
|
||||
@@ -811,13 +811,13 @@ bool TranslatorVisitor::arm_LDM(Cond cond, bool W, Reg n, RegList list) {
|
||||
}
|
||||
|
||||
const auto start_address = ir.GetRegister(n);
|
||||
const auto writeback_address = ir.Add(start_address, ir.Imm32(u32(std::popcount(list) * 4)));
|
||||
const auto writeback_address = ir.Add(start_address, ir.Imm32(u32(mcl::bit::count_ones(list) * 4)));
|
||||
return LDMHelper(ir, W, n, list, start_address, writeback_address);
|
||||
}
|
||||
|
||||
// LDMDA <Rn>{!}, <reg_list>
|
||||
bool TranslatorVisitor::arm_LDMDA(Cond cond, bool W, Reg n, RegList list) {
|
||||
if (n == Reg::PC || std::popcount(list) < 1) {
|
||||
if (n == Reg::PC || mcl::bit::count_ones(list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
if (W && mcl::bit::get_bit(static_cast<size_t>(n), list)) {
|
||||
@@ -828,14 +828,14 @@ bool TranslatorVisitor::arm_LDMDA(Cond cond, bool W, Reg n, RegList list) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list) - 4)));
|
||||
const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list) - 4)));
|
||||
const auto writeback_address = ir.Sub(start_address, ir.Imm32(4));
|
||||
return LDMHelper(ir, W, n, list, start_address, writeback_address);
|
||||
}
|
||||
|
||||
// LDMDB <Rn>{!}, <reg_list>
|
||||
bool TranslatorVisitor::arm_LDMDB(Cond cond, bool W, Reg n, RegList list) {
|
||||
if (n == Reg::PC || std::popcount(list) < 1) {
|
||||
if (n == Reg::PC || mcl::bit::count_ones(list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
if (W && mcl::bit::get_bit(static_cast<size_t>(n), list)) {
|
||||
@@ -846,14 +846,14 @@ bool TranslatorVisitor::arm_LDMDB(Cond cond, bool W, Reg n, RegList list) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list))));
|
||||
const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list))));
|
||||
const auto writeback_address = start_address;
|
||||
return LDMHelper(ir, W, n, list, start_address, writeback_address);
|
||||
}
|
||||
|
||||
// LDMIB <Rn>{!}, <reg_list>
|
||||
bool TranslatorVisitor::arm_LDMIB(Cond cond, bool W, Reg n, RegList list) {
|
||||
if (n == Reg::PC || std::popcount(list) < 1) {
|
||||
if (n == Reg::PC || mcl::bit::count_ones(list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
if (W && mcl::bit::get_bit(static_cast<size_t>(n), list)) {
|
||||
@@ -865,7 +865,7 @@ bool TranslatorVisitor::arm_LDMIB(Cond cond, bool W, Reg n, RegList list) {
|
||||
}
|
||||
|
||||
const auto start_address = ir.Add(ir.GetRegister(n), ir.Imm32(4));
|
||||
const auto writeback_address = ir.Add(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list))));
|
||||
const auto writeback_address = ir.Add(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list))));
|
||||
return LDMHelper(ir, W, n, list, start_address, writeback_address);
|
||||
}
|
||||
|
||||
@@ -896,7 +896,7 @@ static bool STMHelper(A32::IREmitter& ir, bool W, Reg n, RegList list, IR::U32 s
|
||||
|
||||
// STM <Rn>{!}, <reg_list>
|
||||
bool TranslatorVisitor::arm_STM(Cond cond, bool W, Reg n, RegList list) {
|
||||
if (n == Reg::PC || std::popcount(list) < 1) {
|
||||
if (n == Reg::PC || mcl::bit::count_ones(list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
|
||||
@@ -905,13 +905,13 @@ bool TranslatorVisitor::arm_STM(Cond cond, bool W, Reg n, RegList list) {
|
||||
}
|
||||
|
||||
const auto start_address = ir.GetRegister(n);
|
||||
const auto writeback_address = ir.Add(start_address, ir.Imm32(u32(std::popcount(list) * 4)));
|
||||
const auto writeback_address = ir.Add(start_address, ir.Imm32(u32(mcl::bit::count_ones(list) * 4)));
|
||||
return STMHelper(ir, W, n, list, start_address, writeback_address);
|
||||
}
|
||||
|
||||
// STMDA <Rn>{!}, <reg_list>
|
||||
bool TranslatorVisitor::arm_STMDA(Cond cond, bool W, Reg n, RegList list) {
|
||||
if (n == Reg::PC || std::popcount(list) < 1) {
|
||||
if (n == Reg::PC || mcl::bit::count_ones(list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
|
||||
@@ -919,14 +919,14 @@ bool TranslatorVisitor::arm_STMDA(Cond cond, bool W, Reg n, RegList list) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list) - 4)));
|
||||
const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list) - 4)));
|
||||
const auto writeback_address = ir.Sub(start_address, ir.Imm32(4));
|
||||
return STMHelper(ir, W, n, list, start_address, writeback_address);
|
||||
}
|
||||
|
||||
// STMDB <Rn>{!}, <reg_list>
|
||||
bool TranslatorVisitor::arm_STMDB(Cond cond, bool W, Reg n, RegList list) {
|
||||
if (n == Reg::PC || std::popcount(list) < 1) {
|
||||
if (n == Reg::PC || mcl::bit::count_ones(list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
|
||||
@@ -934,14 +934,14 @@ bool TranslatorVisitor::arm_STMDB(Cond cond, bool W, Reg n, RegList list) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list))));
|
||||
const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list))));
|
||||
const auto writeback_address = start_address;
|
||||
return STMHelper(ir, W, n, list, start_address, writeback_address);
|
||||
}
|
||||
|
||||
// STMIB <Rn>{!}, <reg_list>
|
||||
bool TranslatorVisitor::arm_STMIB(Cond cond, bool W, Reg n, RegList list) {
|
||||
if (n == Reg::PC || std::popcount(list) < 1) {
|
||||
if (n == Reg::PC || mcl::bit::count_ones(list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
|
||||
@@ -950,7 +950,7 @@ bool TranslatorVisitor::arm_STMIB(Cond cond, bool W, Reg n, RegList list) {
|
||||
}
|
||||
|
||||
const auto start_address = ir.Add(ir.GetRegister(n), ir.Imm32(4));
|
||||
const auto writeback_address = ir.Add(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list))));
|
||||
const auto writeback_address = ir.Add(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list))));
|
||||
return STMHelper(ir, W, n, list, start_address, writeback_address);
|
||||
}
|
||||
|
||||
|
||||
@@ -688,7 +688,7 @@ bool TranslatorVisitor::thumb16_NOP() {
|
||||
// IT{<x>{<y>{<z>}}} <cond>
|
||||
bool TranslatorVisitor::thumb16_IT(Imm<8> imm8) {
|
||||
ASSERT((imm8.Bits<0, 3>() != 0b0000) && "Decode Error");
|
||||
if (imm8.Bits<4, 7>() == 0b1111 || (imm8.Bits<4, 7>() == 0b1110 && std::popcount(imm8.Bits<0, 3>()) != 1)) {
|
||||
if (imm8.Bits<4, 7>() == 0b1111 || (imm8.Bits<4, 7>() == 0b1110 && mcl::bit::count_ones(imm8.Bits<0, 3>()) != 1)) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
if (ir.current_location.IT().IsInITBlock()) {
|
||||
@@ -738,11 +738,11 @@ bool TranslatorVisitor::thumb16_PUSH(bool M, RegList reg_list) {
|
||||
if (M) {
|
||||
reg_list |= 1 << 14;
|
||||
}
|
||||
if (std::popcount(reg_list) < 1) {
|
||||
if (mcl::bit::count_ones(reg_list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
|
||||
const u32 num_bytes_to_push = static_cast<u32>(4 * std::popcount(reg_list));
|
||||
const u32 num_bytes_to_push = static_cast<u32>(4 * mcl::bit::count_ones(reg_list));
|
||||
const auto final_address = ir.Sub(ir.GetRegister(Reg::SP), ir.Imm32(num_bytes_to_push));
|
||||
auto address = final_address;
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
@@ -764,7 +764,7 @@ bool TranslatorVisitor::thumb16_POP(bool P, RegList reg_list) {
|
||||
if (P) {
|
||||
reg_list |= 1 << 15;
|
||||
}
|
||||
if (std::popcount(reg_list) < 1) {
|
||||
if (mcl::bit::count_ones(reg_list) < 1) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
|
||||
@@ -851,10 +851,10 @@ bool TranslatorVisitor::thumb16_BKPT(Imm<8> /*imm8*/) {
|
||||
|
||||
// STM <Rn>!, <reg_list>
|
||||
bool TranslatorVisitor::thumb16_STMIA(Reg n, RegList reg_list) {
|
||||
if (std::popcount(reg_list) == 0) {
|
||||
if (mcl::bit::count_ones(reg_list) == 0) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
if (mcl::bit::get_bit(static_cast<size_t>(n), reg_list) && n != static_cast<Reg>(std::countr_zero(reg_list))) {
|
||||
if (mcl::bit::get_bit(static_cast<size_t>(n), reg_list) && n != static_cast<Reg>(mcl::bit::lowest_set_bit(reg_list))) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
|
||||
@@ -873,7 +873,7 @@ bool TranslatorVisitor::thumb16_STMIA(Reg n, RegList reg_list) {
|
||||
|
||||
// LDM <Rn>!, <reg_list>
|
||||
bool TranslatorVisitor::thumb16_LDMIA(Reg n, RegList reg_list) {
|
||||
if (std::popcount(reg_list) == 0) {
|
||||
if (mcl::bit::count_ones(reg_list) == 0) {
|
||||
return UnpredictableInstruction();
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -53,7 +53,7 @@ static bool STMHelper(A32::IREmitter& ir, bool W, Reg n, u32 list, const IR::U32
|
||||
|
||||
bool TranslatorVisitor::thumb32_LDMDB(bool W, Reg n, Imm<16> reg_list) {
|
||||
const auto regs_imm = reg_list.ZeroExtend();
|
||||
const auto num_regs = static_cast<u32>(std::popcount(regs_imm));
|
||||
const auto num_regs = static_cast<u32>(mcl::bit::count_ones(regs_imm));
|
||||
|
||||
if (n == Reg::PC || num_regs < 2) {
|
||||
return UnpredictableInstruction();
|
||||
@@ -78,7 +78,7 @@ bool TranslatorVisitor::thumb32_LDMDB(bool W, Reg n, Imm<16> reg_list) {
|
||||
|
||||
bool TranslatorVisitor::thumb32_LDMIA(bool W, Reg n, Imm<16> reg_list) {
|
||||
const auto regs_imm = reg_list.ZeroExtend();
|
||||
const auto num_regs = static_cast<u32>(std::popcount(regs_imm));
|
||||
const auto num_regs = static_cast<u32>(mcl::bit::count_ones(regs_imm));
|
||||
|
||||
if (n == Reg::PC || num_regs < 2) {
|
||||
return UnpredictableInstruction();
|
||||
@@ -111,7 +111,7 @@ bool TranslatorVisitor::thumb32_PUSH(Imm<15> reg_list) {
|
||||
|
||||
bool TranslatorVisitor::thumb32_STMIA(bool W, Reg n, Imm<15> reg_list) {
|
||||
const auto regs_imm = reg_list.ZeroExtend();
|
||||
const auto num_regs = static_cast<u32>(std::popcount(regs_imm));
|
||||
const auto num_regs = static_cast<u32>(mcl::bit::count_ones(regs_imm));
|
||||
|
||||
if (n == Reg::PC || num_regs < 2) {
|
||||
return UnpredictableInstruction();
|
||||
@@ -130,7 +130,7 @@ bool TranslatorVisitor::thumb32_STMIA(bool W, Reg n, Imm<15> reg_list) {
|
||||
|
||||
bool TranslatorVisitor::thumb32_STMDB(bool W, Reg n, Imm<15> reg_list) {
|
||||
const auto regs_imm = reg_list.ZeroExtend();
|
||||
const auto num_regs = static_cast<u32>(std::popcount(regs_imm));
|
||||
const auto num_regs = static_cast<u32>(mcl::bit::count_ones(regs_imm));
|
||||
|
||||
if (n == Reg::PC || num_regs < 2) {
|
||||
return UnpredictableInstruction();
|
||||
|
||||
@@ -55,7 +55,7 @@ std::optional<TranslatorVisitor::BitMasks> TranslatorVisitor::DecodeBitMasks(boo
|
||||
const size_t esize = size_t{1} << len;
|
||||
const u64 welem = mcl::bit::ones<u64>(S + 1);
|
||||
const u64 telem = mcl::bit::ones<u64>(d + 1);
|
||||
const u64 wmask = std::rotr(mcl::bit::replicate_element<u64>(esize, welem), R);
|
||||
const u64 wmask = mcl::bit::rotate_right(mcl::bit::replicate_element<u64>(esize, welem), R);
|
||||
const u64 tmask = mcl::bit::replicate_element<u64>(esize, telem);
|
||||
|
||||
return BitMasks{wmask, tmask};
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
namespace Dynarmic::A64 {
|
||||
|
||||
bool TranslatorVisitor::DUP_elt_1(Imm<5> imm5, Vec Vn, Vec Vd) {
|
||||
const size_t size = std::countr_zero(imm5.ZeroExtend());
|
||||
const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend());
|
||||
if (size > 3) {
|
||||
return ReservedValue();
|
||||
}
|
||||
@@ -30,7 +30,7 @@ bool TranslatorVisitor::DUP_elt_1(Imm<5> imm5, Vec Vn, Vec Vd) {
|
||||
}
|
||||
|
||||
bool TranslatorVisitor::DUP_elt_2(bool Q, Imm<5> imm5, Vec Vn, Vec Vd) {
|
||||
const size_t size = std::countr_zero(imm5.ZeroExtend());
|
||||
const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend());
|
||||
if (size > 3) {
|
||||
return ReservedValue();
|
||||
}
|
||||
@@ -51,7 +51,7 @@ bool TranslatorVisitor::DUP_elt_2(bool Q, Imm<5> imm5, Vec Vn, Vec Vd) {
|
||||
}
|
||||
|
||||
bool TranslatorVisitor::DUP_gen(bool Q, Imm<5> imm5, Reg Rn, Vec Vd) {
|
||||
const size_t size = std::countr_zero(imm5.ZeroExtend());
|
||||
const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend());
|
||||
if (size > 3) {
|
||||
return ReservedValue();
|
||||
}
|
||||
@@ -73,7 +73,7 @@ bool TranslatorVisitor::DUP_gen(bool Q, Imm<5> imm5, Reg Rn, Vec Vd) {
|
||||
}
|
||||
|
||||
bool TranslatorVisitor::SMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) {
|
||||
const size_t size = std::countr_zero(imm5.ZeroExtend());
|
||||
const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend());
|
||||
if (size == 2 && !Q) {
|
||||
return UnallocatedEncoding();
|
||||
}
|
||||
@@ -96,7 +96,7 @@ bool TranslatorVisitor::SMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) {
|
||||
}
|
||||
|
||||
bool TranslatorVisitor::UMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) {
|
||||
const size_t size = std::countr_zero(imm5.ZeroExtend());
|
||||
const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend());
|
||||
if (size < 3 && Q) {
|
||||
return UnallocatedEncoding();
|
||||
}
|
||||
@@ -123,7 +123,7 @@ bool TranslatorVisitor::UMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) {
|
||||
}
|
||||
|
||||
bool TranslatorVisitor::INS_gen(Imm<5> imm5, Reg Rn, Vec Vd) {
|
||||
const size_t size = std::countr_zero(imm5.ZeroExtend());
|
||||
const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend());
|
||||
if (size > 3) {
|
||||
return ReservedValue();
|
||||
}
|
||||
@@ -140,7 +140,7 @@ bool TranslatorVisitor::INS_gen(Imm<5> imm5, Reg Rn, Vec Vd) {
|
||||
}
|
||||
|
||||
bool TranslatorVisitor::INS_elt(Imm<5> imm5, Imm<4> imm4, Vec Vn, Vec Vd) {
|
||||
const size_t size = std::countr_zero(imm5.ZeroExtend());
|
||||
const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend());
|
||||
if (size > 3) {
|
||||
return ReservedValue();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
@@ -32,10 +31,6 @@ 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;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <bit>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include "boost/container/small_vector.hpp"
|
||||
@@ -29,6 +28,7 @@
|
||||
#include "dynarmic/ir/opt_passes.h"
|
||||
#include "dynarmic/ir/type.h"
|
||||
#include "dynarmic/mcl/bit.hpp"
|
||||
#include "dynarmic/mcl/bit.hpp"
|
||||
|
||||
namespace Dynarmic::Optimization {
|
||||
|
||||
@@ -1074,12 +1074,12 @@ static void ConstantPropagation(IR::Block& block) {
|
||||
break;
|
||||
case Op::BitRotateRight32:
|
||||
if (FoldShifts(inst)) {
|
||||
ReplaceUsesWith(inst, true, std::rotr<u32>(inst.GetArg(0).GetU32(), inst.GetArg(1).GetU8()));
|
||||
ReplaceUsesWith(inst, true, mcl::bit::rotate_right<u32>(inst.GetArg(0).GetU32(), inst.GetArg(1).GetU8()));
|
||||
}
|
||||
break;
|
||||
case Op::BitRotateRight64:
|
||||
if (FoldShifts(inst)) {
|
||||
ReplaceUsesWith(inst, false, std::rotr<u64>(inst.GetArg(0).GetU64(), inst.GetArg(1).GetU8()));
|
||||
ReplaceUsesWith(inst, false, mcl::bit::rotate_right<u64>(inst.GetArg(0).GetU64(), inst.GetArg(1).GetU8()));
|
||||
}
|
||||
break;
|
||||
case Op::LogicalShiftLeftMasked32:
|
||||
@@ -1114,12 +1114,12 @@ static void ConstantPropagation(IR::Block& block) {
|
||||
break;
|
||||
case Op::RotateRightMasked32:
|
||||
if (inst.AreAllArgsImmediates()) {
|
||||
ReplaceUsesWith(inst, true, std::rotr<u32>(inst.GetArg(0).GetU32(), inst.GetArg(1).GetU32()));
|
||||
ReplaceUsesWith(inst, true, mcl::bit::rotate_right<u32>(inst.GetArg(0).GetU32(), inst.GetArg(1).GetU32()));
|
||||
}
|
||||
break;
|
||||
case Op::RotateRightMasked64:
|
||||
if (inst.AreAllArgsImmediates()) {
|
||||
ReplaceUsesWith(inst, false, std::rotr<u64>(inst.GetArg(0).GetU64(), inst.GetArg(1).GetU64()));
|
||||
ReplaceUsesWith(inst, false, mcl::bit::rotate_right<u64>(inst.GetArg(0).GetU64(), inst.GetArg(1).GetU64()));
|
||||
}
|
||||
break;
|
||||
case Op::Add32:
|
||||
|
||||
@@ -8,15 +8,26 @@
|
||||
#include <bitset>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <concepts>
|
||||
#include <bit>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace mcl {
|
||||
namespace detail {
|
||||
template<typename T, typename U>
|
||||
concept SameHelper = std::is_same_v<T, U>;
|
||||
} // namespace detail
|
||||
template<typename T, typename U>
|
||||
concept SameAs = detail::SameHelper<T, U> && detail::SameHelper<U, T>;
|
||||
template<typename T, typename... U>
|
||||
concept IsAnyOf = (SameAs<T, U> || ...);
|
||||
/// Integral upon which bit operations can be safely performed.
|
||||
template<typename T>
|
||||
concept BitIntegral = IsAnyOf<T, u8, u16, u32, u64, std::uintptr_t, size_t>;
|
||||
|
||||
template<typename T>
|
||||
constexpr std::size_t bitsizeof = CHAR_BIT * sizeof(T);
|
||||
|
||||
} // namespace mcl
|
||||
|
||||
namespace mcl::bit {
|
||||
@@ -60,37 +71,58 @@ constexpr u64 swap_words_64(u64 value) {
|
||||
| ((value & 0x00000000ffffffffull) << 32);
|
||||
}
|
||||
|
||||
template<BitIntegral T>
|
||||
constexpr T rotate_right(T x, size_t amount) {
|
||||
amount %= bitsizeof<T>;
|
||||
if (amount == 0) {
|
||||
return x;
|
||||
}
|
||||
return static_cast<T>((x >> amount) | (x << (bitsizeof<T> - amount)));
|
||||
}
|
||||
|
||||
template<BitIntegral T>
|
||||
constexpr T rotate_left(T x, size_t amount) {
|
||||
amount %= bitsizeof<T>;
|
||||
if (amount == 0) {
|
||||
return x;
|
||||
}
|
||||
return static_cast<T>((x << amount) | (x >> (bitsizeof<T> - amount)));
|
||||
}
|
||||
|
||||
/// Create a mask with `count` number of one bits.
|
||||
template<size_t count, std::integral T>
|
||||
template<size_t count, BitIntegral T>
|
||||
constexpr T ones() {
|
||||
static_assert(count <= bitsizeof<T>, "count larger than bitsize of T");
|
||||
|
||||
if constexpr (count == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return T(~T(0)) >> (bitsizeof<T> - count);
|
||||
return static_cast<T>(~static_cast<T>(0)) >> (bitsizeof<T> - count);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a mask with `count` number of one bits.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T ones(size_t count) {
|
||||
ASSERT(count <= bitsizeof<T> && "count larger than bitsize of T");
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
return T(~T(0)) >> (bitsizeof<T> - count);
|
||||
}
|
||||
return static_cast<T>(~static_cast<T>(0)) >> (bitsizeof<T> - count);
|
||||
}
|
||||
|
||||
/// Create a mask of type T for bits [begin_bit, end_bit] inclusive.
|
||||
template<size_t begin_bit, size_t end_bit, std::integral T>
|
||||
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
||||
constexpr T mask() {
|
||||
static_assert(begin_bit <= end_bit, "invalid bit range (position of beginning bit cannot be greater than that of end bit)");
|
||||
static_assert(begin_bit < bitsizeof<T>, "begin_bit must be smaller than size of T");
|
||||
static_assert(end_bit < bitsizeof<T>, "end_bit must be smaller than size of T");
|
||||
|
||||
return ones<end_bit - begin_bit + 1, T>() << begin_bit;
|
||||
}
|
||||
|
||||
/// Create a mask of type T for bits [begin_bit, end_bit] inclusive.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T mask(size_t begin_bit, size_t end_bit) {
|
||||
ASSERT(begin_bit <= end_bit && "invalid bit range (position of beginning bit cannot be greater than that of end bit)");
|
||||
ASSERT(begin_bit < bitsizeof<T> && "begin_bit must be smaller than size of T");
|
||||
@@ -99,100 +131,101 @@ constexpr T mask(size_t begin_bit, size_t end_bit) {
|
||||
}
|
||||
|
||||
/// Extract bits [begin_bit, end_bit] inclusive from value of type T.
|
||||
template<size_t begin_bit, size_t end_bit, std::integral T>
|
||||
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
||||
constexpr T get_bits(T value) {
|
||||
constexpr T m = mask<begin_bit, end_bit, T>();
|
||||
return (value & m) >> begin_bit;
|
||||
}
|
||||
|
||||
/// Extract bits [begin_bit, end_bit] inclusive from value of type T.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T get_bits(size_t begin_bit, size_t end_bit, T value) {
|
||||
const T m = mask<T>(begin_bit, end_bit);
|
||||
return (value & m) >> begin_bit;
|
||||
}
|
||||
|
||||
/// Clears bits [begin_bit, end_bit] inclusive of value of type T.
|
||||
template<size_t begin_bit, size_t end_bit, std::integral T>
|
||||
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
||||
constexpr T clear_bits(T value) {
|
||||
constexpr T m = mask<begin_bit, end_bit, T>();
|
||||
return value & ~m;
|
||||
}
|
||||
|
||||
/// Clears bits [begin_bit, end_bit] inclusive of value of type T.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T clear_bits(size_t begin_bit, size_t end_bit, T value) {
|
||||
const T m = mask<T>(begin_bit, end_bit);
|
||||
return value & ~m;
|
||||
}
|
||||
|
||||
/// Modifies bits [begin_bit, end_bit] inclusive of value of type T.
|
||||
template<size_t begin_bit, size_t end_bit, std::integral T>
|
||||
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
||||
constexpr T set_bits(T value, T new_bits) {
|
||||
constexpr T m = mask<begin_bit, end_bit, T>();
|
||||
return (value & ~m) | ((new_bits << begin_bit) & m);
|
||||
}
|
||||
|
||||
/// Modifies bits [begin_bit, end_bit] inclusive of value of type T.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T set_bits(size_t begin_bit, size_t end_bit, T value, T new_bits) {
|
||||
const T m = mask<T>(begin_bit, end_bit);
|
||||
return (value & ~m) | ((new_bits << begin_bit) & m);
|
||||
}
|
||||
|
||||
/// Extract bit at bit_position from value of type T.
|
||||
template<size_t bit_position, std::integral T>
|
||||
template<size_t bit_position, BitIntegral T>
|
||||
constexpr bool get_bit(T value) {
|
||||
constexpr T m = mask<bit_position, bit_position, T>();
|
||||
return (value & m) != 0;
|
||||
}
|
||||
|
||||
/// Extract bit at bit_position from value of type T.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr bool get_bit(size_t bit_position, T value) {
|
||||
const T m = mask<T>(bit_position, bit_position);
|
||||
return (value & m) != 0;
|
||||
}
|
||||
|
||||
/// Clears bit at bit_position of value of type T.
|
||||
template<size_t bit_position, std::integral T>
|
||||
template<size_t bit_position, BitIntegral T>
|
||||
constexpr T clear_bit(T value) {
|
||||
constexpr T m = mask<bit_position, bit_position, T>();
|
||||
return value & ~m;
|
||||
}
|
||||
|
||||
/// Clears bit at bit_position of value of type T.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T clear_bit(size_t bit_position, T value) {
|
||||
const T m = mask<T>(bit_position, bit_position);
|
||||
return value & ~m;
|
||||
}
|
||||
|
||||
/// Modifies bit at bit_position of value of type T.
|
||||
template<size_t bit_position, std::integral T>
|
||||
template<size_t bit_position, BitIntegral T>
|
||||
constexpr T set_bit(T value, bool new_bit) {
|
||||
constexpr T m = mask<bit_position, bit_position, T>();
|
||||
return (value & ~m) | (new_bit ? m : T(0));
|
||||
return (value & ~m) | (new_bit ? m : static_cast<T>(0));
|
||||
}
|
||||
|
||||
/// Modifies bit at bit_position of value of type T.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T set_bit(size_t bit_position, T value, bool new_bit) {
|
||||
const T m = mask<T>(bit_position, bit_position);
|
||||
return (value & ~m) | (new_bit ? m : T(0));
|
||||
return (value & ~m) | (new_bit ? m : static_cast<T>(0));
|
||||
}
|
||||
|
||||
/// Sign-extends a value that has bit_count bits to the full bitwidth of type T.
|
||||
template<size_t bit_count, std::integral T>
|
||||
template<size_t bit_count, BitIntegral T>
|
||||
constexpr T sign_extend(T value) {
|
||||
static_assert(bit_count != 0, "cannot sign-extend zero-sized value");
|
||||
|
||||
using S = std::make_signed_t<T>;
|
||||
constexpr size_t shift_amount = bitsizeof<T> - bit_count;
|
||||
return T(S(value << shift_amount) >> shift_amount);
|
||||
return static_cast<T>(static_cast<S>(value << shift_amount) >> shift_amount);
|
||||
}
|
||||
|
||||
/// Sign-extends a value that has bit_count bits to the full bitwidth of type T.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T sign_extend(size_t bit_count, T value) {
|
||||
ASSERT(bit_count != 0 && "cannot sign-extend zero-sized value");
|
||||
using S = std::make_signed_t<T>;
|
||||
@@ -201,42 +234,78 @@ constexpr T sign_extend(size_t bit_count, T value) {
|
||||
}
|
||||
|
||||
/// Replicate an element across a value of type T.
|
||||
template<size_t element_size, std::integral T>
|
||||
template<size_t element_size, BitIntegral T>
|
||||
constexpr T replicate_element(T value) {
|
||||
static_assert(element_size <= bitsizeof<T>, "element_size is too large");
|
||||
static_assert(bitsizeof<T> % element_size == 0, "bitsize of T not divisible by element_size");
|
||||
|
||||
if constexpr (element_size == bitsizeof<T>) {
|
||||
return value;
|
||||
} else {
|
||||
return replicate_element<element_size * 2, T>(T(value | (value << element_size)));
|
||||
return replicate_element<element_size * 2, T>(static_cast<T>(value | (value << element_size)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Replicate an element of type U across a value of type T.
|
||||
template<std::integral U, std::integral T>
|
||||
template<BitIntegral U, BitIntegral T>
|
||||
constexpr T replicate_element(T value) {
|
||||
static_assert(bitsizeof<U> <= bitsizeof<T>, "element_size is too large");
|
||||
|
||||
return replicate_element<bitsizeof<U>, T>(value);
|
||||
}
|
||||
|
||||
/// Replicate an element across a value of type T.
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr T replicate_element(size_t element_size, T value) {
|
||||
ASSERT(element_size <= bitsizeof<T> && "element_size is too large");
|
||||
ASSERT(bitsizeof<T> % element_size == 0 && "bitsize of T not divisible by element_size");
|
||||
if (element_size == bitsizeof<T>)
|
||||
return value;
|
||||
return replicate_element<T>(element_size * 2, T(value | (value << element_size)));
|
||||
return replicate_element<T>(element_size * 2, static_cast<T>(value | (value << element_size)));
|
||||
}
|
||||
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
constexpr bool most_significant_bit(T value) {
|
||||
return get_bit<bitsizeof<T> - 1, T>(value);
|
||||
}
|
||||
|
||||
template<std::integral T>
|
||||
template<BitIntegral T>
|
||||
inline size_t count_ones(T x) {
|
||||
return std::bitset<bitsizeof<T>>(x).count();
|
||||
}
|
||||
|
||||
template<BitIntegral T>
|
||||
constexpr size_t count_leading_zeros(T x) {
|
||||
size_t result = bitsizeof<T>;
|
||||
while (x != 0) {
|
||||
x >>= 1;
|
||||
result--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<BitIntegral T>
|
||||
constexpr int highest_set_bit(T x) {
|
||||
return std::bit_width(x) - 1;
|
||||
int result = -1;
|
||||
while (x != 0) {
|
||||
x >>= 1;
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<BitIntegral T>
|
||||
constexpr size_t lowest_set_bit(T x) {
|
||||
if (x == 0) {
|
||||
return bitsizeof<T>;
|
||||
}
|
||||
|
||||
size_t result = 0;
|
||||
while ((x & 1) == 0) {
|
||||
x >>= 1;
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace mcl::bit
|
||||
|
||||
@@ -17,25 +17,25 @@ struct integer_of_size_impl {};
|
||||
template<>
|
||||
struct integer_of_size_impl<8> {
|
||||
using unsigned_type = u8;
|
||||
using signed_type = std::make_signed_t<unsigned_type>;
|
||||
using signed_type = s8;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct integer_of_size_impl<16> {
|
||||
using unsigned_type = u16;
|
||||
using signed_type = std::make_signed_t<unsigned_type>;
|
||||
using signed_type = s16;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct integer_of_size_impl<32> {
|
||||
using unsigned_type = u32;
|
||||
using signed_type = std::make_signed_t<unsigned_type>;
|
||||
using signed_type = s32;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct integer_of_size_impl<64> {
|
||||
using unsigned_type = u64;
|
||||
using signed_type = std::make_signed_t<unsigned_type>;
|
||||
using signed_type = s64;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -10,12 +10,23 @@
|
||||
|
||||
namespace mcl {
|
||||
|
||||
/// A metavalue (of type VT and value v).
|
||||
template<class VT, VT v> using value = std::integral_constant<VT, v>;
|
||||
/// A metavalue of type size_t (and value v).
|
||||
template<size_t v> using size_value = value<size_t, v>;
|
||||
/// A metavalue of type bool (and value v). (Aliases to std::bool_constant.)
|
||||
template<bool v> using bool_value = value<bool, v>;
|
||||
/// true metavalue (Aliases to std::true_type).
|
||||
using true_type = bool_value<true>;
|
||||
/// false metavalue (Aliases to std::false_type).
|
||||
using false_type = bool_value<false>;
|
||||
|
||||
/// Is type T an instance of template class C?
|
||||
template<template<class...> class, class>
|
||||
struct is_instance_of_template : std::false_type {};
|
||||
struct is_instance_of_template : false_type {};
|
||||
|
||||
template<template<class...> class C, class... As>
|
||||
struct is_instance_of_template<C, C<As...>> : std::true_type {};
|
||||
struct is_instance_of_template<C, C<As...>> : true_type {};
|
||||
|
||||
/// Is type T an instance of template class C?
|
||||
template<template<class...> class C, class T>
|
||||
|
||||
@@ -665,7 +665,7 @@ TEST_CASE("A32: Test thumb IT instruction", "[thumb]") {
|
||||
A32::ITState it_state = [&] {
|
||||
while (true) {
|
||||
const u16 imm8 = RandInt<u16>(0, 0xFF);
|
||||
if (mcl::bit::get_bits<0, 3>(imm8) == 0b0000 || mcl::bit::get_bits<4, 7>(imm8) == 0b1111 || (mcl::bit::get_bits<4, 7>(imm8) == 0b1110 && std::popcount(mcl::bit::get_bits<0, 3>(imm8)) != 1)) {
|
||||
if (mcl::bit::get_bits<0, 3>(imm8) == 0b0000 || mcl::bit::get_bits<4, 7>(imm8) == 0b1111 || (mcl::bit::get_bits<4, 7>(imm8) == 0b1110 && mcl::bit::count_ones(mcl::bit::get_bits<0, 3>(imm8)) != 1)) {
|
||||
continue;
|
||||
}
|
||||
instructions.push_back(0b1011111100000000 | imm8);
|
||||
|
||||
@@ -486,17 +486,27 @@ void TouchResource::ReadTouchInput() {
|
||||
SanitizeInput(current_touch_state);
|
||||
|
||||
std::scoped_lock lock{*input_mutex};
|
||||
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; !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 (current_touch_state.entry_count == previous_touch_state.entry_count) {
|
||||
if (current_touch_state.entry_count < 1) {
|
||||
return;
|
||||
}
|
||||
if (has_moved) {
|
||||
input_event->Signal();
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (!has_moved) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
input_event->Signal();
|
||||
}
|
||||
|
||||
void TouchResource::OnTouchUpdate(s64 timestamp) {
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
#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"
|
||||
@@ -87,37 +90,8 @@ 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__)
|
||||
id layer = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
|
||||
wsi.render_surface = reinterpret_cast<void* (*)(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);
|
||||
@@ -240,7 +214,7 @@ void Init(QWidget* root) {
|
||||
LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version);
|
||||
LogRuntimes();
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
const auto& caps = Common::g_cpu_caps;
|
||||
const auto& caps = Common::GetCPUCaps();
|
||||
std::string cpu_string = caps.cpu_string;
|
||||
if (caps.avx || caps.avx2 || caps.avx512f) {
|
||||
cpu_string += " | AVX";
|
||||
|
||||
@@ -462,13 +462,6 @@ 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,10 +14,37 @@
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsNonUniformDescriptor(EmitContext& ctx, const IR::Value& index) noexcept {
|
||||
return ctx.profile.support_sampled_image_array_nonuniform_indexing && !index.IsImmediate();
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
class ImageOperands {
|
||||
public:
|
||||
@@ -194,13 +221,11 @@ 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) {
|
||||
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 DescriptorIndex idx{ctx, index};
|
||||
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
|
||||
idx.Decorate(ctx, pointer);
|
||||
const Id object{ctx.OpLoad(def.sampled_type, pointer)};
|
||||
idx.Decorate(ctx, object);
|
||||
return object;
|
||||
} else {
|
||||
return ctx.OpLoad(def.sampled_type, def.id);
|
||||
@@ -219,14 +244,13 @@ 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) {
|
||||
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);
|
||||
const DescriptorIndex idx{ctx, index};
|
||||
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
|
||||
idx.Decorate(ctx, ptr);
|
||||
const Id object{ctx.OpLoad(def.sampled_type, ptr)};
|
||||
idx.Decorate(ctx, object);
|
||||
const Id image{ctx.OpImage(def.image_type, object)};
|
||||
idx.Decorate(ctx, image);
|
||||
return image;
|
||||
}
|
||||
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id));
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <array>
|
||||
|
||||
#include <sirit/sirit.h>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "shader_recompiler/backend/bindings.h"
|
||||
#include "shader_recompiler/frontend/ir/program.h"
|
||||
@@ -367,9 +366,6 @@ 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,8 +733,9 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
|
||||
break;
|
||||
}
|
||||
u32 index;
|
||||
u32 size_shift = cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset) : DESCRIPTOR_SIZE_SHIFT;
|
||||
u32 count = cbuf.count;
|
||||
const 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 * u32(draw_state.index_buffer.FormatSizeInBytes());
|
||||
const u32 new_offset = offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
|
||||
runtime.BindIndexBuffer(buffer, new_offset, size);
|
||||
} else {
|
||||
buffer.MarkUsage(offset, size);
|
||||
@@ -1252,7 +1252,8 @@ 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) * u32(index_buffer_ref.FormatSizeInBytes());
|
||||
const u32 draw_size =
|
||||
(index_buffer_ref.count + index_buffer_ref.first) * index_buffer_ref.FormatSizeInBytes();
|
||||
const u32 size = (std::min)(address_size, draw_size);
|
||||
if (size == 0 || !device_addr) {
|
||||
channel_state->index_buffer = NULL_BINDING;
|
||||
@@ -1619,38 +1620,24 @@ 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 staging_offset = 0;
|
||||
u64 total_size_bytes = 0;
|
||||
u64 largest_copy = 0;
|
||||
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
|
||||
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,
|
||||
});
|
||||
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);
|
||||
total_size_bytes += range_size;
|
||||
largest_copy = (std::max)(largest_copy, range_size);
|
||||
});
|
||||
if (upload_copies.empty()) {
|
||||
if (total_size_bytes == 0) {
|
||||
return true;
|
||||
}
|
||||
UploadMemory(buffer, staging_offset, largest_copy, std::span(upload_copies));
|
||||
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;
|
||||
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]) {
|
||||
if (!subchannel->execution_mask[dma_state.method]) {
|
||||
subchannel->method_sink.emplace_back(dma_state.method, argument);
|
||||
} else {
|
||||
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,20 +270,31 @@ u32 Maxwell3D::GetMaxCurrentVertices() {
|
||||
size_t Maxwell3D::EstimateIndexBufferSize() {
|
||||
GPUVAddr start_address = regs.index_buffer.StartAddress();
|
||||
GPUVAddr end_address = regs.index_buffer.EndAddress();
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
|
||||
// Keep track of the register value in shadow_state when requested.
|
||||
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)
|
||||
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) {
|
||||
return shadow_state.reg_array[method];
|
||||
}
|
||||
return argument;
|
||||
}
|
||||
|
||||
@@ -306,8 +317,10 @@ void Maxwell3D::ConsumeSinkImpl() {
|
||||
|
||||
void Maxwell3D::ProcessDirtyRegisters(u32 method, u32 argument) {
|
||||
regs.reg_array[method] = argument;
|
||||
for (auto const& table : dirty.tables)
|
||||
|
||||
for (const auto& 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;
|
||||
|
||||
size_t FormatSizeInBytes() const {
|
||||
unsigned FormatSizeInBytes() const {
|
||||
switch (format) {
|
||||
case IndexFormat::UnsignedByte:
|
||||
return 1;
|
||||
@@ -2224,7 +2224,7 @@ public:
|
||||
case IndexFormat::UnsignedInt:
|
||||
return 4;
|
||||
}
|
||||
UNREACHABLE();
|
||||
ASSERT(false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -3148,9 +3148,9 @@ public:
|
||||
}
|
||||
|
||||
struct DirtyState {
|
||||
using Flags = std::bitset<(std::numeric_limits<u8>::max)() + 1>;
|
||||
using Flags = std::bitset<(std::numeric_limits<u8>::max)()>;
|
||||
using Table = std::array<u8, Regs::NUM_REGS>;
|
||||
using Tables = std::array<std::array<u8, Regs::NUM_REGS>, 2>;
|
||||
using Tables = std::array<Table, 2>;
|
||||
|
||||
Flags flags;
|
||||
Tables tables{};
|
||||
|
||||
@@ -39,7 +39,7 @@ extern "C" {
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
#if defined(ARCHITECTURE_x86_64)
|
||||
#include "common/cpu_features.h"
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
|
||||
#if defined(ARCHITECTURE_x86_64) \
|
||||
@@ -55,8 +55,9 @@ namespace Tegra::Host1x {
|
||||
namespace {
|
||||
|
||||
static bool HasSSE41() {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
return Common::g_cpu_caps.sse4_1;
|
||||
#if defined(ARCHITECTURE_x86_64)
|
||||
static bool has_sse41 = Common::GetCPUCaps().sse4_1;
|
||||
return has_sse41;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
||||
@@ -90,7 +90,7 @@ void ComputePipeline::Configure() {
|
||||
desc.is_written);
|
||||
++ssbo_index;
|
||||
}
|
||||
texture_cache.SynchronizeDescriptors(true);
|
||||
texture_cache.SynchronizeComputeDescriptors();
|
||||
|
||||
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.GetSamplerId(handle.second, true);
|
||||
VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second);
|
||||
samplers.push_back(sampler);
|
||||
}
|
||||
}
|
||||
for (const auto& desc : info.image_descriptors) {
|
||||
add_image(desc, desc.is_written);
|
||||
}
|
||||
texture_cache.FillImageViews(std::span(views.data(), views.size()), true);
|
||||
texture_cache.FillComputeImageViews(std::span(views.data(), views.size()));
|
||||
|
||||
if (!is_built) {
|
||||
WaitForBuild();
|
||||
|
||||
@@ -283,7 +283,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
size_t views_index{};
|
||||
size_t samplers_index{};
|
||||
|
||||
texture_cache.SynchronizeDescriptors(false);
|
||||
texture_cache.SynchronizeGraphicsDescriptors();
|
||||
|
||||
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.GetSamplerId(handle.second, false)};
|
||||
VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)};
|
||||
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.FillImageViews(std::span(views.data(), views_index), false, Spec::has_images);
|
||||
texture_cache.FillGraphicsImageViews<Spec::has_images>(std::span(views.data(), views_index));
|
||||
|
||||
texture_cache.UpdateRenderTargets(false);
|
||||
state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle());
|
||||
|
||||
@@ -353,13 +353,13 @@ void RasterizerOpenGL::DrawTexture() {
|
||||
gpu.TickWork();
|
||||
};
|
||||
|
||||
texture_cache.SynchronizeDescriptors(false);
|
||||
texture_cache.SynchronizeGraphicsDescriptors();
|
||||
texture_cache.UpdateRenderTargets(false);
|
||||
|
||||
SyncState();
|
||||
|
||||
const auto& draw_texture_state = maxwell3d->draw_manager.draw_texture_state;
|
||||
const auto& sampler = texture_cache.GetSampler(draw_texture_state.src_sampler, true);
|
||||
const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler);
|
||||
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.SynchronizeDescriptors(true);
|
||||
texture_cache.SynchronizeComputeDescriptors();
|
||||
|
||||
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.GetSamplerId(handle.second, true);
|
||||
VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second);
|
||||
samplers.push_back(sampler);
|
||||
}
|
||||
}
|
||||
for (const auto& desc : info.image_descriptors) {
|
||||
add_image(desc, desc.is_written);
|
||||
}
|
||||
texture_cache.FillImageViews(std::span(views.data(), views.size()), true);
|
||||
texture_cache.FillComputeImageViews(std::span(views.data(), views.size()));
|
||||
|
||||
buffer_cache.UnbindComputeTextureBuffers();
|
||||
size_t index{};
|
||||
|
||||
@@ -49,6 +49,7 @@ 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};
|
||||
@@ -313,12 +314,12 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) {
|
||||
|
||||
template <typename Spec>
|
||||
bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
boost::container::small_vector<VideoCommon::ImageViewInOut, 64> views;
|
||||
boost::container::small_vector<VideoCommon::SamplerId, 64> samplers;
|
||||
small_vector<VideoCommon::ImageViewInOut, INLINE_IMAGE_ELEMENTS> views;
|
||||
small_vector<VideoCommon::SamplerId, INLINE_IMAGE_ELEMENTS> samplers;
|
||||
views.reserve(num_image_elements);
|
||||
samplers.reserve(num_textures);
|
||||
|
||||
texture_cache.SynchronizeDescriptors(false);
|
||||
texture_cache.SynchronizeGraphicsDescriptors();
|
||||
|
||||
buffer_cache.SetUniformBuffersState(enabled_uniform_buffer_masks, &uniform_buffer_sizes);
|
||||
|
||||
@@ -383,7 +384,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
const auto handle{read_handle(desc, index)};
|
||||
views.push_back({handle.first});
|
||||
|
||||
VideoCommon::SamplerId sampler{texture_cache.GetSamplerId(handle.second, false)};
|
||||
VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)};
|
||||
samplers.push_back(sampler);
|
||||
}
|
||||
}
|
||||
@@ -412,7 +413,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
}
|
||||
ASSERT(views.size() == num_image_elements);
|
||||
ASSERT(samplers.size() == num_textures);
|
||||
texture_cache.FillImageViews(std::span(views.data(), views.size()), false, Spec::has_images);
|
||||
texture_cache.FillGraphicsImageViews<Spec::has_images>(std::span(views.data(), views.size()));
|
||||
|
||||
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.SynchronizeDescriptors(false);
|
||||
texture_cache.SynchronizeGraphicsDescriptors();
|
||||
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.GetSampler(draw_texture_state.src_sampler, false);
|
||||
const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler);
|
||||
const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture);
|
||||
const auto* framebuffer = texture_cache.GetFramebuffer();
|
||||
|
||||
|
||||
@@ -267,7 +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_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
|
||||
upload_cmdbuf.End();
|
||||
cmdbuf.End();
|
||||
|
||||
|
||||
@@ -24,8 +24,11 @@ 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;
|
||||
|
||||
Maxwell3D::DirtyState::Flags MakeInvalidationFlags() {
|
||||
Flags MakeInvalidationFlags() {
|
||||
static constexpr int INVALIDATION_FLAGS[]{
|
||||
Viewports,
|
||||
Scissors,
|
||||
@@ -65,7 +68,7 @@ Maxwell3D::DirtyState::Flags MakeInvalidationFlags() {
|
||||
LineStippleEnable,
|
||||
LineStippleParams,
|
||||
};
|
||||
Maxwell3D::DirtyState::Flags flags{};
|
||||
Flags flags{};
|
||||
for (const int flag : INVALIDATION_FLAGS) {
|
||||
flags[flag] = true;
|
||||
}
|
||||
@@ -81,7 +84,7 @@ Maxwell3D::DirtyState::Flags MakeInvalidationFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
void SetupDirtyViewports(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyViewports(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);
|
||||
@@ -89,26 +92,26 @@ void SetupDirtyViewports(Maxwell3D::DirtyState::Tables& tables) {
|
||||
tables[1][OFF(window_origin)] = Viewports;
|
||||
}
|
||||
|
||||
void SetupDirtyScissors(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyScissors(Tables& tables) {
|
||||
FillBlock(tables[0], OFF(scissor_test), NUM(scissor_test), Scissors);
|
||||
}
|
||||
|
||||
void SetupDirtyDepthBias(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyDepthBias(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(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyBlendConstants(Tables& tables) {
|
||||
FillBlock(tables[0], OFF(blend_color), NUM(blend_color), BlendConstants);
|
||||
}
|
||||
|
||||
void SetupDirtyDepthBounds(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyDepthBounds(Tables& tables) {
|
||||
FillBlock(tables[0], OFF(depth_bounds), NUM(depth_bounds), DepthBounds);
|
||||
}
|
||||
|
||||
void SetupDirtyStencilProperties(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyStencilProperties(Tables& tables) {
|
||||
const auto setup = [&](size_t position, u8 flag) {
|
||||
tables[0][position] = flag;
|
||||
tables[1][position] = StencilProperties;
|
||||
@@ -122,18 +125,18 @@ void SetupDirtyStencilProperties(Maxwell3D::DirtyState::Tables& tables) {
|
||||
setup(OFF(stencil_back_func_mask), StencilCompare);
|
||||
}
|
||||
|
||||
void SetupDirtyLineWidth(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyLineWidth(Tables& tables) {
|
||||
tables[0][OFF(line_width_smooth)] = LineWidth;
|
||||
tables[0][OFF(line_width_aliased)] = LineWidth;
|
||||
}
|
||||
|
||||
void SetupDirtyCullMode(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyCullMode(Tables& tables) {
|
||||
auto& table = tables[0];
|
||||
table[OFF(gl_cull_face)] = CullMode;
|
||||
table[OFF(gl_cull_test_enabled)] = CullMode;
|
||||
}
|
||||
|
||||
void SetupDirtyStateEnable(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyStateEnable(Tables& tables) {
|
||||
const auto setup = [&](size_t position, u8 flag) {
|
||||
tables[0][position] = flag;
|
||||
tables[1][position] = StateEnable;
|
||||
@@ -154,17 +157,17 @@ void SetupDirtyStateEnable(Maxwell3D::DirtyState::Tables& tables) {
|
||||
setup(OFF(anti_alias_alpha_control.alpha_to_one), AlphaToOneEnable);
|
||||
}
|
||||
|
||||
void SetupDirtyDepthCompareOp(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyDepthCompareOp(Tables& tables) {
|
||||
tables[0][OFF(depth_test_func)] = DepthCompareOp;
|
||||
}
|
||||
|
||||
void SetupDirtyFrontFace(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyFrontFace(Tables& tables) {
|
||||
auto& table = tables[0];
|
||||
table[OFF(gl_front_face)] = FrontFace;
|
||||
table[OFF(window_origin)] = FrontFace;
|
||||
}
|
||||
|
||||
void SetupDirtyStencilOp(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyStencilOp(Tables& tables) {
|
||||
auto& table = tables[0];
|
||||
table[OFF(stencil_front_op.fail)] = StencilOp;
|
||||
table[OFF(stencil_front_op.zfail)] = StencilOp;
|
||||
@@ -179,7 +182,7 @@ void SetupDirtyStencilOp(Maxwell3D::DirtyState::Tables& tables) {
|
||||
tables[1][OFF(stencil_two_side_enable)] = StencilOp;
|
||||
}
|
||||
|
||||
void SetupDirtyBlending(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyBlending(Tables& tables) {
|
||||
tables[0][OFF(color_mask_common)] = Blending;
|
||||
tables[1][OFF(color_mask_common)] = ColorMask;
|
||||
tables[0][OFF(blend_per_target_enabled)] = Blending;
|
||||
@@ -193,11 +196,11 @@ void SetupDirtyBlending(Maxwell3D::DirtyState::Tables& tables) {
|
||||
FillBlock(tables[1], OFF(blend_per_target), NUM(blend_per_target), BlendEquations);
|
||||
}
|
||||
|
||||
void SetupDirtySpecialOps(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtySpecialOps(Tables& tables) {
|
||||
tables[0][OFF(logic_op.op)] = LogicOp;
|
||||
}
|
||||
|
||||
void SetupDirtyViewportSwizzles(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyViewportSwizzles(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] =
|
||||
@@ -205,7 +208,7 @@ void SetupDirtyViewportSwizzles(Maxwell3D::DirtyState::Tables& tables) {
|
||||
}
|
||||
}
|
||||
|
||||
void SetupDirtyVertexAttributes(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyVertexAttributes(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);
|
||||
@@ -213,7 +216,7 @@ void SetupDirtyVertexAttributes(Maxwell3D::DirtyState::Tables& tables) {
|
||||
FillBlock(tables[1], OFF(vertex_attrib_format), Regs::NumVertexAttributes, VertexInput);
|
||||
}
|
||||
|
||||
void SetupDirtyVertexBindings(Maxwell3D::DirtyState::Tables& tables) {
|
||||
void SetupDirtyVertexBindings(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) {
|
||||
@@ -225,7 +228,7 @@ void SetupDirtyVertexBindings(Maxwell3D::DirtyState::Tables& tables) {
|
||||
}
|
||||
}
|
||||
|
||||
void SetupRasterModes(Maxwell3D::DirtyState::Tables &tables) {
|
||||
void SetupRasterModes(Tables &tables) {
|
||||
auto& table = tables[0];
|
||||
|
||||
table[OFF(line_stipple_params)] = LineStippleParams;
|
||||
|
||||
@@ -254,12 +254,11 @@ 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,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
@@ -9,39 +6,37 @@
|
||||
#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 T>
|
||||
template <typename Descriptor>
|
||||
class DescriptorTable {
|
||||
public:
|
||||
[[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;
|
||||
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;
|
||||
}
|
||||
|
||||
void Invalidate() noexcept {
|
||||
std::ranges::fill(read_descriptors, 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<T, bool> Read(Tegra::MemoryManager const& gpu_memory, u32 index) noexcept {
|
||||
[[nodiscard]] std::pair<Descriptor, bool> Read(u32 index) {
|
||||
DEBUG_ASSERT(index <= current_limit);
|
||||
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) {
|
||||
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)) {
|
||||
result.second = result.first != descriptors[index];
|
||||
} else {
|
||||
read_descriptors[index / 64] |= 1ULL << (index % 64);
|
||||
MarkDescriptorAsRead(index);
|
||||
result.second = true;
|
||||
}
|
||||
if (result.second) {
|
||||
@@ -50,24 +45,34 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
void Refresh(GPUVAddr gpu_addr, u32 limit) noexcept {
|
||||
[[nodiscard]] u32 Limit() const noexcept {
|
||||
return current_limit;
|
||||
}
|
||||
|
||||
private:
|
||||
void Refresh(GPUVAddr gpu_addr, u32 limit) {
|
||||
current_gpu_addr = gpu_addr;
|
||||
current_limit = limit;
|
||||
// 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);
|
||||
//
|
||||
|
||||
const size_t num_descriptors = static_cast<size_t>(limit) + 1;
|
||||
read_descriptors.clear();
|
||||
read_descriptors.resize(Common::DivCeil(num_descriptors, 64U), 0);
|
||||
descriptors.resize(num_descriptors);
|
||||
}
|
||||
|
||||
std::vector<u64> read_descriptors;
|
||||
std::vector<T> 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;
|
||||
GPUVAddr current_gpu_addr{};
|
||||
u32 current_limit{};
|
||||
std::vector<u64> read_descriptors;
|
||||
std::vector<Descriptor> descriptors;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
@@ -10,8 +7,8 @@
|
||||
namespace VideoCommon {
|
||||
|
||||
TextureCacheChannelInfo::TextureCacheChannelInfo(Tegra::Control::ChannelState& state) noexcept
|
||||
: ChannelInfo(state)
|
||||
{}
|
||||
: ChannelInfo(state), graphics_image_table{gpu_memory}, graphics_sampler_table{gpu_memory},
|
||||
compute_image_table{gpu_memory}, compute_sampler_table{gpu_memory} {}
|
||||
|
||||
template class VideoCommon::ChannelSetupCaches<VideoCommon::TextureCacheChannelInfo>;
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#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"
|
||||
@@ -205,8 +204,8 @@ typename P::ImageView& TextureCache<P>::GetImageView(ImageViewId id) noexcept {
|
||||
|
||||
template <class P>
|
||||
typename P::ImageView& TextureCache<P>::GetImageView(u32 index) noexcept {
|
||||
// Not compute!
|
||||
const auto image_view_id = VisitImageView(index, false);
|
||||
const auto image_view_id = VisitImageView(channel_state->graphics_image_table,
|
||||
channel_state->graphics_image_view_ids, index);
|
||||
return slot_image_views[image_view_id];
|
||||
}
|
||||
|
||||
@@ -216,25 +215,16 @@ void TextureCache<P>::MarkModification(ImageId id) noexcept {
|
||||
}
|
||||
|
||||
template <class P>
|
||||
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 <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);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -302,25 +292,41 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) {
|
||||
}
|
||||
|
||||
template <class P>
|
||||
typename P::Sampler* TextureCache<P>::GetSampler(u32 index, bool compute) {
|
||||
return &slot_samplers[GetSamplerId(index, compute)];
|
||||
typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) {
|
||||
return &slot_samplers[GetGraphicsSamplerId(index)];
|
||||
}
|
||||
|
||||
template <class P>
|
||||
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) {
|
||||
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()) {
|
||||
LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index);
|
||||
return NULL_SAMPLER_ID;
|
||||
}
|
||||
auto const map_index = index | (compute ? Common::SlotId::TAGGED_VALUE : 0);
|
||||
auto const [descriptor, is_new] = table.Read(*gpu_memory, index);
|
||||
const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index);
|
||||
SamplerId& id = channel_state->graphics_sampler_ids[index];
|
||||
if (is_new) {
|
||||
auto const id = FindSampler(descriptor, compute);
|
||||
channel_state->sampler_ids.insert_or_assign(map_index, id);
|
||||
return id;
|
||||
id = FindSampler(descriptor);
|
||||
}
|
||||
return channel_state->sampler_ids.find(map_index)->second;
|
||||
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;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -334,31 +340,45 @@ typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) noexcept {
|
||||
}
|
||||
|
||||
template <class P>
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,32 +557,47 @@ typename P::Framebuffer* TextureCache<P>::GetFramebuffer() {
|
||||
}
|
||||
|
||||
template <class P>
|
||||
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) {
|
||||
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()) {
|
||||
LOG_DEBUG(HW_GPU, "Invalid image view index={}", index);
|
||||
return NULL_IMAGE_VIEW_ID;
|
||||
}
|
||||
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);
|
||||
const auto [descriptor, is_new] = table.Read(index);
|
||||
ImageViewId& image_view_id = cached_image_view_ids[index];
|
||||
if (is_new) {
|
||||
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;
|
||||
image_view_id = FindImageView(descriptor);
|
||||
}
|
||||
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;
|
||||
if (image_view_id != NULL_IMAGE_VIEW_ID) {
|
||||
PrepareImageView(image_view_id, false, false);
|
||||
}
|
||||
return image_view_id;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -1161,6 +1196,19 @@ 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);
|
||||
@@ -1302,10 +1350,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)
|
||||
for (auto& e : channel_info.image_view_ids)
|
||||
e.second = CORRUPT_ID;
|
||||
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);
|
||||
}
|
||||
channel_info.graphics_image_table.Invalidate();
|
||||
channel_info.compute_image_table.Invalidate();
|
||||
}
|
||||
@@ -1870,7 +1918,7 @@ std::pair<u32, u32> TextureCache<P>::PrepareDmaImage(ImageId dst_id, GPUVAddr ba
|
||||
}
|
||||
|
||||
template <class P>
|
||||
SamplerId TextureCache<P>::FindSampler(const TSCEntry& config, bool compute) {
|
||||
SamplerId TextureCache<P>::FindSampler(const TSCEntry& config) {
|
||||
if (std::ranges::all_of(config.raw, [](u64 value) { return value == 0; })) {
|
||||
return NULL_SAMPLER_ID;
|
||||
}
|
||||
@@ -1893,48 +1941,69 @@ std::optional<size_t> TextureCache<P>::QuerySamplerBudget() const {
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::EnforceSamplerBudget() {
|
||||
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);
|
||||
const auto budget = QuerySamplerBudget();
|
||||
if (!budget) {
|
||||
return;
|
||||
}
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
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 (removed != 0) {
|
||||
LOG_WARNING(HW_GPU, "Sampler cache exceeded {} entries on this driver; reclaimed {} inactive samplers", budget, removed);
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2174,7 +2243,8 @@ 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;
|
||||
}
|
||||
@@ -2434,9 +2504,10 @@ 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)
|
||||
for (auto& e : channel_info.image_view_ids)
|
||||
e.second = CORRUPT_ID;
|
||||
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);
|
||||
}
|
||||
channel_info.graphics_image_table.Invalidate();
|
||||
channel_info.compute_image_table.Invalidate();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#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"
|
||||
@@ -77,20 +76,22 @@ public:
|
||||
TextureCacheChannelInfo(const TextureCacheChannelInfo& state) = delete;
|
||||
TextureCacheChannelInfo& operator=(const TextureCacheChannelInfo&) = delete;
|
||||
|
||||
DescriptorTable<TICEntry> graphics_image_table;
|
||||
DescriptorTable<TSCEntry> graphics_sampler_table;
|
||||
DescriptorTable<TICEntry> compute_image_table;
|
||||
DescriptorTable<TSCEntry> compute_sampler_table;
|
||||
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;
|
||||
|
||||
// TODO: still relies on bad iterators :(
|
||||
std::unordered_map<TICEntry, ImageViewId> image_views;
|
||||
std::unordered_map<TSCEntry, SamplerId> samplers;
|
||||
|
||||
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;
|
||||
TextureCacheGPUMap* gpu_page_table;
|
||||
TextureCacheGPUMap* sparse_page_table;
|
||||
};
|
||||
|
||||
template <class P>
|
||||
@@ -166,17 +167,27 @@ public:
|
||||
/// Mark an image as modified from the GPU
|
||||
void MarkModification(ImageId id) noexcept;
|
||||
|
||||
/// Fill image_view_ids with the graphics/compute images in indices
|
||||
void FillImageViews(std::span<ImageViewInOut> views, bool compute, bool blacklist = true);
|
||||
/// 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);
|
||||
|
||||
/// Handle feedback loops during draws.
|
||||
void CheckFeedbackLoop(std::span<const ImageViewInOut> views);
|
||||
|
||||
/// Get the sampler from the graphics/compute descriptor table in the specified index
|
||||
Sampler* GetSampler(u32 index, bool compute);
|
||||
/// Get the sampler from the graphics descriptor table in the specified index
|
||||
Sampler* GetGraphicsSampler(u32 index);
|
||||
|
||||
/// Get the sampler id from the graphics/compute descriptor table in the specified index
|
||||
SamplerId GetSamplerId(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);
|
||||
|
||||
/// Return a constant reference to the given sampler id
|
||||
[[nodiscard]] const Sampler& GetSampler(SamplerId id) const noexcept;
|
||||
@@ -184,8 +195,11 @@ public:
|
||||
/// Return a reference to the given sampler id
|
||||
[[nodiscard]] Sampler& GetSampler(SamplerId id) noexcept;
|
||||
|
||||
/// Refresh the state for graphics/compute image view and sampler descriptors
|
||||
void SynchronizeDescriptors(bool compute);
|
||||
/// Refresh the state for graphics image view and sampler descriptors
|
||||
void SynchronizeGraphicsDescriptors();
|
||||
|
||||
/// Refresh the state for compute image view and sampler descriptors
|
||||
void SynchronizeComputeDescriptors();
|
||||
|
||||
/// Updates the Render Targets if they can be rescaled
|
||||
/// @retval True if the Render Targets have been rescaled.
|
||||
@@ -296,8 +310,15 @@ 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(u32 index, bool compute);
|
||||
ImageViewId VisitImageView(DescriptorTable<TICEntry>& table,
|
||||
std::span<ImageViewId> cached_image_view_ids, u32 index);
|
||||
|
||||
/// Find or create a framebuffer with the given render target parameters
|
||||
FramebufferId GetFramebufferId(const RenderTargets& key);
|
||||
@@ -309,6 +330,9 @@ 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);
|
||||
|
||||
@@ -336,7 +360,7 @@ private:
|
||||
const Tegra::Engines::Fermi2D::Config& copy);
|
||||
|
||||
/// Find or create a sampler from a guest descriptor sampler
|
||||
[[nodiscard]] SamplerId FindSampler(const TSCEntry& config, bool compute);
|
||||
[[nodiscard]] SamplerId FindSampler(const TSCEntry& config);
|
||||
|
||||
/// Find or create an image view for the given color buffer index
|
||||
[[nodiscard]] ImageViewId FindColorBuffer(size_t index);
|
||||
|
||||
+2
-23
@@ -15,10 +15,6 @@
|
||||
#include "qt_common/gui_settings.h"
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
#include "main_window.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -110,27 +106,10 @@ int main(int argc, char* argv[]) {
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("eden"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("eden"));
|
||||
|
||||
// Increases the maximum open file limit.
|
||||
// TODO: This should be common to all frontends.
|
||||
#ifdef _WIN32
|
||||
// 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
|
||||
// Increases the maximum open file limit to 8192
|
||||
_setmaxstdio(8192);
|
||||
#endif // __MSVCRT__
|
||||
#elif defined(__unix__) || defined(__APPLE__)
|
||||
// Set the max open file limit to 8192, or the hard limit.
|
||||
// Most sane systems should not hit the hard limit here.
|
||||
struct rlimit rl;
|
||||
if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
|
||||
rl.rlim_cur = std::min<rlim_t>(8192, rl.rlim_max);
|
||||
setrlimit(RLIMIT_NOFILE, &rl);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#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
|
||||
// the user folder in the Qt Frontend, we need to cd into that working directory
|
||||
|
||||
@@ -124,7 +124,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include "common/string_util.h"
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/cpu_features.h"
|
||||
#include "common/x64/cpu_detect.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 = g_cpu_caps;
|
||||
// const auto& caps = Common::GetCPUCaps();
|
||||
// const bool has_fma = caps.fma;
|
||||
// const auto processor_count = std::thread::hardware_concurrency();
|
||||
// const bool has_4threads = processor_count == 0 || processor_count >= 4;
|
||||
|
||||
+9
-3
@@ -17,6 +17,12 @@ namespace mcl {
|
||||
template<typename T>
|
||||
constexpr std::size_t bitsizeof = CHAR_BIT * sizeof(T);
|
||||
}
|
||||
namespace mcl::bit {
|
||||
template<typename T>
|
||||
inline size_t count_ones(T x) {
|
||||
return std::bitset<bitsizeof<T>>(x).count();
|
||||
}
|
||||
}
|
||||
template<size_t N>
|
||||
inline consteval std::array<char, N> StringToArray(const char (&str)[N + 1]) {
|
||||
std::array<char, N> result{};
|
||||
@@ -374,7 +380,7 @@ INST(arm_SRS, "SRS", "1111100--1-0110100000101000-----
|
||||
};
|
||||
// If a matcher has more bits in its mask it is more specific, so it should come first.
|
||||
std::stable_sort(list.begin(), list.end(), [](const auto& matcher1, const auto& matcher2) {
|
||||
return std::popcount(matcher1.second) > std::popcount(matcher2.second);
|
||||
return mcl::bit::count_ones(matcher1.second) > mcl::bit::count_ones(matcher2.second);
|
||||
});
|
||||
for (auto const& e : list)
|
||||
printf("%s\n", e.inst_final);
|
||||
@@ -581,7 +587,7 @@ INST(v8_VLD_single, "VLD{1-4} (single)", "111101001D10nnnnddddzzN
|
||||
});
|
||||
// If a matcher has more bits in its mask it is more specific, so it should come first.
|
||||
std::stable_sort(sort_begin, sort_end, [](const auto& a, const auto& b) {
|
||||
return std::popcount(a.second) > std::popcount(b.second);
|
||||
return mcl::bit::count_ones(a.second) > mcl::bit::count_ones(b.second);
|
||||
});
|
||||
for (auto const& e : table)
|
||||
printf("%s\n", e.inst_final);
|
||||
@@ -1622,7 +1628,7 @@ INST(FNMSUB_float, "FNMSUB", "00011
|
||||
// If a matcher has more bits in its mask it is more specific, so it should come first.
|
||||
std::stable_sort(list.begin(), list.end(), [](const auto& a, const auto& b) {
|
||||
// If a matcher has more bits in its mask it is more specific, so it should come first.
|
||||
return std::popcount(a.second) > std::popcount(b.second);
|
||||
return mcl::bit::count_ones(a.second) > mcl::bit::count_ones(b.second);
|
||||
});
|
||||
// Exceptions to the above rule of thumb.
|
||||
std::stable_partition(list.begin(), list.end(), [&](const auto& e) {
|
||||
|
||||
Reference in New Issue
Block a user