mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-14 21:04:41 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d3d37e6f4 | |||
| 5c94f8c937 | |||
| 4c38976339 | |||
| ff2da91740 | |||
| da4670edd8 | |||
| 21d488c187 | |||
| d3e604549b | |||
| 804c3bede8 | |||
| 13b6d88537 | |||
| bf38b38184 | |||
| 4192a4a7ba | |||
| e4df8475ef | |||
| 08e8f51f00 | |||
| 16319a85b0 | |||
| 5e41d3457e | |||
| ba752ba786 | |||
| 5f416b97e5 | |||
| 558bebd76a | |||
| c9cdadd7b1 | |||
| b53e9d516a | |||
| 937566d534 | |||
| 6858fcdbc9 | |||
| b7fe8f7967 | |||
| 4c1170851b | |||
| c76ee6faec | |||
| 8f90d12b56 | |||
| baec6645de | |||
| 4a737c3568 | |||
| 4e7c5f3c30 | |||
| 26ce96297c | |||
| b3cc8723c1 |
File diff suppressed because it is too large
Load Diff
@@ -35,17 +35,17 @@ set(GIT_DESC ${BUILD_VERSION})
|
||||
# Generate cpp with Git revision from template
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||
|
||||
# Auto-updater metadata! Must somewhat mirror GitHub API endpoint
|
||||
# Auto-updater metadata! Must somewhat mirror GitHub/Forgejo API endpoint
|
||||
|
||||
set(BUILD_AUTO_UPDATE_API_PATH "/latest/release.json")
|
||||
if (NIGHTLY_BUILD)
|
||||
set(BUILD_AUTO_UPDATE_WEBSITE "https://git.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_API "git.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_API_PATH "/api/v1/repos/")
|
||||
set(BUILD_AUTO_UPDATE_API "nightly.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_REPO "eden-ci/nightly")
|
||||
set(REPO_NAME "Eden Nightly")
|
||||
else()
|
||||
set(BUILD_AUTO_UPDATE_WEBSITE "https://git.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_API "git.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_API_PATH "/api/v1/repos/")
|
||||
set(BUILD_AUTO_UPDATE_API "stable.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_REPO "eden-emu/eden")
|
||||
set(REPO_NAME "Eden")
|
||||
endif()
|
||||
|
||||
+3
-2
@@ -79,7 +79,7 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c
|
||||
|
||||
On amd64:
|
||||
|
||||
* [xbyak](https://github.com/herumi/xbyak) - 7.22 or earlier is recommended
|
||||
* [xbyak](https://github.com/herumi/xbyak) - 7.35.2+
|
||||
|
||||
On aarch64 OR if `DYNARMIC_TESTS` is on:
|
||||
|
||||
@@ -161,11 +161,12 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
|
||||
<summary>Ubuntu, Debian, Mint Linux</summary>
|
||||
|
||||
```sh
|
||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
||||
```
|
||||
|
||||
* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required.
|
||||
* To enable QT Web Engine, add `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake.
|
||||
* `libxbyak-dev` is optional for x86_64 targets
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
Vendored
+7
-2
@@ -58,8 +58,13 @@
|
||||
"package": "xbyak",
|
||||
"repo": "herumi/xbyak",
|
||||
"tag": "v%VERSION%",
|
||||
"hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a",
|
||||
"git_version": "7.35.2"
|
||||
"hash": "d93971cc8f17f20818e36099aa862d15d30b60f17c7dff0cc8b7ac89ad6dc6453256dd47a454904666a85cea6ca27f9867f782c7b2c6d0c16039af8e3e28e6cc",
|
||||
"git_version": "7.35.4",
|
||||
"bundled": true,
|
||||
"skip_updates": true,
|
||||
"patches": [
|
||||
"0001-rvalue-optimize.patch"
|
||||
]
|
||||
},
|
||||
"oaknut": {
|
||||
"repo": "eden-emulator/oaknut",
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
|
||||
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
|
||||
FIX_BLOOM_EFFECTS("fix_bloom_effects"),
|
||||
EMULATE_BGR565("emulate_bgr565"),
|
||||
CPUOPT_UNSAFE_HOST_MMU("cpuopt_unsafe_host_mmu"),
|
||||
USE_DOCKED_MODE("use_docked_mode"),
|
||||
USE_AUTO_STUB("use_auto_stub"),
|
||||
|
||||
+7
@@ -756,6 +756,13 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.fix_bloom_effects_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.EMULATE_BGR565,
|
||||
titleId = R.string.emulate_bgr565,
|
||||
descriptionId = R.string.emulate_bgr565_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.CPUOPT_UNSAFE_HOST_MMU,
|
||||
|
||||
+1
@@ -293,6 +293,7 @@ class SettingsFragmentPresenter(
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
|
||||
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
|
||||
add(BooleanSetting.EMULATE_BGR565.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
|
||||
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
|
||||
|
||||
@@ -1726,9 +1726,8 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
||||
jobject obj,
|
||||
jstring version) {
|
||||
const char* version_str = env->GetStringUTFChars(version, nullptr);
|
||||
const std::string url = fmt::format("{}/{}/releases/tag/{}",
|
||||
std::string{Common::g_build_auto_update_website},
|
||||
std::string{Common::g_build_auto_update_repo},
|
||||
const std::string url = fmt::format("{}/{}",
|
||||
std::string{Common::g_build_auto_update_api},
|
||||
version_str);
|
||||
env->ReleaseStringUTFChars(version, version_str);
|
||||
return env->NewStringUTF(url.c_str());
|
||||
@@ -1760,11 +1759,10 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
||||
}
|
||||
|
||||
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", artifact_str, variant);
|
||||
const std::string url = fmt::format("{}/{}/releases/download/{}/{}",
|
||||
std::string{Common::g_build_auto_update_website},
|
||||
std::string{Common::g_build_auto_update_repo},
|
||||
version_str,
|
||||
apk_filename);
|
||||
|
||||
const std::string url = fmt::format("{}/{}/{}",
|
||||
std::string{Common::g_build_auto_update_api},
|
||||
version_str, apk_filename);
|
||||
|
||||
env->ReleaseStringUTFChars(tag, version_str);
|
||||
env->ReleaseStringUTFChars(artifact, artifact_str);
|
||||
|
||||
@@ -501,7 +501,7 @@
|
||||
<string name="enable_buffer_history">Enable buffer history</string>
|
||||
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
|
||||
<string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string>
|
||||
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers. Will crash on older drivers.</string>
|
||||
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers.</string>
|
||||
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
@@ -510,7 +510,9 @@
|
||||
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
|
||||
<string name="fix_bloom_effects">Fix Bloom Effects</string>
|
||||
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno 700), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
|
||||
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
|
||||
<string name="emulate_bgr565">Emulate BGR565</string>
|
||||
<string name="emulate_bgr565_description">Fixes problems with inverted colors in games or strange artifacts or strange shadows.</string>
|
||||
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
|
||||
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
|
||||
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
|
||||
|
||||
@@ -183,8 +183,6 @@ if(ARCHITECTURE_x86_64)
|
||||
x64/cpu_detect.h
|
||||
x64/cpu_wait.cpp
|
||||
x64/cpu_wait.h
|
||||
x64/native_clock.cpp
|
||||
x64/native_clock.h
|
||||
x64/rdtsc.cpp
|
||||
x64/rdtsc.h
|
||||
x64/xbyak_abi.h
|
||||
@@ -192,10 +190,6 @@ if(ARCHITECTURE_x86_64)
|
||||
target_link_libraries(common PRIVATE xbyak::xbyak)
|
||||
endif()
|
||||
|
||||
if(HAS_NCE)
|
||||
target_sources(common PRIVATE arm64/native_clock.cpp arm64/native_clock.h)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
target_compile_definitions(
|
||||
common
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <sys/system_properties.h>
|
||||
#endif
|
||||
#include "common/arm64/native_clock.h"
|
||||
|
||||
namespace Common::Arm64 {
|
||||
|
||||
namespace {
|
||||
|
||||
NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
|
||||
return (static_cast<NativeClock::FactorType>(num) << 64) / den;
|
||||
}
|
||||
|
||||
u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
|
||||
return static_cast<u64>((m * factor) >> 64);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NativeClock::NativeClock() {
|
||||
const u64 host_cntfrq = GetHostCNTFRQ();
|
||||
ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
|
||||
us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
|
||||
ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
|
||||
guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
|
||||
gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
|
||||
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)};
|
||||
}
|
||||
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() const {
|
||||
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)};
|
||||
}
|
||||
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() const {
|
||||
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)};
|
||||
}
|
||||
|
||||
s64 NativeClock::GetCNTPCT() const {
|
||||
return MultiplyHigh(GetUptime(), guest_cntfrq_factor);
|
||||
}
|
||||
|
||||
s64 NativeClock::GetGPUTick() const {
|
||||
return MultiplyHigh(GetUptime(), gputick_cntfrq_factor);
|
||||
}
|
||||
|
||||
s64 NativeClock::GetUptime() const {
|
||||
s64 cntvct_el0 = 0;
|
||||
asm volatile("dsb ish\n\t"
|
||||
"mrs %[cntvct_el0], cntvct_el0\n\t"
|
||||
"dsb ish\n\t"
|
||||
: [cntvct_el0] "=r"(cntvct_el0));
|
||||
return cntvct_el0;
|
||||
}
|
||||
|
||||
bool NativeClock::IsNative() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
s64 NativeClock::GetHostCNTFRQ() {
|
||||
u64 cntfrq_el0 = 0;
|
||||
std::string_view board{""};
|
||||
#ifdef ANDROID
|
||||
char buffer[PROP_VALUE_MAX];
|
||||
int len{__system_property_get("ro.product.board", buffer)};
|
||||
board = std::string_view(buffer, static_cast<size_t>(len));
|
||||
#endif
|
||||
if (board == "s5e9925") { // Exynos 2200
|
||||
cntfrq_el0 = 25600000;
|
||||
} else if (board == "exynos2100") { // Exynos 2100
|
||||
cntfrq_el0 = 26000000;
|
||||
} else if (board == "exynos9810") { // Exynos 9810
|
||||
cntfrq_el0 = 26000000;
|
||||
} else if (board == "s5e8825") { // Exynos 1280
|
||||
cntfrq_el0 = 26000000;
|
||||
} else {
|
||||
asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
|
||||
}
|
||||
return cntfrq_el0;
|
||||
}
|
||||
|
||||
} // namespace Common::Arm64
|
||||
@@ -1,45 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Common::Arm64 {
|
||||
|
||||
class NativeClock final : public WallClock {
|
||||
public:
|
||||
explicit NativeClock();
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() const override;
|
||||
|
||||
std::chrono::microseconds GetTimeUS() const override;
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() const override;
|
||||
|
||||
s64 GetCNTPCT() const override;
|
||||
|
||||
s64 GetGPUTick() const override;
|
||||
|
||||
s64 GetUptime() const override;
|
||||
|
||||
bool IsNative() const override;
|
||||
|
||||
static s64 GetHostCNTFRQ();
|
||||
|
||||
public:
|
||||
using FactorType = unsigned __int128;
|
||||
|
||||
FactorType GetGuestCNTFRQFactor() const {
|
||||
return guest_cntfrq_factor;
|
||||
}
|
||||
|
||||
private:
|
||||
FactorType ns_cntfrq_factor;
|
||||
FactorType us_cntfrq_factor;
|
||||
FactorType ms_cntfrq_factor;
|
||||
FactorType guest_cntfrq_factor;
|
||||
FactorType gputick_cntfrq_factor;
|
||||
};
|
||||
|
||||
} // namespace Common::Arm64
|
||||
@@ -13,13 +13,13 @@
|
||||
namespace Common {
|
||||
|
||||
template <typename BaseAddr>
|
||||
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
|
||||
std::size_t first_level_bits_,
|
||||
std::size_t page_bits_)
|
||||
: address_space_bits{address_space_bits_},
|
||||
first_level_bits{first_level_bits_}, page_bits{page_bits_} {
|
||||
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_, std::size_t first_level_bits_, std::size_t page_bits_)
|
||||
: address_space_bits{address_space_bits_}
|
||||
, first_level_bits{first_level_bits_}
|
||||
, page_bits{page_bits_}
|
||||
{
|
||||
if (page_bits == 0) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
first_level_shift = address_space_bits - first_level_bits;
|
||||
first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
|
||||
@@ -30,12 +30,9 @@ MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bit
|
||||
void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
|
||||
#else
|
||||
void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
|
||||
|
||||
if (base == MAP_FAILED) {
|
||||
if (base == MAP_FAILED)
|
||||
base = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
ASSERT(base);
|
||||
base_ptr = reinterpret_cast<BaseAddr*>(base);
|
||||
}
|
||||
@@ -56,29 +53,21 @@ template <typename BaseAddr>
|
||||
void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
|
||||
const u64 new_start = start >> first_level_shift;
|
||||
const u64 new_end = (start + size) >> first_level_shift;
|
||||
for (u64 i = new_start; i <= new_end; i++) {
|
||||
if (!first_level_map[i]) {
|
||||
for (u64 i = new_start; i <= new_end; i++)
|
||||
if (!first_level_map[i])
|
||||
AllocateLevel(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BaseAddr>
|
||||
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
|
||||
void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
|
||||
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 index) {
|
||||
void* ptr = reinterpret_cast<char *>(base_ptr) + index * first_level_chunk_size;
|
||||
#ifdef _WIN32
|
||||
void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
|
||||
#else
|
||||
void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
|
||||
|
||||
if (base == MAP_FAILED) {
|
||||
base = nullptr;
|
||||
}
|
||||
#endif
|
||||
void* base = VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE);
|
||||
ASSERT(base);
|
||||
|
||||
first_level_map[level] = base;
|
||||
#else
|
||||
void* base = ptr;
|
||||
#endif
|
||||
first_level_map[index] = base;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -555,6 +555,9 @@ struct Values {
|
||||
SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> emulate_bgr565{linkage, false, "emulate_bgr565",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
|
||||
Category::RendererHacks};
|
||||
|
||||
@@ -584,7 +587,7 @@ struct Values {
|
||||
|
||||
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
|
||||
#if defined(ANDROID)
|
||||
ExtendedDynamicState::EDS1,
|
||||
ExtendedDynamicState::Disabled,
|
||||
#elif defined(__APPLE__)
|
||||
ExtendedDynamicState::Disabled,
|
||||
#else
|
||||
|
||||
+174
-55
@@ -1,77 +1,196 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <sys/system_properties.h>
|
||||
#endif
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
#ifdef HAS_NCE
|
||||
#include "common/arm64/native_clock.h"
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
class StandardWallClock final : public WallClock {
|
||||
public:
|
||||
explicit StandardWallClock() {}
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() const override {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
}
|
||||
|
||||
std::chrono::microseconds GetTimeUS() const override {
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
}
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() const override {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
}
|
||||
|
||||
s64 GetCNTPCT() const override {
|
||||
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
s64 GetGPUTick() const override {
|
||||
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
s64 GetUptime() const override {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
bool IsNative() const override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<WallClock> CreateOptimalClock() {
|
||||
#if defined(ARCHITECTURE_x86_64)
|
||||
const auto& caps = GetCPUCaps();
|
||||
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept
|
||||
: invariant{invariant_}
|
||||
, rdtsc_frequency{rdtsc_frequency_}
|
||||
, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_)}
|
||||
, us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_)}
|
||||
, ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_)}
|
||||
, cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_)}
|
||||
, gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_)}
|
||||
{}
|
||||
|
||||
if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
|
||||
return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
|
||||
} else {
|
||||
// Fallback to StandardWallClock if the hardware TSC
|
||||
// - Is not invariant
|
||||
// - Is not more precise than 1 GHz (1ns resolution)
|
||||
return std::make_unique<StandardWallClock>();
|
||||
}
|
||||
std::chrono::nanoseconds WallClock::GetTimeNS() const {
|
||||
if (invariant)
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
|
||||
}
|
||||
|
||||
std::chrono::microseconds WallClock::GetTimeUS() const {
|
||||
if (invariant)
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
|
||||
}
|
||||
|
||||
std::chrono::milliseconds WallClock::GetTimeMS() const {
|
||||
if (invariant)
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
|
||||
}
|
||||
|
||||
s64 WallClock::GetCNTPCT() const {
|
||||
if (invariant)
|
||||
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor);
|
||||
}
|
||||
|
||||
s64 WallClock::GetGPUTick() const {
|
||||
if (invariant)
|
||||
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
|
||||
return MultiplyHigh(GetUptime(), gputick_rdtsc_factor);
|
||||
}
|
||||
|
||||
s64 WallClock::GetUptime() const {
|
||||
if (invariant)
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
return s64(Common::X64::FencedRDTSC());
|
||||
}
|
||||
|
||||
bool WallClock::IsNative() const {
|
||||
if (invariant)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
#elif defined(HAS_NCE)
|
||||
return std::make_unique<Arm64::NativeClock>();
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] WallClock::FactorType GetFixedPointFactor(u64 num, u64 den) noexcept {
|
||||
return (WallClock::FactorType(num) << 64) / den;
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 MultiplyHigh(u64 m, WallClock::FactorType factor) noexcept {
|
||||
return static_cast<u64>((m * factor) >> 64);
|
||||
}
|
||||
|
||||
[[nodiscard]] s64 GetHostCNTFRQ() noexcept {
|
||||
u64 cntfrq_el0 = 0;
|
||||
#ifdef ANDROID
|
||||
std::string_view board{""};
|
||||
char buffer[PROP_VALUE_MAX];
|
||||
int len{__system_property_get("ro.product.board", buffer)};
|
||||
board = std::string_view(buffer, static_cast<size_t>(len));
|
||||
if (board == "s5e9925") { // Exynos 2200
|
||||
cntfrq_el0 = 25600000;
|
||||
} else if (board == "exynos2100") { // Exynos 2100
|
||||
cntfrq_el0 = 26000000;
|
||||
} else if (board == "exynos9810") { // Exynos 9810
|
||||
cntfrq_el0 = 26000000;
|
||||
} else if (board == "s5e8825") { // Exynos 1280
|
||||
cntfrq_el0 = 26000000;
|
||||
} else {
|
||||
asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
|
||||
}
|
||||
return cntfrq_el0;
|
||||
#else
|
||||
return std::make_unique<StandardWallClock>();
|
||||
asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
|
||||
return cntfrq_el0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {
|
||||
const u64 host_cntfrq = std::max<u64>(GetHostCNTFRQ(), 1);
|
||||
ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
|
||||
us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
|
||||
ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
|
||||
guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
|
||||
gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds WallClock::GetTimeNS() const {
|
||||
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)};
|
||||
}
|
||||
|
||||
std::chrono::microseconds WallClock::GetTimeUS() const {
|
||||
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)};
|
||||
}
|
||||
|
||||
std::chrono::milliseconds WallClock::GetTimeMS() const {
|
||||
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)};
|
||||
}
|
||||
|
||||
s64 WallClock::GetCNTPCT() const {
|
||||
return MultiplyHigh(GetUptime(), guest_cntfrq_factor);
|
||||
}
|
||||
|
||||
s64 WallClock::GetGPUTick() const {
|
||||
return MultiplyHigh(GetUptime(), gputick_cntfrq_factor);
|
||||
}
|
||||
|
||||
s64 WallClock::GetUptime() const {
|
||||
s64 cntvct_el0 = 0;
|
||||
asm volatile(
|
||||
"dsb ish\n\t"
|
||||
"mrs %[cntvct_el0], cntvct_el0\n\t"
|
||||
"dsb ish\n\t"
|
||||
: [cntvct_el0] "=r"(cntvct_el0)
|
||||
);
|
||||
return cntvct_el0;
|
||||
}
|
||||
|
||||
bool WallClock::IsNative() const {
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {}
|
||||
|
||||
std::chrono::nanoseconds WallClock::GetTimeNS() const {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
}
|
||||
|
||||
std::chrono::microseconds WallClock::GetTimeUS() const {
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
}
|
||||
|
||||
std::chrono::milliseconds WallClock::GetTimeMS() const {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
}
|
||||
|
||||
s64 WallClock::GetCNTPCT() const {
|
||||
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
s64 WallClock::GetGPUTick() const {
|
||||
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
s64 WallClock::GetUptime() const {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
bool WallClock::IsNative() const {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
WallClock CreateOptimalClock() noexcept {
|
||||
#if defined(ARCHITECTURE_x86_64)
|
||||
auto const& caps = GetCPUCaps();
|
||||
return WallClock(!(caps.invariant_tsc && caps.tsc_frequency >= std::nano::den), std::max<u64>(caps.tsc_frequency, 1));
|
||||
#elif defined(HAS_NCE)
|
||||
return WallClock(false, 1);
|
||||
#else
|
||||
return WallClock(true, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+35
-10
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -20,28 +20,28 @@ public:
|
||||
static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz
|
||||
static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz
|
||||
|
||||
virtual ~WallClock() = default;
|
||||
explicit WallClock(bool invariant, u64 rdtsc_frequency_) noexcept;
|
||||
|
||||
/// @returns The time in nanoseconds since the construction of this clock.
|
||||
virtual std::chrono::nanoseconds GetTimeNS() const = 0;
|
||||
std::chrono::nanoseconds GetTimeNS() const;
|
||||
|
||||
/// @returns The time in microseconds since the construction of this clock.
|
||||
virtual std::chrono::microseconds GetTimeUS() const = 0;
|
||||
std::chrono::microseconds GetTimeUS() const;
|
||||
|
||||
/// @returns The time in milliseconds since the construction of this clock.
|
||||
virtual std::chrono::milliseconds GetTimeMS() const = 0;
|
||||
std::chrono::milliseconds GetTimeMS() const;
|
||||
|
||||
/// @returns The guest CNTPCT ticks since the construction of this clock.
|
||||
virtual s64 GetCNTPCT() const = 0;
|
||||
s64 GetCNTPCT() const;
|
||||
|
||||
/// @returns The guest GPU ticks since the construction of this clock.
|
||||
virtual s64 GetGPUTick() const = 0;
|
||||
s64 GetGPUTick() const;
|
||||
|
||||
/// @returns The raw host timer ticks since an indeterminate epoch.
|
||||
virtual s64 GetUptime() const = 0;
|
||||
s64 GetUptime() const;
|
||||
|
||||
/// @returns Whether the clock directly uses the host's hardware clock.
|
||||
virtual bool IsNative() const = 0;
|
||||
bool IsNative() const;
|
||||
|
||||
static inline u64 NSToCNTPCT(u64 ns) {
|
||||
return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
@@ -85,8 +85,33 @@ protected:
|
||||
using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>;
|
||||
using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>;
|
||||
using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>;
|
||||
|
||||
#if defined(ARCHITECTURE_x86_64)
|
||||
bool invariant;
|
||||
u64 rdtsc_frequency;
|
||||
u64 ns_rdtsc_factor;
|
||||
u64 us_rdtsc_factor;
|
||||
u64 ms_rdtsc_factor;
|
||||
u64 cntpct_rdtsc_factor;
|
||||
u64 gputick_rdtsc_factor;
|
||||
#elif defined(HAS_NCE)
|
||||
public:
|
||||
using FactorType = unsigned __int128;
|
||||
|
||||
FactorType GetGuestCNTFRQFactor() const {
|
||||
return guest_cntfrq_factor;
|
||||
}
|
||||
protected:
|
||||
FactorType ns_cntfrq_factor;
|
||||
FactorType us_cntfrq_factor;
|
||||
FactorType ms_cntfrq_factor;
|
||||
FactorType guest_cntfrq_factor;
|
||||
FactorType gputick_cntfrq_factor;
|
||||
#else
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<WallClock> CreateOptimalClock();
|
||||
[[nodiscard]] WallClock CreateOptimalClock() noexcept;
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/uint128.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
NativeClock::NativeClock(u64 rdtsc_frequency_)
|
||||
: rdtsc_frequency{rdtsc_frequency_}, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den,
|
||||
rdtsc_frequency)},
|
||||
us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
|
||||
ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
|
||||
cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)},
|
||||
gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {}
|
||||
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
|
||||
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
|
||||
}
|
||||
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() const {
|
||||
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
|
||||
}
|
||||
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() const {
|
||||
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
|
||||
}
|
||||
|
||||
s64 NativeClock::GetCNTPCT() const {
|
||||
return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor);
|
||||
}
|
||||
|
||||
s64 NativeClock::GetGPUTick() const {
|
||||
return MultiplyHigh(GetUptime(), gputick_rdtsc_factor);
|
||||
}
|
||||
|
||||
s64 NativeClock::GetUptime() const {
|
||||
return static_cast<s64>(FencedRDTSC());
|
||||
}
|
||||
|
||||
bool NativeClock::IsNative() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common::X64
|
||||
@@ -1,38 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
class NativeClock final : public WallClock {
|
||||
public:
|
||||
explicit NativeClock(u64 rdtsc_frequency_);
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() const override;
|
||||
|
||||
std::chrono::microseconds GetTimeUS() const override;
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() const override;
|
||||
|
||||
s64 GetCNTPCT() const override;
|
||||
|
||||
s64 GetGPUTick() const override;
|
||||
|
||||
s64 GetUptime() const override;
|
||||
|
||||
bool IsNative() const override;
|
||||
|
||||
private:
|
||||
u64 rdtsc_frequency;
|
||||
|
||||
u64 ns_rdtsc_factor;
|
||||
u64 us_rdtsc_factor;
|
||||
u64 ms_rdtsc_factor;
|
||||
u64 cntpct_rdtsc_factor;
|
||||
u64 gputick_rdtsc_factor;
|
||||
};
|
||||
|
||||
} // namespace Common::X64
|
||||
@@ -113,8 +113,7 @@ void DynarmicCallbacks32::CallSVC(u32 swi) {
|
||||
}
|
||||
|
||||
void DynarmicCallbacks32::AddTicks(u64 ticks) {
|
||||
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
|
||||
|
||||
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
|
||||
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
|
||||
// rough approximation of the amount of executed ticks in the system, it may be thrown off
|
||||
// if not all cores are doing a similar amount of work. Instead of doing this, we should
|
||||
@@ -123,14 +122,12 @@ void DynarmicCallbacks32::AddTicks(u64 ticks) {
|
||||
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
|
||||
// Always execute at least one tick.
|
||||
amortized_ticks = std::max<u64>(amortized_ticks, 1);
|
||||
|
||||
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
|
||||
}
|
||||
|
||||
u64 DynarmicCallbacks32::GetTicksRemaining() {
|
||||
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
|
||||
|
||||
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
|
||||
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
|
||||
return std::max<s64>(m_parent.m_system.CoreTiming().downcount, 0);
|
||||
}
|
||||
|
||||
bool DynarmicCallbacks32::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) {
|
||||
|
||||
@@ -150,8 +150,7 @@ void DynarmicCallbacks64::CallSVC(u32 svc) {
|
||||
}
|
||||
|
||||
void DynarmicCallbacks64::AddTicks(u64 ticks) {
|
||||
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
|
||||
|
||||
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
|
||||
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
|
||||
// rough approximation of the amount of executed ticks in the system, it may be thrown off
|
||||
// if not all cores are doing a similar amount of work. Instead of doing this, we should
|
||||
@@ -160,13 +159,12 @@ void DynarmicCallbacks64::AddTicks(u64 ticks) {
|
||||
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
|
||||
// Always execute at least one tick.
|
||||
amortized_ticks = std::max<u64>(amortized_ticks, 1);
|
||||
|
||||
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
|
||||
}
|
||||
|
||||
u64 DynarmicCallbacks64::GetTicksRemaining() {
|
||||
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
|
||||
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
|
||||
return std::max<s64>(m_parent.m_system.CoreTiming().downcount, 0);
|
||||
}
|
||||
|
||||
u64 DynarmicCallbacks64::GetCNTPCT() {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -7,28 +10,30 @@
|
||||
namespace Core {
|
||||
|
||||
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory_, std::size_t core_count_)
|
||||
: monitor{core_count_}, memory{memory_} {}
|
||||
: monitor{}
|
||||
, memory{memory_}
|
||||
{}
|
||||
|
||||
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
|
||||
|
||||
u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u8>(core_index, addr, [&]() -> u8 { return memory.Read8(addr); });
|
||||
return monitor.ReadAndMark<u8>(core_index, addr, [=]() -> u8 { return memory.Read8(addr); });
|
||||
}
|
||||
|
||||
u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u16>(core_index, addr, [&]() -> u16 { return memory.Read16(addr); });
|
||||
return monitor.ReadAndMark<u16>(core_index, addr, [=]() -> u16 { return memory.Read16(addr); });
|
||||
}
|
||||
|
||||
u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u32>(core_index, addr, [&]() -> u32 { return memory.Read32(addr); });
|
||||
return monitor.ReadAndMark<u32>(core_index, addr, [=]() -> u32 { return memory.Read32(addr); });
|
||||
}
|
||||
|
||||
u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u64>(core_index, addr, [&]() -> u64 { return memory.Read64(addr); });
|
||||
return monitor.ReadAndMark<u64>(core_index, addr, [=]() -> u64 { return memory.Read64(addr); });
|
||||
}
|
||||
|
||||
u128 DynarmicExclusiveMonitor::ExclusiveRead128(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u128>(core_index, addr, [&]() -> u128 {
|
||||
return monitor.ReadAndMark<u128>(core_index, addr, [=]() -> u128 {
|
||||
u128 result;
|
||||
result[0] = memory.Read64(addr);
|
||||
result[1] = memory.Read64(addr + 8);
|
||||
@@ -41,31 +46,31 @@ void DynarmicExclusiveMonitor::ClearExclusive(std::size_t core_index) {
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
|
||||
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [&](u8 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [=](u8 expected) -> bool {
|
||||
return memory.WriteExclusive8(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
|
||||
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [&](u16 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [=](u16 expected) -> bool {
|
||||
return memory.WriteExclusive16(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
|
||||
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [&](u32 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [=](u32 expected) -> bool {
|
||||
return memory.WriteExclusive32(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
|
||||
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [&](u64 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [=](u64 expected) -> bool {
|
||||
return memory.WriteExclusive64(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
|
||||
return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [&](u128 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [=](u128 expected) -> bool {
|
||||
return memory.WriteExclusive128(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <numeric>
|
||||
#include <bit>
|
||||
#include "common/arm64/native_clock.h"
|
||||
#include "common/wall_clock.h"
|
||||
#include "common/alignment.h"
|
||||
#include "common/literals.h"
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
@@ -578,7 +578,11 @@ void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg,
|
||||
}
|
||||
|
||||
void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) {
|
||||
static Common::Arm64::NativeClock clock{};
|
||||
#if defined(HAS_NCE)
|
||||
static Common::WallClock clock(false, 1);
|
||||
#else
|
||||
static Common::WallClock clock(true, 1);
|
||||
#endif
|
||||
const auto factor = clock.GetGuestCNTFRQFactor();
|
||||
const auto raw_factor = std::bit_cast<std::array<u64, 2>>(factor);
|
||||
|
||||
|
||||
+67
-88
@@ -57,15 +57,51 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
Reset();
|
||||
on_thread_init = std::move(on_thread_init_);
|
||||
event_fifo_id = 0;
|
||||
shutting_down = false;
|
||||
cpu_ticks = 0;
|
||||
if (is_multicore) {
|
||||
timer_thread.emplace([](CoreTiming& instance) {
|
||||
timer_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("HostTiming");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
instance.on_thread_init();
|
||||
instance.ThreadLoop();
|
||||
}, std::ref(*this));
|
||||
on_thread_init();
|
||||
has_started = true;
|
||||
while (!stop_token.stop_requested()) {
|
||||
while (!paused && !stop_token.stop_requested()) {
|
||||
paused_set = false;
|
||||
if (auto const next_time = Advance(); next_time) {
|
||||
// There are more events left in the queue, wait until the next event.
|
||||
auto wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
if (wait_time > 0) {
|
||||
#ifdef _WIN32
|
||||
while (!paused && !event.IsSet() && wait_time > 0) {
|
||||
wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
if (wait_time >= timer_resolution_ns) {
|
||||
Common::Windows::SleepForOneTick();
|
||||
} else {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
Common::X64::MicroSleep();
|
||||
#else
|
||||
std::this_thread::yield();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (event.IsSet())
|
||||
event.Reset();
|
||||
#else
|
||||
event.WaitFor(std::chrono::nanoseconds(wait_time));
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// Queue is empty, wait until another event is scheduled and signals us to
|
||||
// continue.
|
||||
wait_set = true;
|
||||
event.Wait();
|
||||
}
|
||||
wait_set = false;
|
||||
}
|
||||
paused_set = true;
|
||||
pause_event.Wait();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +126,7 @@ void CoreTiming::SyncPause(bool is_paused) {
|
||||
}
|
||||
|
||||
Pause(is_paused);
|
||||
if (timer_thread) {
|
||||
if (timer_thread.joinable()) {
|
||||
if (!is_paused) {
|
||||
pause_event.Set();
|
||||
}
|
||||
@@ -190,33 +226,22 @@ void CoreTiming::ResetTicks() {
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetClockTicks() const {
|
||||
u64 fres;
|
||||
if (is_multicore) [[likely]] {
|
||||
fres = clock->GetCNTPCT();
|
||||
} else {
|
||||
fres = Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
u64 fres = is_multicore ? clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) {
|
||||
fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock)));
|
||||
}
|
||||
|
||||
const auto overclock = Settings::values.fast_cpu_time.GetValue();
|
||||
|
||||
if (overclock != Settings::CpuClock::Off) {
|
||||
fres = (u64) ((double) fres * (1.7 + 0.3 * u32(overclock)));
|
||||
}
|
||||
|
||||
if (Settings::values.sync_core_speed.GetValue()) {
|
||||
const auto ticks = double(fres);
|
||||
const auto speed_limit = double(Settings::SpeedLimit())*0.01;
|
||||
return u64(ticks/speed_limit);
|
||||
} else {
|
||||
return fres;
|
||||
}
|
||||
if (::Settings::values.sync_core_speed.GetValue()) {
|
||||
auto const ticks = f64(fres);
|
||||
auto const speed_limit = f64(Settings::SpeedLimit()) * 0.01;
|
||||
return u64(ticks / speed_limit);
|
||||
}
|
||||
return fres;
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetGPUTicks() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return clock->GetGPUTick();
|
||||
}
|
||||
return Common::WallClock::CPUTickToGPUTick(cpu_ticks);
|
||||
return is_multicore
|
||||
? clock.GetGPUTick()
|
||||
: Common::WallClock::CPUTickToGPUTick(cpu_ticks);
|
||||
}
|
||||
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
@@ -278,75 +303,29 @@ std::optional<s64> CoreTiming::Advance() {
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::ThreadLoop() {
|
||||
has_started = true;
|
||||
while (!shutting_down) {
|
||||
while (!paused) {
|
||||
paused_set = false;
|
||||
const auto next_time = Advance();
|
||||
if (next_time) {
|
||||
// There are more events left in the queue, wait until the next event.
|
||||
auto wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
if (wait_time > 0) {
|
||||
#ifdef _WIN32
|
||||
while (!paused && !event.IsSet() && wait_time > 0) {
|
||||
wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
if (wait_time >= timer_resolution_ns) {
|
||||
Common::Windows::SleepForOneTick();
|
||||
} else {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
Common::X64::MicroSleep();
|
||||
#else
|
||||
std::this_thread::yield();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (event.IsSet()) {
|
||||
event.Reset();
|
||||
}
|
||||
#else
|
||||
event.WaitFor(std::chrono::nanoseconds(wait_time));
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// Queue is empty, wait until another event is scheduled and signals us to
|
||||
// continue.
|
||||
wait_set = true;
|
||||
event.Wait();
|
||||
}
|
||||
wait_set = false;
|
||||
}
|
||||
|
||||
paused_set = true;
|
||||
pause_event.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::Reset() {
|
||||
paused = true;
|
||||
shutting_down = true;
|
||||
pause_event.Set();
|
||||
event.Set();
|
||||
if (timer_thread) {
|
||||
timer_thread->join();
|
||||
if (timer_thread.joinable()) {
|
||||
timer_thread.request_stop();
|
||||
timer_thread.join();
|
||||
}
|
||||
timer_thread.reset();
|
||||
has_started = false;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return clock->GetTimeNS();
|
||||
}
|
||||
return std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)};
|
||||
/// @brief Returns current time in nanoseconds.
|
||||
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const noexcept {
|
||||
return is_multicore
|
||||
? clock.GetTimeNS()
|
||||
: std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)};
|
||||
}
|
||||
|
||||
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return clock->GetTimeUS();
|
||||
}
|
||||
return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
|
||||
/// @brief Returns current time in microseconds.
|
||||
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const noexcept {
|
||||
return is_multicore
|
||||
? clock.GetTimeUS()
|
||||
: std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
+6
-12
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -118,7 +118,7 @@ public:
|
||||
|
||||
void Idle();
|
||||
|
||||
s64 GetDowncount() const {
|
||||
s64 GetDowncount() const noexcept {
|
||||
return downcount;
|
||||
}
|
||||
|
||||
@@ -128,11 +128,8 @@ public:
|
||||
/// Returns the current GPU tick value.
|
||||
u64 GetGPUTicks() const;
|
||||
|
||||
/// Returns current time in microseconds.
|
||||
std::chrono::microseconds GetGlobalTimeUs() const;
|
||||
|
||||
/// Returns current time in nanoseconds.
|
||||
std::chrono::nanoseconds GetGlobalTimeNs() const;
|
||||
[[nodiscard]] std::chrono::microseconds GetGlobalTimeUs() const noexcept;
|
||||
[[nodiscard]] std::chrono::nanoseconds GetGlobalTimeNs() const noexcept;
|
||||
|
||||
/// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
|
||||
std::optional<s64> Advance();
|
||||
@@ -141,13 +138,11 @@ public:
|
||||
void SetTimerResolutionNs(std::chrono::nanoseconds ns);
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct Event;
|
||||
void ThreadLoop();
|
||||
|
||||
void Reset();
|
||||
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
Common::WallClock clock;
|
||||
|
||||
s64 global_timer = 0;
|
||||
|
||||
@@ -165,11 +160,10 @@ private:
|
||||
Common::Event pause_event{};
|
||||
mutable std::mutex basic_lock;
|
||||
std::mutex advance_lock;
|
||||
std::optional<std::jthread> timer_thread;
|
||||
std::jthread timer_thread;
|
||||
std::atomic<bool> paused{};
|
||||
std::atomic<bool> paused_set{};
|
||||
std::atomic<bool> wait_set{};
|
||||
std::atomic<bool> shutting_down{};
|
||||
std::atomic<bool> has_started{};
|
||||
std::function<void()> on_thread_init{};
|
||||
|
||||
|
||||
@@ -15,86 +15,76 @@
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800;
|
||||
constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28;
|
||||
|
||||
using SystemArchiveSupplier = VirtualDir (*)();
|
||||
|
||||
struct SystemArchiveDescriptor {
|
||||
u64 title_id;
|
||||
const char* name;
|
||||
SystemArchiveSupplier supplier;
|
||||
VirtualDir (*supplier)();
|
||||
};
|
||||
|
||||
constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
|
||||
{0x0100000000000800, "CertStore", nullptr},
|
||||
{0x0100000000000801, "ErrorMessage", nullptr},
|
||||
{0x0100000000000802, "MiiModel", &MiiModel},
|
||||
{0x0100000000000803, "BrowserDll", nullptr},
|
||||
{0x0100000000000804, "Help", nullptr},
|
||||
{0x0100000000000805, "SharedFont", nullptr},
|
||||
{0x0100000000000806, "NgWord", &NgWord1},
|
||||
{0x0100000000000807, "SsidList", nullptr},
|
||||
{0x0100000000000808, "Dictionary", nullptr},
|
||||
{0x0100000000000809, "SystemVersion", &SystemVersion},
|
||||
{0x010000000000080A, "AvatarImage", nullptr},
|
||||
{0x010000000000080B, "LocalNews", nullptr},
|
||||
{0x010000000000080C, "Eula", nullptr},
|
||||
{0x010000000000080D, "UrlBlackList", nullptr},
|
||||
{0x010000000000080E, "TimeZoneBinary", &TimeZoneBinary},
|
||||
{0x010000000000080F, "CertStoreCruiser", nullptr},
|
||||
{0x0100000000000810, "FontNintendoExtension", &FontNintendoExtension},
|
||||
{0x0100000000000811, "FontStandard", &FontStandard},
|
||||
{0x0100000000000812, "FontKorean", &FontKorean},
|
||||
{0x0100000000000813, "FontChineseTraditional", &FontChineseTraditional},
|
||||
{0x0100000000000814, "FontChineseSimple", &FontChineseSimple},
|
||||
{0x0100000000000815, "FontBfcpx", nullptr},
|
||||
{0x0100000000000816, "SystemUpdate", nullptr},
|
||||
{0x0100000000000817, "0100000000000817", nullptr},
|
||||
{0x0100000000000818, "FirmwareDebugSettings", nullptr},
|
||||
{0x0100000000000819, "BootImagePackage", nullptr},
|
||||
{0x010000000000081A, "BootImagePackageSafe", nullptr},
|
||||
{0x010000000000081B, "BootImagePackageExFat", nullptr},
|
||||
{0x010000000000081C, "BootImagePackageExFatSafe", nullptr},
|
||||
{0x010000000000081D, "FatalMessage", nullptr},
|
||||
{0x010000000000081E, "ControllerIcon", nullptr},
|
||||
{0x010000000000081F, "PlatformConfigIcosa", nullptr},
|
||||
{0x0100000000000820, "PlatformConfigCopper", nullptr},
|
||||
{0x0100000000000821, "PlatformConfigHoag", nullptr},
|
||||
{0x0100000000000822, "ControllerFirmware", nullptr},
|
||||
{0x0100000000000823, "NgWord2", &NgWord2},
|
||||
{0x0100000000000824, "PlatformConfigIcosaMariko", nullptr},
|
||||
{0x0100000000000825, "ApplicationBlackList", nullptr},
|
||||
{0x0100000000000826, "RebootlessSystemUpdateVersion", nullptr},
|
||||
{0x0100000000000827, "ContentActionTable", nullptr},
|
||||
}};
|
||||
constexpr inline SystemArchiveDescriptor GetSystemArchive(u64 title_id) {
|
||||
switch (title_id) {
|
||||
case 0x0100000000000800: return {"CertStore", nullptr};
|
||||
case 0x0100000000000801: return {"ErrorMessage", nullptr};
|
||||
case 0x0100000000000802: return {"MiiModel", &MiiModel};
|
||||
case 0x0100000000000803: return {"BrowserDll", nullptr};
|
||||
case 0x0100000000000804: return {"Help", nullptr};
|
||||
case 0x0100000000000805: return {"SharedFont", nullptr};
|
||||
case 0x0100000000000806: return {"NgWord", &NgWord1};
|
||||
case 0x0100000000000807: return {"SsidList", nullptr};
|
||||
case 0x0100000000000808: return {"Dictionary", nullptr};
|
||||
case 0x0100000000000809: return {"SystemVersion", &SystemVersion};
|
||||
case 0x010000000000080A: return {"AvatarImage", nullptr};
|
||||
case 0x010000000000080B: return {"LocalNews", nullptr};
|
||||
case 0x010000000000080C: return {"Eula", nullptr};
|
||||
case 0x010000000000080D: return {"UrlBlackList", nullptr};
|
||||
case 0x010000000000080E: return {"TimeZoneBinary", &TimeZoneBinary};
|
||||
case 0x010000000000080F: return {"CertStoreCruiser", nullptr};
|
||||
case 0x0100000000000810: return {"FontNintendoExtension", &FontNintendoExtension};
|
||||
case 0x0100000000000811: return {"FontStandard", &FontStandard};
|
||||
case 0x0100000000000812: return {"FontKorean", &FontKorean};
|
||||
case 0x0100000000000813: return {"FontChineseTraditional", &FontChineseTraditional};
|
||||
case 0x0100000000000814: return {"FontChineseSimple", &FontChineseSimple};
|
||||
case 0x0100000000000815: return {"FontBfcpx", nullptr};
|
||||
case 0x0100000000000816: return {"SystemUpdate", nullptr};
|
||||
case 0x0100000000000817: return {"0100000000000817", nullptr};
|
||||
case 0x0100000000000818: return {"FirmwareDebugSettings", nullptr};
|
||||
case 0x0100000000000819: return {"BootImagePackage", nullptr};
|
||||
case 0x010000000000081A: return {"BootImagePackageSafe", nullptr};
|
||||
case 0x010000000000081B: return {"BootImagePackageExFat", nullptr};
|
||||
case 0x010000000000081C: return {"BootImagePackageExFatSafe", nullptr};
|
||||
case 0x010000000000081D: return {"FatalMessage", nullptr};
|
||||
case 0x010000000000081E: return {"ControllerIcon", nullptr};
|
||||
case 0x010000000000081F: return {"PlatformConfigIcosa", nullptr};
|
||||
case 0x0100000000000820: return {"PlatformConfigCopper", nullptr};
|
||||
case 0x0100000000000821: return {"PlatformConfigHoag", nullptr};
|
||||
case 0x0100000000000822: return {"ControllerFirmware", nullptr};
|
||||
case 0x0100000000000823: return {"NgWord2", &NgWord2};
|
||||
case 0x0100000000000824: return {"PlatformConfigIcosaMariko", nullptr};
|
||||
case 0x0100000000000825: return {"ApplicationBlackList", nullptr};
|
||||
case 0x0100000000000826: return {"RebootlessSystemUpdateVersion", nullptr};
|
||||
case 0x0100000000000827: return {"ContentActionTable", nullptr};
|
||||
case 0x0100000000000828: return {"FunctionBlackList", nullptr};
|
||||
case 0x0100000000000829: return {"PlatformConfigCalcio", nullptr};
|
||||
case 0x0100000000000830: return {"NgWordT", nullptr};
|
||||
case 0x0100000000000831: return {"PlatformConfigAula", nullptr};
|
||||
case 0x0100000000000832: return {"CradleFirmware", nullptr};
|
||||
case 0x0100000000000835: return {"ErrorMessageUtf8", nullptr};
|
||||
case 0x0100000000000859: return {"ClientCertData", nullptr};
|
||||
case 0x010000000000085C: return {"GameCardConfigurationData", nullptr};
|
||||
default: return {nullptr, nullptr};
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile SynthesizeSystemArchive(const u64 title_id) {
|
||||
if (title_id < SYSTEM_ARCHIVES.front().title_id || title_id > SYSTEM_ARCHIVES.back().title_id) {
|
||||
return nullptr;
|
||||
auto const desc = GetSystemArchive(title_id);
|
||||
LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, title_id);
|
||||
if (desc.supplier != nullptr) {
|
||||
if (auto const dir = desc.supplier(); dir != nullptr) {
|
||||
if (auto const romfs = CreateRomFS(dir); romfs != nullptr) {
|
||||
LOG_INFO(Service_FS, " - System archive generation successful!");
|
||||
return romfs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& desc = SYSTEM_ARCHIVES[title_id - SYSTEM_ARCHIVE_BASE_TITLE_ID];
|
||||
|
||||
LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, desc.title_id);
|
||||
|
||||
if (desc.supplier == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto dir = desc.supplier();
|
||||
|
||||
if (dir == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto romfs = CreateRomFS(dir);
|
||||
|
||||
if (romfs == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LOG_INFO(Service_FS, " - System archive generation successful!");
|
||||
return romfs;
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace FileSys::SystemArchive
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "applet_web_browser_types.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
@@ -370,16 +371,13 @@ void WebBrowser::ExtractOfflineRomFS() {
|
||||
|
||||
void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
|
||||
const bool use_tlv_output =
|
||||
(web_arg_header.shim_kind == ShimKind::Share &&
|
||||
web_applet_version >= WebAppletVersion::Version196608) ||
|
||||
(web_arg_header.shim_kind == ShimKind::Web &&
|
||||
web_applet_version >= WebAppletVersion::Version524288) ||
|
||||
(web_arg_header.shim_kind == ShimKind::Lhub);
|
||||
(web_arg_header.shim_kind == ShimKind::Share && web_applet_version >= WebAppletVersion::Version196608)
|
||||
|| (web_arg_header.shim_kind == ShimKind::Web && web_applet_version >= WebAppletVersion::Version524288)
|
||||
|| (web_arg_header.shim_kind == ShimKind::Lhub);
|
||||
|
||||
// https://switchbrew.org/wiki/Internet_Browser#TLVs
|
||||
if (use_tlv_output) {
|
||||
LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}",
|
||||
exit_reason, last_url, last_url.size());
|
||||
LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}", exit_reason, last_url, last_url.size());
|
||||
|
||||
// storage size for TLVs is 0x2000 bytes (as per switchbrew documentation)
|
||||
constexpr size_t TLV_STORAGE_SIZE = 0x2000;
|
||||
@@ -600,11 +598,14 @@ void WebBrowser::ExecuteShare() {
|
||||
|
||||
void WebBrowser::ExecuteWeb() {
|
||||
LOG_INFO(Service_AM, "Opening external URL at {}", external_url);
|
||||
|
||||
frontend.OpenExternalWebPage(external_url,
|
||||
[this](WebExitReason exit_reason, std::string last_url) {
|
||||
WebBrowserExit(exit_reason, last_url);
|
||||
});
|
||||
frontend.OpenExternalWebPage(external_url, [this](WebExitReason exit_reason, std::string last_url) {
|
||||
// Offline and web applets must be explicitly exited from because they respect exit state
|
||||
// Unlike the other web stuffs
|
||||
if (exit_reason == WebExitReason::ExitRequested || exit_reason == WebExitReason::EndButtonPressed)
|
||||
exit_reason = (web_arg_header.shim_kind == ShimKind::Web || web_arg_header.shim_kind == ShimKind::Offline)
|
||||
? WebExitReason::ExitRequested : WebExitReason::EndButtonPressed;
|
||||
WebBrowserExit(exit_reason, last_url);
|
||||
});
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteWifi() {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
@@ -130,12 +131,12 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
|
||||
|
||||
const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
|
||||
const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
|
||||
vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
|
||||
vm.small_page_allocator.emplace(start_pages, end_pages);
|
||||
|
||||
const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
|
||||
const auto end_big_pages{
|
||||
static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
|
||||
vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
|
||||
vm.big_page_allocator.emplace(start_big_pages, end_big_pages);
|
||||
|
||||
gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split,
|
||||
vm.big_page_size_bits, VM::PAGE_SIZE_BITS);
|
||||
@@ -188,8 +189,8 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
|
||||
}
|
||||
|
||||
allocation_map[params.offset] = {
|
||||
.size = size,
|
||||
.mappings{},
|
||||
.size = size,
|
||||
.page_size = params.page_size,
|
||||
.sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
|
||||
.big_pages = params.page_size != VM::YUZU_PAGESIZE,
|
||||
@@ -199,71 +200,52 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
|
||||
}
|
||||
|
||||
void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
|
||||
auto mapping{mapping_map.at(offset)};
|
||||
|
||||
if (!mapping->fixed) {
|
||||
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
|
||||
u64 aligned_size{Common::AlignUp(mapping->size, page_size)};
|
||||
|
||||
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
|
||||
static_cast<u32>(aligned_size >> page_size_bits));
|
||||
auto const it = mapping_map.find(offset);
|
||||
auto const mapping = it->second;
|
||||
if (!mapping.fixed) {
|
||||
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
u32 page_size{mapping.big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
|
||||
u64 aligned_size{Common::AlignUp(mapping.size, page_size)};
|
||||
allocator.Free(u32(mapping.offset >> page_size_bits), u32(aligned_size >> page_size_bits));
|
||||
}
|
||||
|
||||
nvmap.UnpinHandle(mapping->handle);
|
||||
|
||||
nvmap.UnpinHandle(mapping.handle);
|
||||
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
|
||||
// Only FreeSpace can unmap them fully
|
||||
if (mapping->sparse_alloc) {
|
||||
gmmu->MapSparse(offset, mapping->size, mapping->big_page);
|
||||
if (mapping.sparse_alloc) {
|
||||
gmmu->MapSparse(offset, mapping.size, mapping.big_page);
|
||||
} else {
|
||||
gmmu->Unmap(offset, mapping->size);
|
||||
gmmu->Unmap(offset, mapping.size);
|
||||
}
|
||||
|
||||
mapping_map.erase(offset);
|
||||
mapping_map.erase(it);
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
|
||||
params.pages, params.page_size);
|
||||
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset, params.pages, params.page_size);
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
try {
|
||||
auto allocation{allocation_map[params.offset]};
|
||||
|
||||
if (allocation.page_size != params.page_size ||
|
||||
allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
|
||||
if (auto const it = allocation_map.find(params.offset); it != allocation_map.end()) {
|
||||
auto const allocation = it->second;
|
||||
if (allocation.page_size != params.page_size || allocation.size != (u64(params.pages) * params.page_size))
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
for (const auto& mapping : allocation.mappings) {
|
||||
FreeMappingLocked(mapping->offset);
|
||||
}
|
||||
for (const auto mapping_offset : allocation.mappings)
|
||||
FreeMappingLocked(mapping_offset);
|
||||
|
||||
// Unset sparse flag if required
|
||||
if (allocation.sparse) {
|
||||
if (allocation.sparse)
|
||||
gmmu->Unmap(params.offset, allocation.size);
|
||||
}
|
||||
|
||||
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
|
||||
: *vm.big_page_allocator};
|
||||
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
|
||||
: vm.big_page_size_bits};
|
||||
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator : *vm.big_page_allocator};
|
||||
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS : vm.big_page_size_bits};
|
||||
|
||||
allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
|
||||
static_cast<u32>(allocation.size >> page_size_bits));
|
||||
allocator.Free(u32(params.offset >> page_size_bits), u32(allocation.size >> page_size_bits));
|
||||
allocation_map.erase(params.offset);
|
||||
} catch (const std::out_of_range&) {
|
||||
return NvResult::BadValue;
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
return NvResult::Success;
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) {
|
||||
@@ -327,26 +309,18 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
|
||||
// Remaps a subregion of an existing mapping to a different PA
|
||||
if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
|
||||
try {
|
||||
auto mapping{mapping_map.at(params.offset)};
|
||||
|
||||
if (mapping->size < params.mapping_size) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"Cannot remap a partially mapped GPU address space region: {:#X}",
|
||||
params.offset);
|
||||
if (auto const it = mapping_map.find(params.offset); it != mapping_map.end()) {
|
||||
auto const mapping = it->second;
|
||||
if (mapping.size < params.mapping_size) {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#X}", params.offset);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
|
||||
VAddr device_address{mapping->ptr + params.buffer_offset};
|
||||
|
||||
gmmu->Map(gpu_address, device_address, params.mapping_size,
|
||||
static_cast<Tegra::PTEKind>(params.kind), mapping->big_page);
|
||||
|
||||
u64 gpu_address = u64(params.offset + params.buffer_offset);
|
||||
VAddr device_address{mapping.ptr + params.buffer_offset};
|
||||
gmmu->Map(gpu_address, device_address, params.mapping_size, Tegra::PTEKind(params.kind), mapping.big_page);
|
||||
return NvResult::Success;
|
||||
} catch (const std::out_of_range&) {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}",
|
||||
params.offset);
|
||||
} else {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}", params.offset);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
}
|
||||
@@ -356,8 +330,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
DAddr device_address{
|
||||
static_cast<DAddr>(nvmap.PinHandle(params.handle, false) + params.buffer_offset)};
|
||||
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset);
|
||||
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
|
||||
|
||||
bool big_page{[&]() {
|
||||
@@ -381,32 +354,22 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
}
|
||||
|
||||
const bool use_big_pages = alloc->second.big_pages && big_page;
|
||||
gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind),
|
||||
use_big_pages);
|
||||
gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind), use_big_pages);
|
||||
|
||||
auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
|
||||
true, use_big_pages, alloc->second.sparse)};
|
||||
alloc->second.mappings.push_back(mapping);
|
||||
mapping_map[params.offset] = mapping;
|
||||
alloc->second.mappings.push_back(params.offset);
|
||||
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, true, use_big_pages, alloc->second.sparse));
|
||||
} else {
|
||||
auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
|
||||
u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
|
||||
params.offset = static_cast<u64>(allocator.Allocate(
|
||||
static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
|
||||
<< page_size_bits;
|
||||
params.offset = u64(allocator.Allocate(u32(Common::AlignUp(size, page_size) >> page_size_bits))) << page_size_bits;
|
||||
if (!params.offset) {
|
||||
ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
|
||||
return NvResult::InsufficientMemory;
|
||||
}
|
||||
|
||||
gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size),
|
||||
static_cast<Tegra::PTEKind>(params.kind), big_page);
|
||||
|
||||
auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
|
||||
false, big_page, false)};
|
||||
mapping_map[params.offset] = mapping;
|
||||
gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size), Tegra::PTEKind(params.kind), big_page);
|
||||
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false));
|
||||
}
|
||||
|
||||
map_buffer_offsets.insert(params.offset);
|
||||
@@ -415,37 +378,32 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
|
||||
if (map_buffer_offsets.find(params.offset) != map_buffer_offsets.end()) {
|
||||
std::scoped_lock lock(mutex);
|
||||
if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
auto mapping{mapping_map.at(params.offset)};
|
||||
|
||||
if (!mapping->fixed) {
|
||||
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
|
||||
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
|
||||
static_cast<u32>(mapping->size >> page_size_bits));
|
||||
auto const it = mapping_map.find(params.offset);
|
||||
auto const mapping = it->second;
|
||||
if (!mapping.fixed) {
|
||||
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
allocator.Free(u32(mapping.offset >> page_size_bits), u32(mapping.size >> page_size_bits));
|
||||
}
|
||||
|
||||
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
|
||||
// Only FreeSpace can unmap them fully
|
||||
if (mapping->sparse_alloc) {
|
||||
gmmu->MapSparse(params.offset, mapping->size, mapping->big_page);
|
||||
if (mapping.sparse_alloc) {
|
||||
gmmu->MapSparse(params.offset, mapping.size, mapping.big_page);
|
||||
} else {
|
||||
gmmu->Unmap(params.offset, mapping->size);
|
||||
gmmu->Unmap(params.offset, mapping.size);
|
||||
}
|
||||
|
||||
nvmap.UnpinHandle(mapping->handle);
|
||||
|
||||
mapping_map.erase(params.offset);
|
||||
map_buffer_offsets.erase(params.offset);
|
||||
nvmap.UnpinHandle(mapping.handle);
|
||||
mapping_map.erase(it);
|
||||
map_buffer_offsets.erase(offset_it);
|
||||
}
|
||||
return NvResult::Success;
|
||||
}
|
||||
@@ -478,8 +436,7 @@ void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::GetVARegions1(IoctlGetVaRegions& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
||||
params.buf_size);
|
||||
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr, params.buf_size);
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
|
||||
@@ -165,35 +165,36 @@ private:
|
||||
NvCore::NvMap& nvmap;
|
||||
|
||||
struct Mapping {
|
||||
NvCore::NvMap::Handle::Id handle;
|
||||
DAddr ptr;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
bool fixed;
|
||||
bool big_page; // Only valid if fixed == false
|
||||
bool sparse_alloc;
|
||||
NvCore::NvMap::Handle::Id handle;
|
||||
bool fixed : 1;
|
||||
bool big_page : 1; // Only valid if fixed == false
|
||||
bool sparse_alloc : 1;
|
||||
|
||||
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_,
|
||||
bool big_page_, bool sparse_alloc_)
|
||||
: handle(handle_), ptr(ptr_), offset(offset_), size(size_), fixed(fixed_),
|
||||
big_page(big_page_), sparse_alloc(sparse_alloc_) {}
|
||||
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
|
||||
: ptr(ptr_), offset(offset_), size(size_), handle(handle_)
|
||||
, fixed(fixed_), big_page(big_page_), sparse_alloc(sparse_alloc_)
|
||||
{}
|
||||
};
|
||||
|
||||
struct Allocation {
|
||||
std::vector<u64> mappings;
|
||||
u64 size;
|
||||
std::list<std::shared_ptr<Mapping>> mappings;
|
||||
u32 page_size;
|
||||
bool sparse;
|
||||
bool big_pages;
|
||||
};
|
||||
|
||||
std::map<u64, std::shared_ptr<Mapping>>
|
||||
mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
|
||||
//!< mapping type, this is needed as what was originally a single buffer may
|
||||
//!< have been split into multiple GPU side buffers with the remap flag.
|
||||
std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
|
||||
//!< which fixed buffers can be mapped into
|
||||
std::mutex mutex; //!< Locks all AS operations
|
||||
//!< This maps the base addresses of mapped buffers to their total sizes and
|
||||
//!< mapping type, this is needed as what was originally a single buffer may
|
||||
//!< have been split into multiple GPU side buffers with the remap flag.
|
||||
std::map<u64, Mapping> mapping_map;
|
||||
//!< Holds allocations created by AllocSpace from
|
||||
//!< which fixed buffers can be mapped into
|
||||
std::map<u64, Allocation> allocation_map;
|
||||
std::mutex mutex; //!< Locks all AS operations
|
||||
|
||||
struct VM {
|
||||
static constexpr u32 YUZU_PAGESIZE{0x1000};
|
||||
@@ -213,9 +214,8 @@ private:
|
||||
|
||||
using Allocator = Common::FlatAllocator<u32, 0, 32>;
|
||||
|
||||
std::unique_ptr<Allocator> big_page_allocator;
|
||||
std::shared_ptr<Allocator>
|
||||
small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
|
||||
std::optional<Allocator> big_page_allocator;
|
||||
std::optional<Allocator> small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
|
||||
|
||||
bool initialised{};
|
||||
} vm;
|
||||
|
||||
@@ -26,8 +26,11 @@ namespace Service::android {
|
||||
BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
|
||||
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
||||
Service::Nvidia::NvCore::NvMap& nvmap_)
|
||||
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
|
||||
clock{Common::CreateOptimalClock()}, nvmap(nvmap_) {
|
||||
: service_context{service_context_}, core{std::move(buffer_queue_core_)}
|
||||
, slots(core->slots)
|
||||
, clock{Common::CreateOptimalClock()}
|
||||
, nvmap(nvmap_)
|
||||
{
|
||||
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
|
||||
}
|
||||
|
||||
@@ -485,7 +488,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
slots[slot].buffer_state = BufferState::Queued;
|
||||
slots[slot].frame_number = core->frame_counter;
|
||||
slots[slot].queue_time = timestamp;
|
||||
slots[slot].presentation_time = clock->GetTimeNS().count();
|
||||
slots[slot].presentation_time = clock.GetTimeNS().count();
|
||||
slots[slot].fence = fence;
|
||||
|
||||
item.slot = slot;
|
||||
|
||||
@@ -89,8 +89,7 @@ private:
|
||||
s32 next_callback_ticket{};
|
||||
s32 current_callback_ticket{};
|
||||
std::condition_variable_any callback_condition;
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
|
||||
Common::WallClock clock;
|
||||
Service::Nvidia::NvCore::NvMap& nvmap;
|
||||
};
|
||||
|
||||
|
||||
@@ -54,13 +54,14 @@ enum class NetDbError : s32 {
|
||||
};
|
||||
|
||||
static const constexpr std::array blockedDomains = {
|
||||
"srv.nintendo.net", //obvious
|
||||
"phoenix-api.wbagora.com", //hogwarts legacy
|
||||
"srv.nintendo.net", // Obvious
|
||||
"phoenix-api.wbagora.com", // Hogwarts legacy
|
||||
"battle.net",
|
||||
"microsoft.com", //minecraft dungeons + other games
|
||||
"microsoft.com", // Minecraft dungeons + other games
|
||||
"mojang.com",
|
||||
"xboxlive.com",
|
||||
"minecraftservices.com"
|
||||
"minecraftservices.com",
|
||||
"508223012e5a5ff19f30a391b2bdadc0.my.2k.com", // Civilization 5
|
||||
};
|
||||
|
||||
static bool IsBlockedHost(const std::string& host) {
|
||||
|
||||
@@ -228,9 +228,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
code_size += patch_ctx.GetTotalPatchSize();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
|
||||
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) {
|
||||
|
||||
@@ -89,9 +89,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
|
||||
codeset.DataSegment().size += kip->GetBSSSize();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
|
||||
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) {
|
||||
|
||||
@@ -242,9 +242,8 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
}();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue()
|
||||
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
|
||||
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
|
||||
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process
|
||||
|
||||
@@ -169,8 +169,6 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
backend/x64/emit_x64_vector.cpp
|
||||
backend/x64/emit_x64_vector_floating_point.cpp
|
||||
backend/x64/emit_x64_vector_saturation.cpp
|
||||
backend/x64/exclusive_monitor.cpp
|
||||
backend/x64/exclusive_monitor_friend.h
|
||||
backend/x64/host_feature.h
|
||||
backend/x64/hostloc.h
|
||||
backend/x64/jitstate_info.h
|
||||
@@ -231,7 +229,6 @@ if ("arm64" IN_LIST ARCHITECTURE)
|
||||
backend/arm64/emit_arm64_vector_floating_point.cpp
|
||||
backend/arm64/emit_arm64_vector_saturation.cpp
|
||||
backend/arm64/emit_context.h
|
||||
backend/arm64/exclusive_monitor.cpp
|
||||
backend/arm64/fastmem.h
|
||||
backend/arm64/fpsr_manager.cpp
|
||||
backend/arm64/fpsr_manager.h
|
||||
@@ -278,7 +275,6 @@ if ("riscv64" IN_LIST ARCHITECTURE)
|
||||
backend/riscv64/emit_riscv64_vector.cpp
|
||||
backend/riscv64/emit_riscv64.cpp
|
||||
backend/riscv64/emit_riscv64.h
|
||||
backend/riscv64/exclusive_monitor.cpp
|
||||
backend/riscv64/reg_alloc.cpp
|
||||
backend/riscv64/reg_alloc.h
|
||||
backend/riscv64/stack_layout.h
|
||||
@@ -367,7 +363,6 @@ if (BOOST_NO_HEADERS)
|
||||
else()
|
||||
target_link_libraries(dynarmic PRIVATE Boost::headers)
|
||||
endif()
|
||||
|
||||
if (DYNARMIC_USE_LLVM)
|
||||
target_include_directories(dynarmic PRIVATE ${LLVM_INCLUDE_DIRS})
|
||||
target_compile_definitions(dynarmic PRIVATE DYNARMIC_USE_LLVM=1 ${LLVM_DEFINITIONS})
|
||||
@@ -385,4 +380,7 @@ endif()
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
target_compile_definitions(dynarmic PRIVATE FMT_USE_WINDOWS_H=0)
|
||||
endif()
|
||||
if (NOT DEFINED xbyak_ADDED)
|
||||
target_compile_definitions(dynarmic PRIVATE XBYAK_BUNDLED=1)
|
||||
endif()
|
||||
target_compile_definitions(dynarmic PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
|
||||
|
||||
@@ -135,12 +135,9 @@ static void* EmitExclusiveWriteCallTrampoline(oaknut::CodeGenerator& code, const
|
||||
oaknut::Label l_addr, l_this;
|
||||
|
||||
auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, T value) -> u32 {
|
||||
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr,
|
||||
[&](T expected) -> bool {
|
||||
return (conf.callbacks->*callback)(vaddr, value, expected);
|
||||
})
|
||||
? 0
|
||||
: 1;
|
||||
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr, [&](T expected) -> bool {
|
||||
return (conf.callbacks->*callback)(vaddr, value, expected);
|
||||
}) ? 0 : 1;
|
||||
};
|
||||
|
||||
void* target = code.xptr<void*>();
|
||||
@@ -300,12 +297,9 @@ static void* EmitExclusiveWrite128CallTrampoline(oaknut::CodeGenerator& code, co
|
||||
oaknut::Label l_addr, l_this;
|
||||
|
||||
auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, Vector value) -> u32 {
|
||||
return conf.global_monitor->DoExclusiveOperation<Vector>(conf.processor_id, vaddr,
|
||||
[&](Vector expected) -> bool {
|
||||
return conf.callbacks->MemoryWriteExclusive128(vaddr, value, expected);
|
||||
})
|
||||
? 0
|
||||
: 1;
|
||||
return conf.global_monitor->DoExclusiveOperation<Vector>(conf.processor_id, vaddr, [&](Vector expected) -> bool {
|
||||
return conf.callbacks->MemoryWriteExclusive128(vaddr, value, expected);
|
||||
}) ? 0 : 1;
|
||||
};
|
||||
|
||||
void* target = code.xptr<void*>();
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2022 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
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
|
||||
@@ -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(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(size_t processor_id) {
|
||||
Lock();
|
||||
exclusive_addresses[processor_id] = INVALID_EXCLUSIVE_ADDRESS;
|
||||
Unlock();
|
||||
}
|
||||
|
||||
} // namespace Dynarmic
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "dynarmic/backend/x64/abi.h"
|
||||
#include "dynarmic/backend/x64/devirtualize.h"
|
||||
#include "dynarmic/backend/x64/emit_x64_memory.h"
|
||||
#include "dynarmic/backend/x64/exclusive_monitor_friend.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic/backend/x64/perf_map.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
|
||||
@@ -174,67 +174,35 @@ void A32EmitX64::EmitA32ClearExclusive(A32EmitContext&, IR::Inst*) {
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory8(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory16(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory32(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory64(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveWriteMemory8(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveWriteMemory16(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveWriteMemory32(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveWriteMemory64(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitCheckMemoryAbort(A32EmitContext& ctx, IR::Inst* inst, Xbyak::Label* end) {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "dynarmic/backend/x64/abi.h"
|
||||
#include "dynarmic/backend/x64/devirtualize.h"
|
||||
#include "dynarmic/backend/x64/emit_x64_memory.h"
|
||||
#include "dynarmic/backend/x64/exclusive_monitor_friend.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic/backend/x64/perf_map.h"
|
||||
#include "dynarmic/common/spin_lock_x64.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
@@ -330,83 +330,43 @@ void A64EmitX64::EmitA64ClearExclusive(A64EmitContext&, IR::Inst*) {
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory8(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory16(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory32(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory64(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory128(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveReadMemoryInline<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory8(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory16(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory32(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory64(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory128(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst);
|
||||
}
|
||||
EmitExclusiveWriteMemoryInline<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitCheckMemoryAbort(A64EmitContext&, IR::Inst* inst, Xbyak::Label* end) {
|
||||
|
||||
@@ -519,10 +519,6 @@ void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) {
|
||||
}
|
||||
}
|
||||
|
||||
Xbyak::Address BlockOfCode::Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
|
||||
return constant_pool.GetConstant(frame, lower, upper);
|
||||
}
|
||||
|
||||
CodePtr BlockOfCode::GetCodeBegin() const {
|
||||
return code_begin;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "dynarmic/common/cast_util.h"
|
||||
#include "dynarmic/interface/halt_reason.h"
|
||||
#include "dynarmic/ir/cond.h"
|
||||
#include "xbyak/xbyak.h"
|
||||
|
||||
namespace Dynarmic::Backend::X64 {
|
||||
|
||||
@@ -101,8 +102,8 @@ public:
|
||||
}
|
||||
|
||||
/// Code emitter: Calls the lambda. Lambda must not have any captures.
|
||||
template<typename Lambda>
|
||||
void CallLambda(Lambda l) {
|
||||
template<typename F>
|
||||
void CallLambda(F l) {
|
||||
CallFunction(Common::FptrCast(l));
|
||||
}
|
||||
|
||||
@@ -124,22 +125,35 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
Xbyak::Address Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
|
||||
|
||||
template<size_t esize>
|
||||
Xbyak::Address BConst(const Xbyak::AddressFrame& frame, u64 value) {
|
||||
return Const(frame, mcl::bit::replicate_element<u64>(esize, value),
|
||||
mcl::bit::replicate_element<u64>(esize, value));
|
||||
// Xbyak benefits slightly from not having & references on some of its ctors, however one main
|
||||
// disadvantage is that this breaks ABI slightly because we need to hijack the main privated ctor, hence
|
||||
// we define two paths, one for gentoo (non bundled) and one for our custom bundled set with patch
|
||||
#ifdef XBYAK_BUNDLED
|
||||
[[nodiscard]] Xbyak::Address Const(const Xbyak::AddressFrame frame, u64 lower, u64 upper = 0) {
|
||||
return constant_pool.GetConstant(Xbyak::AddressFrame(frame.bit_, frame.broadcast_), lower, upper);
|
||||
}
|
||||
template<size_t esize>
|
||||
[[nodiscard]] Xbyak::Address BConst(const Xbyak::AddressFrame frame, u64 value) {
|
||||
return Const(Xbyak::AddressFrame(frame.bit_, frame.broadcast_), mcl::bit::replicate_element<u64>(esize, value), mcl::bit::replicate_element<u64>(esize, value));
|
||||
}
|
||||
#else
|
||||
[[nodiscard]] Xbyak::Address Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0) {
|
||||
return constant_pool.GetConstant(frame, lower, upper);
|
||||
}
|
||||
template<size_t esize>
|
||||
[[nodiscard]] Xbyak::Address BConst(const Xbyak::AddressFrame& frame, u64 value) {
|
||||
return Const(frame, mcl::bit::replicate_element<u64>(esize, value), mcl::bit::replicate_element<u64>(esize, value));
|
||||
}
|
||||
#endif
|
||||
|
||||
CodePtr GetCodeBegin() const;
|
||||
size_t GetTotalCodeSize() const;
|
||||
|
||||
const void* GetReturnFromRunCodeAddress() const {
|
||||
[[nodiscard]] const void* GetReturnFromRunCodeAddress() const {
|
||||
return return_from_run_code[0];
|
||||
}
|
||||
|
||||
const void* GetForceReturnFromRunCodeAddress() const {
|
||||
[[nodiscard]] const void* GetForceReturnFromRunCodeAddress() const {
|
||||
return return_from_run_code[FORCE_RETURN];
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,11 @@ ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
|
||||
reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size);
|
||||
}
|
||||
|
||||
#ifdef XBYAK_BUNDLED
|
||||
Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame frame, u64 lower, u64 upper) {
|
||||
#else
|
||||
Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
|
||||
#endif
|
||||
const auto constant = ConstantT(lower, upper);
|
||||
auto iter = constant_info.find(constant);
|
||||
if (iter == constant_info.end()) {
|
||||
|
||||
@@ -29,7 +29,11 @@ class ConstantPool final {
|
||||
public:
|
||||
ConstantPool(BlockOfCode& code, size_t size);
|
||||
|
||||
#ifdef XBYAK_BUNDLED
|
||||
Xbyak::Address GetConstant(const Xbyak::AddressFrame frame, u64 lower, u64 upper = 0);
|
||||
#else
|
||||
Xbyak::Address GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
|
||||
#endif
|
||||
|
||||
private:
|
||||
static constexpr size_t align_size = 16; // bytes
|
||||
|
||||
@@ -230,12 +230,11 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
code.CallLambda(
|
||||
[](AxxUserConfig& conf, Axx::VAddr vaddr) -> T {
|
||||
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr) -> T {
|
||||
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
});
|
||||
code.ZeroExtendFrom(bitsize, code.ABI_RETURN);
|
||||
} else {
|
||||
const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code);
|
||||
@@ -250,12 +249,11 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
code.CallLambda(
|
||||
[](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) {
|
||||
ret = conf.global_monitor->ReadAndMark<Vector>(conf.processor_id, vaddr, [&]() -> Vector {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) {
|
||||
ret = conf.global_monitor->ReadAndMark<Vector>(conf.processor_id, vaddr, [&]() -> Vector {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
});
|
||||
code.movups(result, xword[rsp + ABI_SHADOW_SPACE]);
|
||||
ctx.reg_alloc.ReleaseStackSpace(code, 16 + ABI_SHADOW_SPACE);
|
||||
|
||||
@@ -320,11 +318,12 @@ void AxxEmitX64::EmitExclusiveWriteMemory(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
|
||||
template<std::size_t bitsize, auto callback>
|
||||
void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
ASSERT(conf.global_monitor && conf.fastmem_pointer);
|
||||
if (!exception_handler.SupportsFastmem()) {
|
||||
ASSERT(conf.global_monitor);
|
||||
if (!conf.fastmem_exclusive_access || !exception_handler.SupportsFastmem()) {
|
||||
EmitExclusiveReadMemory<bitsize, callback>(ctx, inst);
|
||||
return;
|
||||
}
|
||||
ASSERT(conf.fastmem_pointer);
|
||||
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
constexpr bool ordered = true;
|
||||
@@ -344,10 +343,10 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
|
||||
|
||||
const auto wrapped_fn = read_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value_idx)];
|
||||
|
||||
EmitExclusiveLock(code, conf, tmp, tmp2.cvt32());
|
||||
EmitExclusiveLock(code, conf, tmp2.cvt32());
|
||||
|
||||
code.mov(code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)], u8(1));
|
||||
code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, conf.processor_id)));
|
||||
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + conf.processor_id));
|
||||
code.mov(qword[tmp], vaddr);
|
||||
|
||||
const auto fastmem_marker = ShouldFastmem(ctx, inst);
|
||||
@@ -381,10 +380,10 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
|
||||
code.call(wrapped_fn);
|
||||
}
|
||||
|
||||
code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorValuePointer(conf.global_monitor, conf.processor_id)));
|
||||
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_values.data() + conf.processor_id));
|
||||
EmitWriteMemoryMov<bitsize>(code, tmp, value_idx, false);
|
||||
|
||||
EmitExclusiveUnlock(code, conf, tmp, tmp2.cvt32());
|
||||
EmitExclusiveUnlock(code, conf, tmp2.cvt32());
|
||||
|
||||
if constexpr (bitsize == 128) {
|
||||
ctx.reg_alloc.DefineValue(code, inst, Xbyak::Xmm{value_idx});
|
||||
@@ -397,11 +396,12 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
|
||||
|
||||
template<std::size_t bitsize, auto callback>
|
||||
void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
ASSERT(conf.global_monitor && conf.fastmem_pointer);
|
||||
if (!exception_handler.SupportsFastmem()) {
|
||||
ASSERT(conf.global_monitor);
|
||||
if (!conf.fastmem_exclusive_access || !exception_handler.SupportsFastmem()) {
|
||||
EmitExclusiveWriteMemory<bitsize, callback>(ctx, inst);
|
||||
return;
|
||||
}
|
||||
ASSERT(conf.fastmem_pointer);
|
||||
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
constexpr bool ordered = true;
|
||||
@@ -425,7 +425,7 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
|
||||
|
||||
const auto wrapped_fn = exclusive_write_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value.getIdx())];
|
||||
|
||||
EmitExclusiveLock(code, conf, tmp, tmp2.cvt32());
|
||||
EmitExclusiveLock(code, conf, tmp2.cvt32());
|
||||
|
||||
SharedLabel end = ctx.GenSharedLabel();
|
||||
|
||||
@@ -433,14 +433,14 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
|
||||
code.movzx(tmp.cvt32(), code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)]);
|
||||
code.test(tmp.cvt8(), tmp.cvt8());
|
||||
code.je(*end, code.T_NEAR);
|
||||
code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, conf.processor_id)));
|
||||
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + conf.processor_id));
|
||||
code.cmp(qword[tmp], vaddr);
|
||||
code.jne(*end, code.T_NEAR);
|
||||
|
||||
EmitExclusiveTestAndClear(code, conf, vaddr, tmp, rax);
|
||||
|
||||
code.mov(code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)], u8(0));
|
||||
code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorValuePointer(conf.global_monitor, conf.processor_id)));
|
||||
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_values.data() + conf.processor_id));
|
||||
|
||||
if constexpr (bitsize == 128) {
|
||||
code.mov(rax, qword[tmp + 0]);
|
||||
@@ -519,7 +519,7 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
|
||||
}
|
||||
|
||||
code.L(*end);
|
||||
EmitExclusiveUnlock(code, conf, tmp, eax);
|
||||
EmitExclusiveUnlock(code, conf, eax);
|
||||
ctx.reg_alloc.DefineValue(code, inst, status);
|
||||
EmitCheckMemoryAbort(ctx, inst);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "dynarmic/backend/x64/a32_emit_x64.h"
|
||||
#include "dynarmic/backend/x64/a64_emit_x64.h"
|
||||
#include "dynarmic/backend/x64/exclusive_monitor_friend.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic/common/spin_lock_x64.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic/ir/acc_type.h"
|
||||
@@ -344,43 +344,36 @@ const void* EmitWriteMemoryMov(BlockOfCode& code, const Xbyak::RegExp& addr, int
|
||||
}
|
||||
|
||||
template<typename UserConfig>
|
||||
void EmitExclusiveLock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 pointer, Xbyak::Reg32 tmp) {
|
||||
if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
return;
|
||||
void EmitExclusiveLock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg32 tmp) {
|
||||
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
u64 const slp = std::bit_cast<u64>(std::addressof(conf.global_monitor->lock.storage));
|
||||
EmitSpinLockLock(code, dword[slp], tmp, code.HasHostFeature(HostFeature::WAITPKG));
|
||||
}
|
||||
|
||||
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorLockPointer(conf.global_monitor)));
|
||||
EmitSpinLockLock(code, pointer, tmp, code.HasHostFeature(HostFeature::WAITPKG));
|
||||
}
|
||||
|
||||
template<typename UserConfig>
|
||||
void EmitExclusiveUnlock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 pointer, Xbyak::Reg32 tmp) {
|
||||
if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
return;
|
||||
void EmitExclusiveUnlock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg32 tmp) {
|
||||
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
u64 const slp = std::bit_cast<u64>(std::addressof(conf.global_monitor->lock.storage));
|
||||
EmitSpinLockUnlock(code, dword[slp], tmp);
|
||||
}
|
||||
|
||||
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorLockPointer(conf.global_monitor)));
|
||||
EmitSpinLockUnlock(code, pointer, tmp);
|
||||
}
|
||||
|
||||
template<typename UserConfig>
|
||||
void EmitExclusiveTestAndClear(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 vaddr, Xbyak::Reg64 pointer, Xbyak::Reg64 tmp) {
|
||||
if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
code.mov(tmp, 0xDEAD'DEAD'DEAD'DEAD);
|
||||
const size_t processor_count = GetExclusiveMonitorProcessorCount(conf.global_monitor);
|
||||
for (size_t processor_index = 0; processor_index < processor_count; processor_index++) {
|
||||
if (processor_index == conf.processor_id) {
|
||||
continue;
|
||||
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
code.mov(tmp, 0xDEAD'DEAD'DEAD'DEAD);
|
||||
static_assert(ExclusiveMonitor::MAX_NUM_CPU_CORES == 4);
|
||||
for (size_t i = 0; i < ExclusiveMonitor::MAX_NUM_CPU_CORES; i++) {
|
||||
if (i != conf.processor_id) {
|
||||
Xbyak::Label ok;
|
||||
code.mov(pointer, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + i));
|
||||
code.cmp(qword[pointer], vaddr);
|
||||
code.jne(ok, code.T_NEAR);
|
||||
code.mov(qword[pointer], tmp);
|
||||
code.L(ok);
|
||||
}
|
||||
}
|
||||
Xbyak::Label ok;
|
||||
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, processor_index)));
|
||||
code.cmp(qword[pointer], vaddr);
|
||||
code.jne(ok, code.T_NEAR);
|
||||
code.mov(qword[pointer], tmp);
|
||||
code.L(ok);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -595,7 +595,8 @@ void EmitX64::EmitPackedHalvingSubAddS16(EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitPackedSubAdd(code, ctx, inst, false, true, true);
|
||||
}
|
||||
|
||||
static void EmitPackedOperation(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, void (Xbyak::CodeGenerator::*fn)(const Xbyak::Mmx& mmx, const Xbyak::Operand&)) {
|
||||
template<typename F>
|
||||
static void EmitPackedOperation(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, F&& fn) {
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
|
||||
const Xbyak::Xmm xmm_a = ctx.reg_alloc.UseScratchXmm(code, args[0]);
|
||||
|
||||
@@ -23,7 +23,8 @@ using namespace Xbyak::util;
|
||||
|
||||
namespace {
|
||||
|
||||
void EmitVectorSaturatedNative(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, void (Xbyak::CodeGenerator::*saturated_fn)(const Xbyak::Mmx& mmx, const Xbyak::Operand&), void (Xbyak::CodeGenerator::*unsaturated_fn)(const Xbyak::Mmx& mmx, const Xbyak::Operand&), void (Xbyak::CodeGenerator::*sub_fn)(const Xbyak::Mmx& mmx, const Xbyak::Operand&)) {
|
||||
template<typename F1, typename F2, typename F3>
|
||||
static void EmitVectorSaturatedNative(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, F1&& saturated_fn, F2&& unsaturated_fn, F3&& sub_fn) {
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
|
||||
const Xbyak::Xmm result = ctx.reg_alloc.UseScratchXmm(code, args[0]);
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <xbyak/xbyak.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "dynarmic/backend/x64/xbyak.h"
|
||||
|
||||
@@ -33,7 +33,8 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32
|
||||
code.push(Xbyak::util::rdx);
|
||||
// TODO: This clobbers EAX and EDX did we tell the regalloc?
|
||||
// ARM ptr for address-monitoring
|
||||
code.umonitor(ptr);
|
||||
code.mov(Xbyak::util::eax, ptr);
|
||||
code.umonitor(Xbyak::util::eax);
|
||||
// tmp.bit[0] = 0: C0.1 | Slow Wakup | Better Savings
|
||||
// tmp.bit[0] = 1: C0.2 | Fast Wakup | Lesser Savings
|
||||
// edx:eax is implicitly used as a 64-bit deadline timestamp
|
||||
@@ -60,9 +61,10 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32
|
||||
code.jnz(loop, code.T_NEAR);
|
||||
}
|
||||
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp) {
|
||||
// ptr operand must be a dword[ptr]
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp) {
|
||||
code.xor_(tmp, tmp);
|
||||
code.xchg(code.dword[ptr], tmp);
|
||||
code.xchg(ptr, tmp);
|
||||
code.mfence();
|
||||
}
|
||||
|
||||
@@ -85,11 +87,12 @@ void SpinLockImpl::Initialize() noexcept {
|
||||
Xbyak::Reg64 const ABI_PARAM1 = Backend::X64::HostLocToReg64(Backend::X64::ABI_PARAM1);
|
||||
code.align();
|
||||
lock = code.getCurr<void (*)(volatile int*)>();
|
||||
EmitSpinLockLock(code, ABI_PARAM1, code.eax, false);
|
||||
EmitSpinLockLock(code, code.dword[ABI_PARAM1], code.eax, false);
|
||||
code.ret();
|
||||
|
||||
code.align();
|
||||
unlock = code.getCurr<void (*)(volatile int*)>();
|
||||
EmitSpinLockUnlock(code, ABI_PARAM1, code.eax);
|
||||
EmitSpinLockUnlock(code, code.dword[ABI_PARAM1], code.eax);
|
||||
code.ret();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace Dynarmic {
|
||||
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg);
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp);
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp, bool waitpkg);
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp);
|
||||
|
||||
} // namespace Dynarmic
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -20,68 +23,71 @@ using Vector = std::array<std::uint64_t, 2>;
|
||||
|
||||
class ExclusiveMonitor {
|
||||
public:
|
||||
/// @param processor_count Maximum number of processors using this global
|
||||
/// exclusive monitor. Each processor must have a
|
||||
/// unique id.
|
||||
explicit ExclusiveMonitor(size_t processor_count);
|
||||
|
||||
size_t GetProcessorCount() const;
|
||||
explicit ExclusiveMonitor() noexcept {
|
||||
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
|
||||
}
|
||||
|
||||
/// Marks a region containing [address, address+size) to be exclusive to
|
||||
/// processor processor_id.
|
||||
template<typename T, typename Function>
|
||||
T ReadAndMark(size_t processor_id, VAddr address, Function op) {
|
||||
/// processor index.
|
||||
template<typename T, typename F>
|
||||
[[nodiscard]] inline T ReadAndMark(std::size_t index, VAddr address, F f) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
|
||||
|
||||
Lock();
|
||||
exclusive_addresses[processor_id] = masked_address;
|
||||
const T value = op();
|
||||
std::memcpy(exclusive_values[processor_id].data(), &value, sizeof(T));
|
||||
Unlock();
|
||||
lock.Lock();
|
||||
exclusive_addresses[index] = masked_address;
|
||||
T const value = f();
|
||||
std::memcpy(exclusive_values[index].data(), std::addressof(value), sizeof(T));
|
||||
lock.Unlock();
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Checks to see if processor processor_id has exclusive access to the
|
||||
[[nodiscard]] inline bool CheckAndClear(std::size_t index, VAddr address) {
|
||||
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
|
||||
if (exclusive_addresses[index] != masked_address)
|
||||
return false;
|
||||
for (VAddr& other_address : exclusive_addresses)
|
||||
if (other_address == masked_address)
|
||||
other_address = INVALID_EXCLUSIVE_ADDRESS;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Checks to see if processor index has exclusive access to the
|
||||
/// specified region. If it does, executes the operation then clears
|
||||
/// the exclusive state for processors if their exclusive region(s)
|
||||
/// contain [address, address+size).
|
||||
template<typename T, typename Function>
|
||||
bool DoExclusiveOperation(size_t processor_id, VAddr address, Function op) {
|
||||
template<typename T, typename F>
|
||||
[[nodiscard]] inline bool DoExclusiveOperation(std::size_t index, VAddr address, F&& f) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
if (!CheckAndClear(processor_id, address)) {
|
||||
return false;
|
||||
bool result = false;
|
||||
lock.Lock();
|
||||
if (CheckAndClear(index, address)) {
|
||||
T saved_value{};
|
||||
std::memcpy(std::addressof(saved_value), exclusive_values[index].data(), sizeof(T));
|
||||
result = f(saved_value);
|
||||
}
|
||||
|
||||
T saved_value;
|
||||
std::memcpy(&saved_value, exclusive_values[processor_id].data(), sizeof(T));
|
||||
const bool result = op(saved_value);
|
||||
|
||||
Unlock();
|
||||
lock.Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Unmark everything.
|
||||
void Clear();
|
||||
inline void Clear() {
|
||||
lock.Lock();
|
||||
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
|
||||
lock.Unlock();
|
||||
}
|
||||
|
||||
/// Unmark processor id
|
||||
void ClearProcessor(size_t processor_id);
|
||||
|
||||
private:
|
||||
bool CheckAndClear(size_t processor_id, VAddr address);
|
||||
|
||||
void Lock();
|
||||
void Unlock();
|
||||
|
||||
friend volatile int* GetExclusiveMonitorLockPointer(ExclusiveMonitor*);
|
||||
friend size_t GetExclusiveMonitorProcessorCount(ExclusiveMonitor*);
|
||||
friend VAddr* GetExclusiveMonitorAddressPointer(ExclusiveMonitor*, size_t index);
|
||||
friend Vector* GetExclusiveMonitorValuePointer(ExclusiveMonitor*, size_t index);
|
||||
inline void ClearProcessor(size_t index) {
|
||||
lock.Lock();
|
||||
exclusive_addresses[index] = INVALID_EXCLUSIVE_ADDRESS;
|
||||
lock.Unlock();
|
||||
}
|
||||
|
||||
static constexpr VAddr RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFFFull;
|
||||
static constexpr VAddr INVALID_EXCLUSIVE_ADDRESS = 0xDEAD'DEAD'DEAD'DEADull;
|
||||
static constexpr size_t MAX_NUM_CPU_CORES = 4; // Sync with src/core/hardware_properties
|
||||
boost::container::static_vector<VAddr, MAX_NUM_CPU_CORES> exclusive_addresses;
|
||||
boost::container::static_vector<Vector, MAX_NUM_CPU_CORES> exclusive_values;
|
||||
std::array<VAddr, MAX_NUM_CPU_CORES> exclusive_addresses;
|
||||
std::array<Vector, MAX_NUM_CPU_CORES> exclusive_values;
|
||||
SpinLock lock;
|
||||
};
|
||||
|
||||
|
||||
@@ -80,55 +80,24 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool include_prereleases) {
|
||||
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease() {
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
const auto update_check_url = fmt::format("https://{}", Common::g_build_auto_update_api);
|
||||
#else
|
||||
const auto update_check_url = std::string{Common::g_build_auto_update_api};
|
||||
#endif
|
||||
|
||||
auto update_check_path = fmt::format("{}{}", std::string{Common::g_build_auto_update_api_path},
|
||||
std::string{Common::g_build_auto_update_repo});
|
||||
auto update_check_path = std::string{Common::g_build_auto_update_api_path};
|
||||
try {
|
||||
if (include_prereleases) { // This can return either a prerelease or a stable release,
|
||||
// whichever is more recent.
|
||||
const auto update_check_tags_path = update_check_path + "/tags";
|
||||
const auto update_check_releases_path = update_check_path + "/releases";
|
||||
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
|
||||
|
||||
const auto tags_response = UpdateChecker::GetResponse(update_check_url, update_check_tags_path);
|
||||
const auto releases_response = UpdateChecker::GetResponse(update_check_url, update_check_releases_path);
|
||||
if (!response)
|
||||
return {};
|
||||
|
||||
if (!tags_response || !releases_response)
|
||||
return {};
|
||||
|
||||
const std::string latest_tag
|
||||
= nlohmann::json::parse(tags_response.value()).at(0).at("name");
|
||||
const std::string latest_name =
|
||||
nlohmann::json::parse(releases_response.value()).at(0).at("name");
|
||||
|
||||
const bool latest_tag_has_release = releases_response.value().find(
|
||||
fmt::format("\"{}\"", latest_tag))
|
||||
!= std::string::npos;
|
||||
|
||||
// If there is a newer tag, but that tag has no associated release, don't prompt the
|
||||
// user to update.
|
||||
if (!latest_tag_has_release)
|
||||
return {};
|
||||
|
||||
return Update{latest_tag, latest_name};
|
||||
} else { // This is a stable release, only check for other stable releases.
|
||||
update_check_path += "/releases/latest";
|
||||
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
|
||||
|
||||
if (!response)
|
||||
return {};
|
||||
|
||||
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
|
||||
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
|
||||
|
||||
return Update{latest_tag, latest_name};
|
||||
}
|
||||
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
|
||||
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
|
||||
|
||||
return Update{latest_tag, latest_name};
|
||||
} catch (nlohmann::detail::out_of_range&) {
|
||||
LOG_ERROR(Frontend,
|
||||
"Parsing JSON response from {}{} failed during update check: "
|
||||
@@ -147,12 +116,8 @@ std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool includ
|
||||
}
|
||||
|
||||
std::optional<UpdateChecker::Update> UpdateChecker::GetUpdate() {
|
||||
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != NULL) ||
|
||||
(strstr(Common::g_build_version, "alpha") != NULL) ||
|
||||
(strstr(Common::g_build_version, "beta") != NULL) ||
|
||||
(strstr(Common::g_build_version, "rc") != NULL));
|
||||
const std::optional<UpdateChecker::Update> latest_release_tag =
|
||||
UpdateChecker::GetLatestRelease(is_prerelease);
|
||||
UpdateChecker::GetLatestRelease();
|
||||
|
||||
if (!latest_release_tag)
|
||||
goto empty;
|
||||
|
||||
@@ -18,6 +18,6 @@ typedef struct {
|
||||
} Update;
|
||||
|
||||
std::optional<std::string> GetResponse(std::string url, std::string path);
|
||||
std::optional<Update> GetLatestRelease(bool include_prereleases);
|
||||
std::optional<Update> GetLatestRelease();
|
||||
std::optional<Update> GetUpdate();
|
||||
} // namespace UpdateChecker
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -108,10 +108,10 @@ void EmulatedController::ReloadFromSettings() {
|
||||
// Other or debug controller should always be a pro controller
|
||||
if (npad_id_type != NpadIdType::Other) {
|
||||
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
|
||||
original_npad_type = npad_type;
|
||||
original_npad_type = npad_type.load();
|
||||
} else {
|
||||
SetNpadStyleIndex(NpadStyleIndex::Fullkey);
|
||||
original_npad_type = npad_type;
|
||||
original_npad_type = npad_type.load();
|
||||
}
|
||||
|
||||
// Disable special features before disconnecting
|
||||
@@ -577,10 +577,9 @@ void EmulatedController::UnloadInput() {
|
||||
}
|
||||
|
||||
void EmulatedController::EnableConfiguration() {
|
||||
std::unique_lock lock1{connect_mutex}, lock2{npad_mutex};
|
||||
is_configuring = true;
|
||||
tmp_is_connected = is_connected;
|
||||
tmp_npad_type = npad_type;
|
||||
is_configuring.store(true);
|
||||
tmp_is_connected.store(is_connected);
|
||||
tmp_npad_type.store(npad_type);
|
||||
}
|
||||
|
||||
void EmulatedController::DisableConfiguration() {
|
||||
@@ -600,7 +599,7 @@ void EmulatedController::DisableConfiguration() {
|
||||
Disconnect();
|
||||
}
|
||||
SetNpadStyleIndex(tmp_npad_type);
|
||||
original_npad_type = tmp_npad_type;
|
||||
original_npad_type.store(tmp_npad_type);
|
||||
}
|
||||
|
||||
// Apply temporary connected status to the real controller
|
||||
@@ -614,19 +613,16 @@ void EmulatedController::DisableConfiguration() {
|
||||
}
|
||||
|
||||
void EmulatedController::EnableSystemButtons() {
|
||||
std::unique_lock lock{mutex};
|
||||
system_buttons_enabled = true;
|
||||
}
|
||||
|
||||
void EmulatedController::DisableSystemButtons() {
|
||||
std::unique_lock lock{mutex};
|
||||
system_buttons_enabled = false;
|
||||
controller.home_button_state.raw = 0;
|
||||
controller.capture_button_state.raw = 0;
|
||||
}
|
||||
|
||||
void EmulatedController::ResetSystemButtons() {
|
||||
std::unique_lock lock{mutex};
|
||||
controller.home_button_state.home.Assign(false);
|
||||
controller.capture_button_state.capture.Assign(false);
|
||||
}
|
||||
@@ -763,8 +759,7 @@ void EmulatedController::StartMotionCalibration() {
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
|
||||
Common::UUID uuid) {
|
||||
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) {
|
||||
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
|
||||
@@ -772,7 +767,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock{mutex};
|
||||
bool value_changed = false;
|
||||
const auto new_status = TransformToButton(callback);
|
||||
auto& current_status = controller.button_values[index];
|
||||
@@ -818,7 +812,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
|
||||
controller.debug_pad_button_state.raw = 0;
|
||||
controller.home_button_state.raw = 0;
|
||||
controller.capture_button_state.raw = 0;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Button, false);
|
||||
return;
|
||||
}
|
||||
@@ -922,8 +915,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
|
||||
break;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (!is_connected) {
|
||||
if (npad_type == NpadStyleIndex::Handheld) {
|
||||
if (npad_id_type == NpadIdType::Handheld) {
|
||||
@@ -952,7 +943,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
|
||||
auto trigger_guard = SCOPE_GUARD {
|
||||
TriggerOnChange(ControllerTriggerType::Stick, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock{mutex};
|
||||
const auto stick_value = TransformToStick(callback);
|
||||
|
||||
// Only read stick values that have the same uuid or are over the threshold to avoid flapping
|
||||
@@ -1009,7 +999,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
|
||||
auto trigger_guard = SCOPE_GUARD {
|
||||
TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock{mutex};
|
||||
const auto trigger_value = TransformToTrigger(callback);
|
||||
|
||||
// Only read trigger values that have the same uuid or are pressed once
|
||||
@@ -1057,7 +1046,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
|
||||
SCOPE_EXIT {
|
||||
TriggerOnChange(ControllerTriggerType::Motion, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock{mutex};
|
||||
auto& raw_status = controller.motion_values[index].raw_status;
|
||||
auto& emulated = controller.motion_values[index].emulated;
|
||||
|
||||
@@ -1093,7 +1081,6 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
|
||||
auto trigger_guard = SCOPE_GUARD {
|
||||
TriggerOnChange(ControllerTriggerType::Color, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock{mutex};
|
||||
controller.color_values[index] = TransformToColor(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
@@ -1136,15 +1123,13 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
|
||||
std::size_t index) {
|
||||
void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index) {
|
||||
if (index >= controller.battery_values.size()) {
|
||||
return;
|
||||
}
|
||||
SCOPE_EXIT {
|
||||
TriggerOnChange(ControllerTriggerType::Battery, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock{mutex};
|
||||
controller.battery_values[index] = TransformToBattery(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
@@ -1209,47 +1194,33 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
|
||||
SCOPE_EXIT {
|
||||
TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock{mutex};
|
||||
controller.camera_values = TransformToCamera(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
return;
|
||||
if (!is_configuring) {
|
||||
controller.camera_state.sample++;
|
||||
controller.camera_state.format = Core::IrSensor::ImageTransferProcessorFormat(controller.camera_values.format);
|
||||
controller.camera_state.data = controller.camera_values.data;
|
||||
}
|
||||
|
||||
controller.camera_state.sample++;
|
||||
controller.camera_state.format =
|
||||
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
|
||||
controller.camera_state.data = controller.camera_values.data;
|
||||
}
|
||||
|
||||
void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
|
||||
SCOPE_EXIT {
|
||||
TriggerOnChange(ControllerTriggerType::RingController, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock{mutex};
|
||||
const auto force_value = TransformToStick(callback);
|
||||
|
||||
controller.ring_analog_value = force_value.x;
|
||||
|
||||
if (is_configuring) {
|
||||
return;
|
||||
if (!is_configuring) {
|
||||
controller.ring_analog_state.force = force_value.x.value;
|
||||
}
|
||||
|
||||
controller.ring_analog_state.force = force_value.x.value;
|
||||
}
|
||||
|
||||
void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
|
||||
SCOPE_EXIT {
|
||||
TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock{mutex};
|
||||
controller.nfc_values = TransformToNfc(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
return;
|
||||
if (!is_configuring) {
|
||||
controller.nfc_state = controller.nfc_values;
|
||||
}
|
||||
|
||||
controller.nfc_state = controller.nfc_values;
|
||||
}
|
||||
|
||||
bool EmulatedController::SetVibration(bool should_vibrate) {
|
||||
@@ -1258,7 +1229,6 @@ bool EmulatedController::SetVibration(bool should_vibrate) {
|
||||
vibration_value.high_amplitude = 1.0f;
|
||||
vibration_value.low_amplitude = 1.0f;
|
||||
}
|
||||
|
||||
return SetVibration(DeviceIndex::Left, vibration_value);
|
||||
}
|
||||
|
||||
@@ -1268,7 +1238,6 @@ bool EmulatedController::SetVibration(u32 slot, Core::HID::VibrationGcErmCommand
|
||||
vibration_value.high_amplitude = 1.0f;
|
||||
vibration_value.low_amplitude = 1.0f;
|
||||
}
|
||||
|
||||
return SetVibration(DeviceIndex::Left, vibration_value);
|
||||
}
|
||||
|
||||
@@ -1632,7 +1601,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
|
||||
// Attempt to reconnect with the original type
|
||||
if (npad_type != original_npad_type) {
|
||||
Disconnect();
|
||||
const auto current_npad_type = npad_type;
|
||||
const auto current_npad_type = npad_type.load();
|
||||
SetNpadStyleIndex(original_npad_type);
|
||||
if (IsControllerSupported()) {
|
||||
Connect();
|
||||
@@ -1650,7 +1619,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
|
||||
|
||||
// Fallback Fullkey controllers to Pro controllers
|
||||
if (IsControllerFullkey() && supported_style_tag.fullkey) {
|
||||
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
|
||||
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load());
|
||||
SetNpadStyleIndex(NpadStyleIndex::Fullkey);
|
||||
Connect();
|
||||
return;
|
||||
@@ -1658,7 +1627,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
|
||||
|
||||
// Fallback Dual joycon controllers to Pro controllers
|
||||
if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
|
||||
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
|
||||
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load());
|
||||
SetNpadStyleIndex(NpadStyleIndex::Fullkey);
|
||||
Connect();
|
||||
return;
|
||||
@@ -1666,19 +1635,16 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
|
||||
|
||||
// Fallback Pro controllers to Dual joycon
|
||||
if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) {
|
||||
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
|
||||
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type.load());
|
||||
SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
|
||||
Connect();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller",
|
||||
npad_type);
|
||||
LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", npad_type.load());
|
||||
}
|
||||
|
||||
bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
|
||||
std::unique_lock lock{mutex};
|
||||
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
|
||||
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load();
|
||||
switch (type) {
|
||||
case NpadStyleIndex::Fullkey:
|
||||
case NpadStyleIndex::GameCube:
|
||||
@@ -1693,39 +1659,26 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
|
||||
}
|
||||
|
||||
bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
|
||||
std::unique_lock lock{mutex};
|
||||
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
|
||||
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load();
|
||||
switch (type) {
|
||||
case NpadStyleIndex::Fullkey:
|
||||
return supported_style_tag.fullkey.As<bool>();
|
||||
case NpadStyleIndex::Handheld:
|
||||
return supported_style_tag.handheld.As<bool>();
|
||||
case NpadStyleIndex::JoyconDual:
|
||||
return supported_style_tag.joycon_dual.As<bool>();
|
||||
case NpadStyleIndex::JoyconLeft:
|
||||
return supported_style_tag.joycon_left.As<bool>();
|
||||
case NpadStyleIndex::JoyconRight:
|
||||
return supported_style_tag.joycon_right.As<bool>();
|
||||
case NpadStyleIndex::GameCube:
|
||||
return supported_style_tag.gamecube.As<bool>();
|
||||
case NpadStyleIndex::Pokeball:
|
||||
return supported_style_tag.palma.As<bool>();
|
||||
case NpadStyleIndex::NES:
|
||||
return supported_style_tag.lark.As<bool>();
|
||||
case NpadStyleIndex::SNES:
|
||||
return supported_style_tag.lucia.As<bool>();
|
||||
case NpadStyleIndex::N64:
|
||||
return supported_style_tag.lagoon.As<bool>();
|
||||
case NpadStyleIndex::SegaGenesis:
|
||||
return supported_style_tag.lager.As<bool>();
|
||||
default:
|
||||
return false;
|
||||
case NpadStyleIndex::Fullkey: return supported_style_tag.fullkey.As<bool>();
|
||||
case NpadStyleIndex::Handheld: return supported_style_tag.handheld.As<bool>();
|
||||
case NpadStyleIndex::JoyconDual: return supported_style_tag.joycon_dual.As<bool>();
|
||||
case NpadStyleIndex::JoyconLeft: return supported_style_tag.joycon_left.As<bool>();
|
||||
case NpadStyleIndex::JoyconRight: return supported_style_tag.joycon_right.As<bool>();
|
||||
case NpadStyleIndex::GameCube: return supported_style_tag.gamecube.As<bool>();
|
||||
case NpadStyleIndex::Pokeball: return supported_style_tag.palma.As<bool>();
|
||||
case NpadStyleIndex::NES: return supported_style_tag.lark.As<bool>();
|
||||
case NpadStyleIndex::SNES: return supported_style_tag.lucia.As<bool>();
|
||||
case NpadStyleIndex::N64: return supported_style_tag.lagoon.As<bool>();
|
||||
case NpadStyleIndex::SegaGenesis: return supported_style_tag.lager.As<bool>();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::Connect(bool use_temporary_value) {
|
||||
if (!IsControllerSupported(use_temporary_value)) {
|
||||
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
|
||||
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load();
|
||||
LOG_ERROR(Service_HID, "Controller type {} is not supported", type);
|
||||
return;
|
||||
}
|
||||
@@ -1733,12 +1686,10 @@ void EmulatedController::Connect(bool use_temporary_value) {
|
||||
auto trigger_guard = SCOPE_GUARD {
|
||||
TriggerOnChange(ControllerTriggerType::Connected, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock1{connect_mutex}, lock2{mutex};
|
||||
if (is_configuring) {
|
||||
tmp_is_connected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_connected) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
@@ -1750,12 +1701,10 @@ void EmulatedController::Disconnect() {
|
||||
auto trigger_guard = SCOPE_GUARD {
|
||||
TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock1{connect_mutex}, lock2{mutex};
|
||||
if (is_configuring) {
|
||||
tmp_is_connected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_connected) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
@@ -1764,19 +1713,16 @@ void EmulatedController::Disconnect() {
|
||||
}
|
||||
|
||||
bool EmulatedController::IsConnected(bool get_temporary_value) const {
|
||||
std::shared_lock lock{connect_mutex};
|
||||
if (get_temporary_value && is_configuring)
|
||||
return tmp_is_connected;
|
||||
return is_connected;
|
||||
}
|
||||
|
||||
NpadIdType EmulatedController::GetNpadIdType() const {
|
||||
std::shared_lock lock{mutex};
|
||||
return npad_id_type;
|
||||
}
|
||||
|
||||
NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
|
||||
std::shared_lock lock{npad_mutex};
|
||||
if (get_temporary_value && is_configuring)
|
||||
return tmp_npad_type;
|
||||
return npad_type;
|
||||
@@ -1786,8 +1732,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
|
||||
auto trigger_guard = SCOPE_GUARD {
|
||||
TriggerOnChange(ControllerTriggerType::Type, !is_configuring);
|
||||
};
|
||||
std::unique_lock lock1{mutex}, lock2{npad_mutex};
|
||||
|
||||
if (is_configuring) {
|
||||
if (tmp_npad_type == npad_type_) {
|
||||
trigger_guard.Cancel();
|
||||
@@ -1796,14 +1740,12 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
|
||||
tmp_npad_type = npad_type_;
|
||||
return;
|
||||
}
|
||||
|
||||
if (npad_type == npad_type_) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
if (is_connected) {
|
||||
LOG_WARNING(Service_HID, "Controller {} type changed while it's connected",
|
||||
Service::HID::NpadIdTypeToIndex(npad_id_type));
|
||||
LOG_WARNING(Service_HID, "Controller {} type changed while it's connected", Service::HID::NpadIdTypeToIndex(npad_id_type));
|
||||
}
|
||||
npad_type = npad_type_;
|
||||
}
|
||||
@@ -1832,37 +1774,30 @@ LedPattern EmulatedController::GetLedPattern() const {
|
||||
}
|
||||
|
||||
ButtonValues EmulatedController::GetButtonsValues() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.button_values;
|
||||
}
|
||||
|
||||
SticksValues EmulatedController::GetSticksValues() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.stick_values;
|
||||
}
|
||||
|
||||
TriggerValues EmulatedController::GetTriggersValues() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.trigger_values;
|
||||
}
|
||||
|
||||
ControllerMotionValues EmulatedController::GetMotionValues() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.motion_values;
|
||||
}
|
||||
|
||||
ColorValues EmulatedController::GetColorsValues() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.color_values;
|
||||
}
|
||||
|
||||
BatteryValues EmulatedController::GetBatteryValues() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.battery_values;
|
||||
}
|
||||
|
||||
CameraValues EmulatedController::GetCameraValues() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.camera_values;
|
||||
}
|
||||
|
||||
@@ -1871,72 +1806,54 @@ RingAnalogValue EmulatedController::GetRingSensorValues() const {
|
||||
}
|
||||
|
||||
HomeButtonState EmulatedController::GetHomeButtons() const {
|
||||
std::unique_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
if (is_configuring)
|
||||
return {};
|
||||
}
|
||||
return controller.home_button_state;
|
||||
}
|
||||
|
||||
CaptureButtonState EmulatedController::GetCaptureButtons() const {
|
||||
std::unique_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
if (is_configuring)
|
||||
return {};
|
||||
}
|
||||
return controller.capture_button_state;
|
||||
}
|
||||
|
||||
NpadButtonState EmulatedController::GetNpadButtons() const {
|
||||
std::unique_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
if (is_configuring)
|
||||
return {};
|
||||
}
|
||||
return {controller.npad_button_state.raw & GetTurboButtonMask()};
|
||||
}
|
||||
|
||||
DebugPadButton EmulatedController::GetDebugPadButtons() const {
|
||||
std::unique_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
if (is_configuring)
|
||||
return {};
|
||||
}
|
||||
return controller.debug_pad_button_state;
|
||||
}
|
||||
|
||||
AnalogSticks EmulatedController::GetSticks() const {
|
||||
std::unique_lock lock{mutex};
|
||||
|
||||
if (is_configuring) {
|
||||
if (is_configuring)
|
||||
return {};
|
||||
}
|
||||
|
||||
return controller.analog_stick_state;
|
||||
}
|
||||
|
||||
NpadGcTriggerState EmulatedController::GetTriggers() const {
|
||||
std::unique_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
if (is_configuring)
|
||||
return {};
|
||||
}
|
||||
return controller.gc_trigger_state;
|
||||
}
|
||||
|
||||
MotionState EmulatedController::GetMotions() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.motion_state;
|
||||
}
|
||||
|
||||
ControllerColors EmulatedController::GetColors() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.colors_state;
|
||||
}
|
||||
|
||||
BatteryLevelState EmulatedController::GetBattery() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.battery_state;
|
||||
}
|
||||
|
||||
const CameraState& EmulatedController::GetCamera() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.camera_state;
|
||||
}
|
||||
|
||||
@@ -1945,15 +1862,14 @@ RingSensorForce EmulatedController::GetRingSensorForce() const {
|
||||
}
|
||||
|
||||
const NfcState& EmulatedController::GetNfc() const {
|
||||
std::unique_lock lock{mutex};
|
||||
return controller.nfc_state;
|
||||
}
|
||||
|
||||
NpadColor EmulatedController::GetNpadColor(u32 color) {
|
||||
return {
|
||||
.r = static_cast<u8>((color >> 16) & 0xFF),
|
||||
.g = static_cast<u8>((color >> 8) & 0xFF),
|
||||
.b = static_cast<u8>(color & 0xFF),
|
||||
.r = u8((color >> 16) & 0xFF),
|
||||
.g = u8((color >> 8) & 0xFF),
|
||||
.b = u8(color & 0xFF),
|
||||
.a = 0xff,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/input.h"
|
||||
@@ -576,13 +578,7 @@ private:
|
||||
NpadButton GetTurboButtonMask() const;
|
||||
|
||||
const NpadIdType npad_id_type;
|
||||
NpadStyleIndex npad_type{NpadStyleIndex::None};
|
||||
NpadStyleIndex original_npad_type{NpadStyleIndex::None};
|
||||
NpadStyleTag supported_style_tag{NpadStyleSet::All};
|
||||
bool is_connected{false};
|
||||
bool is_configuring{false};
|
||||
bool is_initialized{false};
|
||||
bool system_buttons_enabled{true};
|
||||
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
|
||||
u32 turbo_button_state{0};
|
||||
std::size_t nfc_handles{0};
|
||||
@@ -591,9 +587,16 @@ private:
|
||||
std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{};
|
||||
std::array<bool, HIDCore::available_controllers> controller_connected{};
|
||||
|
||||
// Atomically synched values
|
||||
std::atomic<HID::NpadStyleIndex> npad_type{HID::NpadStyleIndex::None};
|
||||
std::atomic<HID::NpadStyleIndex> original_npad_type{HID::NpadStyleIndex::None};
|
||||
// Temporary values to avoid doing changes while the controller is in configuring mode
|
||||
NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
|
||||
bool tmp_is_connected{false};
|
||||
std::atomic<HID::NpadStyleIndex> tmp_npad_type{HID::NpadStyleIndex::None};
|
||||
std::atomic<bool> tmp_is_connected{false};
|
||||
std::atomic<bool> is_connected{false};
|
||||
std::atomic<bool> is_configuring{false};
|
||||
std::atomic<bool> is_initialized{false};
|
||||
std::atomic<bool> system_buttons_enabled{true};
|
||||
|
||||
ButtonParams button_params;
|
||||
StickParams stick_params;
|
||||
@@ -632,10 +635,7 @@ private:
|
||||
StickDevices virtual_stick_devices;
|
||||
ControllerMotionDevices virtual_motion_devices;
|
||||
|
||||
mutable std::shared_mutex mutex;
|
||||
mutable std::shared_mutex callback_mutex;
|
||||
mutable std::shared_mutex npad_mutex;
|
||||
mutable std::shared_mutex connect_mutex;
|
||||
mutable std::mutex callback_mutex;
|
||||
ankerl::unordered_dense::map<int, ControllerUpdateCallback> callback_list;
|
||||
int last_callback_key = 0;
|
||||
|
||||
|
||||
@@ -157,11 +157,14 @@ void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t c
|
||||
if (!controller.device->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
auto* shared_memory = controller.shared_memory;
|
||||
const auto& battery_level = controller.device->GetBattery();
|
||||
shared_memory->battery_level_dual = battery_level.dual.battery_level;
|
||||
shared_memory->battery_level_left = battery_level.left.battery_level;
|
||||
shared_memory->battery_level_right = battery_level.right.battery_level;
|
||||
if (auto* shared_memory = controller.shared_memory; shared_memory) {
|
||||
const auto& battery_level = controller.device->GetBattery();
|
||||
shared_memory->battery_level_dual = battery_level.dual.battery_level;
|
||||
shared_memory->battery_level_left = battery_level.left.battery_level;
|
||||
shared_memory->battery_level_right = battery_level.right.battery_level;
|
||||
} else {
|
||||
LOG_WARNING(Service_HID, "shared_memory is null {}", controller_idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -180,6 +183,10 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) {
|
||||
const auto& body_colors = controller.device->GetColors();
|
||||
const auto& battery_level = controller.device->GetBattery();
|
||||
auto* shared_memory = controller.shared_memory;
|
||||
if (!shared_memory) {
|
||||
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id);
|
||||
return;
|
||||
}
|
||||
if (controller_type == Core::HID::NpadStyleIndex::None) {
|
||||
npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id);
|
||||
return;
|
||||
@@ -801,7 +808,7 @@ Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) {
|
||||
|
||||
auto* shared_memory = controller.shared_memory;
|
||||
if (!shared_memory) {
|
||||
LOG_WARNING(Service_HID, "DisconnectNpad: shared_memory is null for npad_id={}", npad_id);
|
||||
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id);
|
||||
return ResultSuccess;
|
||||
}
|
||||
// Don't reset shared_memory->assignment_mode this value is persistent
|
||||
@@ -1195,11 +1202,10 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
|
||||
|
||||
AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) {
|
||||
const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid();
|
||||
const auto& shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory;
|
||||
|
||||
const auto* shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory;
|
||||
return {
|
||||
.ui_variant = 0,
|
||||
.footer = shared_memory->applet_footer_type,
|
||||
.footer = shared_memory ? shared_memory->applet_footer_type : AppletFooterUiType::None,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,9 @@ void DefineGenericOutput(EmitContext& ctx, size_t index, std::optional<u32> invo
|
||||
ctx.Decorate(id, spv::Decoration::XfbBuffer, xfb_varying->buffer);
|
||||
ctx.Decorate(id, spv::Decoration::XfbStride, xfb_varying->stride);
|
||||
ctx.Decorate(id, spv::Decoration::Offset, xfb_varying->offset);
|
||||
if (ctx.stage == Stage::Geometry && xfb_varying->stream != 0) {
|
||||
ctx.Decorate(id, spv::Decoration::Stream, xfb_varying->stream);
|
||||
}
|
||||
}
|
||||
if (num_components < 4 || element > 0) {
|
||||
const std::string_view subswizzle{swizzle.substr(element, num_components)};
|
||||
|
||||
@@ -76,6 +76,7 @@ enum class TessSpacing {
|
||||
|
||||
struct TransformFeedbackVarying {
|
||||
u32 buffer{};
|
||||
u32 stream{};
|
||||
u32 stride{};
|
||||
u32 offset{};
|
||||
u32 components{};
|
||||
|
||||
@@ -1067,26 +1067,29 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||
HostBindings<typename P::Buffer> host_bindings;
|
||||
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
||||
const Binding& binding = channel_state->transform_feedback_buffers[index];
|
||||
if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
|
||||
maxwell3d->regs.transform_feedback.controls[index].stride == 0) {
|
||||
break;
|
||||
const auto& control = maxwell3d->regs.transform_feedback.controls[index];
|
||||
const bool has_layout = control.varying_count != 0 || control.stride != 0;
|
||||
|
||||
Buffer* host_buffer = &slot_buffers[NULL_BUFFER_ID];
|
||||
u32 offset = 0;
|
||||
u32 size = 0;
|
||||
|
||||
if (has_layout && binding.buffer_id != NULL_BUFFER_ID && binding.size != 0) {
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
size = binding.size;
|
||||
SynchronizeBuffer(buffer, binding.device_addr, size);
|
||||
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
|
||||
offset = buffer.Offset(binding.device_addr);
|
||||
buffer.MarkUsage(offset, size);
|
||||
host_buffer = &buffer;
|
||||
}
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
const u32 size = binding.size;
|
||||
SynchronizeBuffer(buffer, binding.device_addr, size);
|
||||
|
||||
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
|
||||
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
buffer.MarkUsage(offset, size);
|
||||
host_bindings.buffers.push_back(&buffer);
|
||||
host_bindings.buffers.push_back(host_buffer);
|
||||
host_bindings.offsets.push_back(offset);
|
||||
host_bindings.sizes.push_back(size);
|
||||
}
|
||||
if (host_bindings.buffers.size() > 0) {
|
||||
runtime.BindTransformFeedbackBuffers(host_bindings);
|
||||
}
|
||||
runtime.BindTransformFeedbackBuffers(host_bindings);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -92,7 +92,16 @@ void ThreadManager::InvalidateRegion(DAddr addr, u64 size) {
|
||||
}
|
||||
|
||||
void ThreadManager::FlushAndInvalidateRegion(DAddr addr, u64 size) {
|
||||
// Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important
|
||||
if (Settings::IsGPULevelHigh()) {
|
||||
if (!is_async) {
|
||||
PushCommand(FlushRegionCommand(addr, size));
|
||||
} else {
|
||||
auto& gpu = system.GPU();
|
||||
const u64 fence = gpu.RequestFlush(addr, size);
|
||||
TickGPU();
|
||||
gpu.WaitForSyncOperation(fence);
|
||||
}
|
||||
}
|
||||
rasterizer->OnCacheInvalidation(addr, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -5,16 +8,22 @@
|
||||
|
||||
layout(local_size_x = 1) in;
|
||||
|
||||
layout(std430, binding = 0) buffer Query {
|
||||
uvec2 initial;
|
||||
uvec2 unknown;
|
||||
uvec2 current;
|
||||
layout(std430, binding = 0) readonly buffer Query {
|
||||
uint data[];
|
||||
};
|
||||
|
||||
layout(std430, binding = 1) buffer Result {
|
||||
layout(std430, binding = 1) writeonly buffer Result {
|
||||
uint result;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
uint compare_to_zero;
|
||||
};
|
||||
|
||||
void main() {
|
||||
result = all(equal(initial, current)) ? 1 : 0;
|
||||
if (compare_to_zero != 0u) {
|
||||
result = (data[0] != 0u && data[1] != 0u) ? 1u : 0u;
|
||||
} else {
|
||||
result = (data[0] == data[4] && data[1] == data[5]) ? 1u : 0u;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,11 +285,11 @@ void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d,
|
||||
}
|
||||
void HLE_DrawIndirectByteCount::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
|
||||
const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback();
|
||||
auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0] & 0xFFFFU);
|
||||
if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) {
|
||||
if (!force) {
|
||||
Fallback(maxwell3d, parameters);
|
||||
return;
|
||||
}
|
||||
auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0] & 0xFFFFU);
|
||||
auto& params = maxwell3d.draw_manager->GetIndirectParams();
|
||||
params.is_byte_count = true;
|
||||
params.is_indexed = false;
|
||||
|
||||
@@ -412,6 +412,7 @@ bool QueryCacheBase<Traits>::AccelerateHostConditionalRendering() {
|
||||
.found_query = nullptr,
|
||||
};
|
||||
}
|
||||
it_current = it_current_2;
|
||||
}
|
||||
auto* query = impl->ObtainQuery(it_current->second);
|
||||
qc_dirty |= True(query->flags & QueryFlagBits::IsHostManaged) &&
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -75,7 +78,7 @@ public:
|
||||
}
|
||||
|
||||
u64 GetDependentMask() const {
|
||||
return dependence_mask;
|
||||
return dependent_mask;
|
||||
}
|
||||
|
||||
u64 GetAmendValue() const {
|
||||
|
||||
@@ -629,6 +629,9 @@ void RasterizerOpenGL::ReleaseFences(bool force) {
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(DAddr addr, u64 size,
|
||||
VideoCommon::CacheType which) {
|
||||
if (Settings::IsGPULevelHigh()) {
|
||||
FlushRegion(addr, size, which);
|
||||
}
|
||||
InvalidateRegion(addr, size, which);
|
||||
}
|
||||
|
||||
|
||||
@@ -190,9 +190,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!extended_dynamic_state_3_enables) {
|
||||
dynamic_state.Refresh3(regs);
|
||||
}
|
||||
dynamic_state.Refresh3(regs, features);
|
||||
if (xfb_enabled) {
|
||||
RefreshXfbState(xfb_state, regs);
|
||||
}
|
||||
@@ -295,16 +293,22 @@ void FixedPipelineState::DynamicState::Refresh2(const Maxwell& regs,
|
||||
depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
void FixedPipelineState::DynamicState::Refresh3(const Maxwell& regs) {
|
||||
logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
|
||||
depth_clamp_disabled.Assign(regs.viewport_clip_control.geometry_clip ==
|
||||
Maxwell::ViewportClipControl::GeometryClip::Passthrough ||
|
||||
regs.viewport_clip_control.geometry_clip ==
|
||||
Maxwell::ViewportClipControl::GeometryClip::FrustumXYZ ||
|
||||
regs.viewport_clip_control.geometry_clip ==
|
||||
Maxwell::ViewportClipControl::GeometryClip::FrustumZ);
|
||||
|
||||
line_stipple_enable.Assign(regs.line_stipple_enable);
|
||||
void FixedPipelineState::DynamicState::Refresh3(const Maxwell& regs,
|
||||
const DynamicFeatures& features) {
|
||||
if (!features.has_dynamic_state3_logic_op_enable) {
|
||||
logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
|
||||
}
|
||||
if (!features.has_dynamic_state3_depth_clamp_enable) {
|
||||
depth_clamp_disabled.Assign(regs.viewport_clip_control.geometry_clip ==
|
||||
Maxwell::ViewportClipControl::GeometryClip::Passthrough ||
|
||||
regs.viewport_clip_control.geometry_clip ==
|
||||
Maxwell::ViewportClipControl::GeometryClip::FrustumXYZ ||
|
||||
regs.viewport_clip_control.geometry_clip ==
|
||||
Maxwell::ViewportClipControl::GeometryClip::FrustumZ);
|
||||
}
|
||||
if (!features.has_dynamic_state3_line_stipple_enable) {
|
||||
line_stipple_enable.Assign(regs.line_stipple_enable);
|
||||
}
|
||||
}
|
||||
|
||||
size_t FixedPipelineState::Hash() const noexcept {
|
||||
|
||||
@@ -27,6 +27,9 @@ struct DynamicFeatures {
|
||||
bool has_extended_dynamic_state_2_patch_control_points;
|
||||
bool has_extended_dynamic_state_3_blend;
|
||||
bool has_extended_dynamic_state_3_enables;
|
||||
bool has_dynamic_state3_depth_clamp_enable;
|
||||
bool has_dynamic_state3_logic_op_enable;
|
||||
bool has_dynamic_state3_line_stipple_enable;
|
||||
bool has_dynamic_vertex_input;
|
||||
bool has_provoking_vertex;
|
||||
bool has_provoking_vertex_first_mode;
|
||||
@@ -175,7 +178,7 @@ struct FixedPipelineState {
|
||||
void Refresh(const Maxwell& regs);
|
||||
void Refresh2(const Maxwell& regs, Maxwell::PrimitiveTopology topology,
|
||||
bool base_features_supported);
|
||||
void Refresh3(const Maxwell& regs);
|
||||
void Refresh3(const Maxwell& regs, const DynamicFeatures& features);
|
||||
|
||||
Maxwell::ComparisonOp DepthTestFunc() const noexcept {
|
||||
return UnpackComparisonOp(depth_test_func);
|
||||
@@ -265,8 +268,7 @@ struct FixedPipelineState {
|
||||
return sizeof(*this);
|
||||
}
|
||||
if (dynamic_vertex_input && extended_dynamic_state_3_blend) {
|
||||
// Exclude dynamic state and attributes
|
||||
return offsetof(FixedPipelineState, dynamic_state);
|
||||
return offsetof(FixedPipelineState, attachments);
|
||||
}
|
||||
if (dynamic_vertex_input) {
|
||||
// Exclude dynamic state
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -171,7 +172,11 @@ try
|
||||
|
||||
RendererVulkan::~RendererVulkan() {
|
||||
scheduler.RegisterOnSubmit([] {});
|
||||
void(device.GetLogical().WaitIdle());
|
||||
scheduler.Finish();
|
||||
{
|
||||
std::scoped_lock lock{scheduler.submit_mutex};
|
||||
void(device.GetLogical().WaitIdle());
|
||||
}
|
||||
}
|
||||
|
||||
void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebuffers) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include <mutex>
|
||||
#include "video_core/framebuffer_config.h"
|
||||
#include "video_core/present.h"
|
||||
#include "video_core/renderer_vulkan/present/filters.h"
|
||||
@@ -31,7 +32,10 @@ BlitScreen::~BlitScreen() = default;
|
||||
void BlitScreen::WaitIdle() {
|
||||
present_manager.WaitPresent();
|
||||
scheduler.Finish();
|
||||
device.GetLogical().WaitIdle();
|
||||
{
|
||||
std::scoped_lock lock{scheduler.submit_mutex};
|
||||
device.GetLogical().WaitIdle();
|
||||
}
|
||||
}
|
||||
|
||||
void BlitScreen::SetWindowAdaptPass() {
|
||||
|
||||
@@ -637,12 +637,10 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<
|
||||
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
|
||||
auto handle = bindings.buffers[i]->Handle();
|
||||
if (handle == VK_NULL_HANDLE) {
|
||||
ReserveNullBuffer();
|
||||
handle = *null_buffer;
|
||||
bindings.offsets[i] = 0;
|
||||
bindings.sizes[i] = VK_WHOLE_SIZE;
|
||||
if (!device.HasNullDescriptor()) {
|
||||
ReserveNullBuffer();
|
||||
handle = *null_buffer;
|
||||
}
|
||||
bindings.sizes[i] = 0;
|
||||
}
|
||||
buffer_handles[i] = handle;
|
||||
}
|
||||
|
||||
@@ -228,6 +228,10 @@ struct QueriesPrefixScanPushConstants {
|
||||
u32 accumulation_limit;
|
||||
u32 buffer_offset;
|
||||
};
|
||||
|
||||
struct ConditionalRenderingResolvePushConstants {
|
||||
u32 compare_to_zero;
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
||||
@@ -413,7 +417,8 @@ ConditionalRenderingResolvePass::ConditionalRenderingResolvePass(
|
||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
|
||||
: ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
|
||||
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, nullptr,
|
||||
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO,
|
||||
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(ConditionalRenderingResolvePushConstants)>,
|
||||
RESOLVE_CONDITIONAL_RENDER_COMP_SPV),
|
||||
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
|
||||
|
||||
@@ -430,7 +435,7 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_
|
||||
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, descriptor_data](vk::CommandBuffer cmdbuf) {
|
||||
scheduler.Record([this, descriptor_data, compare_to_zero](vk::CommandBuffer cmdbuf) {
|
||||
static constexpr VkMemoryBarrier read_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
@@ -443,6 +448,9 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
|
||||
};
|
||||
const ConditionalRenderingResolvePushConstants uniforms{
|
||||
.compare_to_zero = compare_to_zero ? 1U : 0U,
|
||||
};
|
||||
const VkDescriptorSet set = descriptor_allocator.Commit();
|
||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
||||
|
||||
@@ -450,9 +458,11 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
|
||||
cmdbuf.Dispatch(1, 1, 1);
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier);
|
||||
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0,
|
||||
write_barrier);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -520,7 +530,7 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe
|
||||
const VkDescriptorSet set = descriptor_allocator.Commit();
|
||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
||||
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||
|
||||
@@ -467,6 +467,10 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
bind_stage_info(4);
|
||||
}
|
||||
|
||||
if (regs.transform_feedback_enabled != 0) {
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
}
|
||||
|
||||
buffer_cache.UpdateGraphicsBuffers(is_indexed);
|
||||
buffer_cache.BindHostGeometryBuffers(is_indexed);
|
||||
|
||||
|
||||
@@ -485,6 +485,12 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
device.IsExtExtendedDynamicState3BlendingSupported();
|
||||
dynamic_features.has_extended_dynamic_state_3_enables =
|
||||
device.IsExtExtendedDynamicState3EnablesSupported();
|
||||
dynamic_features.has_dynamic_state3_depth_clamp_enable =
|
||||
device.SupportsDynamicState3DepthClampEnable();
|
||||
dynamic_features.has_dynamic_state3_logic_op_enable =
|
||||
device.SupportsDynamicState3LogicOpEnable();
|
||||
dynamic_features.has_dynamic_state3_line_stipple_enable =
|
||||
device.SupportsDynamicState3LineStippleEnable();
|
||||
|
||||
// VIDS: Independent toggle (not affected by dyna_state levels)
|
||||
dynamic_features.has_dynamic_vertex_input =
|
||||
|
||||
@@ -189,7 +189,7 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
|
||||
frame->image = memory_allocator.CreateImage({
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
|
||||
.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = swapchain.GetImageFormat(),
|
||||
.extent =
|
||||
|
||||
@@ -157,8 +157,9 @@ public:
|
||||
ReserveHostQuery();
|
||||
|
||||
scheduler.Record([query_pool = current_query_pool,
|
||||
query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
|
||||
query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
|
||||
const bool use_precise = Settings::IsGPULevelHigh();
|
||||
cmdbuf.ResetQueryPool(query_pool, static_cast<u32>(query_index), 1);
|
||||
cmdbuf.BeginQuery(query_pool, static_cast<u32>(query_index),
|
||||
use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
|
||||
});
|
||||
@@ -220,8 +221,7 @@ public:
|
||||
}
|
||||
PauseCounter();
|
||||
const auto driver_id = device.GetDriverID();
|
||||
if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
if (driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
pending_sync.clear();
|
||||
sync_values_stash.clear();
|
||||
return;
|
||||
@@ -666,13 +666,18 @@ public:
|
||||
offsets.fill(0);
|
||||
last_queries.fill(0);
|
||||
last_queries_stride.fill(1);
|
||||
stream_to_slot.fill(INVALID_SLOT);
|
||||
VkBufferUsageFlags counter_buffer_usage =
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
if (device.IsExtTransformFeedbackSupported()) {
|
||||
counter_buffer_usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT;
|
||||
}
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = TFBQueryBank::QUERY_SIZE * NUM_STREAMS,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT,
|
||||
.usage = counter_buffer_usage,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
@@ -692,6 +697,9 @@ public:
|
||||
~TFBCounterStreamer() = default;
|
||||
|
||||
void StartCounter() override {
|
||||
if (!device.IsExtTransformFeedbackSupported()) {
|
||||
return;
|
||||
}
|
||||
FlushBeginTFB();
|
||||
has_started = true;
|
||||
}
|
||||
@@ -706,7 +714,9 @@ public:
|
||||
|
||||
void CloseCounter() override {
|
||||
if (has_flushed_end_pending) {
|
||||
FlushEndTFB();
|
||||
if (scheduler.IsRenderPassActive()) {
|
||||
FlushEndTFB();
|
||||
}
|
||||
}
|
||||
runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
|
||||
if (maxwell3d.regs.transform_feedback_enabled == 0) {
|
||||
@@ -756,18 +766,33 @@ public:
|
||||
if (has_timestamp) {
|
||||
new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
|
||||
}
|
||||
if (!device.IsExtTransformFeedbackSupported()) {
|
||||
new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
|
||||
return index;
|
||||
}
|
||||
if (!subreport_) {
|
||||
new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
|
||||
return index;
|
||||
}
|
||||
const size_t subreport = static_cast<size_t>(*subreport_);
|
||||
if (subreport >= NUM_STREAMS) {
|
||||
new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
|
||||
return index;
|
||||
}
|
||||
last_queries[subreport] = address;
|
||||
if ((streams_mask & (1ULL << subreport)) == 0) {
|
||||
new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
|
||||
return index;
|
||||
}
|
||||
const size_t slot = stream_to_slot[subreport];
|
||||
if (slot >= NUM_STREAMS) {
|
||||
new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
|
||||
return index;
|
||||
}
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
CloseCounter();
|
||||
auto [bank_slot, data_slot] = ProduceCounterBuffer(subreport);
|
||||
auto [bank_slot, data_slot] = ProduceCounterBuffer(slot);
|
||||
new_query->start_bank_id = static_cast<u32>(bank_slot);
|
||||
new_query->size_banks = 1;
|
||||
new_query->start_slot = static_cast<u32>(data_slot);
|
||||
@@ -778,6 +803,9 @@ public:
|
||||
}
|
||||
|
||||
std::optional<std::pair<DAddr, size_t>> GetLastQueryStream(size_t stream) {
|
||||
if (stream >= NUM_STREAMS) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (last_queries[stream] != 0) {
|
||||
std::pair<DAddr, size_t> result(last_queries[stream], last_queries_stride[stream]);
|
||||
return result;
|
||||
@@ -789,6 +817,10 @@ public:
|
||||
return out_topology;
|
||||
}
|
||||
|
||||
u32 GetPatchVertices() const {
|
||||
return patch_vertices;
|
||||
}
|
||||
|
||||
bool HasUnsyncedQueries() const override {
|
||||
return !pending_flush_queries.empty();
|
||||
}
|
||||
@@ -855,6 +887,9 @@ public:
|
||||
|
||||
private:
|
||||
void FlushBeginTFB() {
|
||||
if (!device.IsExtTransformFeedbackSupported()) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
if (has_flushed_end_pending) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
@@ -868,12 +903,24 @@ private:
|
||||
});
|
||||
return;
|
||||
}
|
||||
static constexpr VkMemoryBarrier COUNTER_RESUME_BARRIER{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT,
|
||||
};
|
||||
scheduler.Record([this, total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
|
||||
VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, 0,
|
||||
COUNTER_RESUME_BARRIER);
|
||||
cmdbuf.BeginTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
|
||||
});
|
||||
}
|
||||
|
||||
void FlushEndTFB() {
|
||||
if (!device.IsExtTransformFeedbackSupported()) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
if (!has_flushed_end_pending) [[unlikely]] {
|
||||
UNREACHABLE();
|
||||
return;
|
||||
@@ -899,28 +946,48 @@ private:
|
||||
void UpdateBuffers() {
|
||||
last_queries.fill(0);
|
||||
last_queries_stride.fill(1);
|
||||
stream_to_slot.fill(INVALID_SLOT);
|
||||
streams_mask = 0; // reset previously recorded streams
|
||||
runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
|
||||
buffers_count = 0;
|
||||
out_topology = maxwell3d.draw_manager->GetDrawState().topology;
|
||||
patch_vertices = std::max(maxwell3d.regs.patch_vertices, 1U);
|
||||
if (out_topology == Maxwell3D::Regs::PrimitiveTopology::Patches) {
|
||||
switch (maxwell3d.regs.tessellation.params.output_primitives.Value()) {
|
||||
case Maxwell3D::Regs::Tessellation::OutputPrimitives::Points:
|
||||
out_topology = Maxwell3D::Regs::PrimitiveTopology::Points;
|
||||
break;
|
||||
case Maxwell3D::Regs::Tessellation::OutputPrimitives::Lines:
|
||||
out_topology = Maxwell3D::Regs::PrimitiveTopology::LineStrip;
|
||||
break;
|
||||
case Maxwell3D::Regs::Tessellation::OutputPrimitives::Triangles_CW:
|
||||
case Maxwell3D::Regs::Tessellation::OutputPrimitives::Triangles_CCW:
|
||||
out_topology = Maxwell3D::Regs::PrimitiveTopology::TriangleStrip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
|
||||
const auto& tf = maxwell3d.regs.transform_feedback;
|
||||
if (tf.buffers[i].enable == 0) {
|
||||
continue;
|
||||
}
|
||||
buffers_count = std::max<size_t>(buffers_count, i + 1);
|
||||
const size_t stream = tf.controls[i].stream;
|
||||
if (stream >= last_queries_stride.size()) {
|
||||
LOG_WARNING(Render_Vulkan, "TransformFeedback stream {} out of range", stream);
|
||||
continue;
|
||||
}
|
||||
if ((streams_mask & (1ULL << stream)) != 0) {
|
||||
continue;
|
||||
}
|
||||
last_queries_stride[stream] = tf.controls[i].stride;
|
||||
stream_to_slot[stream] = i;
|
||||
streams_mask |= 1ULL << stream;
|
||||
buffers_count = std::max<size_t>(buffers_count, stream + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> ProduceCounterBuffer(size_t stream) {
|
||||
std::pair<size_t, size_t> ProduceCounterBuffer(size_t slot_index) {
|
||||
if (current_bank == nullptr || current_bank->IsClosed()) {
|
||||
current_bank_id =
|
||||
bank_pool.ReserveBank([this](std::deque<TFBQueryBank>& queue, size_t index) {
|
||||
@@ -946,7 +1013,8 @@ private:
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([dst_buffer = current_bank->GetBuffer(),
|
||||
src_buffer = counter_buffers[stream], src_offset = offsets[stream],
|
||||
src_buffer = counter_buffers[slot_index],
|
||||
src_offset = offsets[slot_index],
|
||||
slot](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
|
||||
@@ -965,6 +1033,7 @@ private:
|
||||
friend class PrimitivesSucceededStreamer;
|
||||
|
||||
static constexpr size_t NUM_STREAMS = 4;
|
||||
static constexpr size_t INVALID_SLOT = NUM_STREAMS;
|
||||
|
||||
QueryCacheRuntime& runtime;
|
||||
const Device& device;
|
||||
@@ -994,7 +1063,9 @@ private:
|
||||
std::array<VkDeviceSize, NUM_STREAMS> offsets{};
|
||||
std::array<DAddr, NUM_STREAMS> last_queries;
|
||||
std::array<size_t, NUM_STREAMS> last_queries_stride;
|
||||
std::array<size_t, NUM_STREAMS> stream_to_slot;
|
||||
Maxwell3D::Regs::PrimitiveTopology out_topology;
|
||||
u32 patch_vertices{1};
|
||||
u64 streams_mask;
|
||||
};
|
||||
|
||||
@@ -1015,6 +1086,7 @@ public:
|
||||
u64 stride{};
|
||||
DAddr dependant_address{};
|
||||
Maxwell3D::Regs::PrimitiveTopology topology{Maxwell3D::Regs::PrimitiveTopology::Points};
|
||||
u32 patch_vertices{1};
|
||||
size_t dependant_index{};
|
||||
bool dependant_manage{};
|
||||
};
|
||||
@@ -1031,6 +1103,10 @@ public:
|
||||
|
||||
~PrimitivesSucceededStreamer() = default;
|
||||
|
||||
void ResetCounter() override {
|
||||
tfb_streamer.ResetCounter();
|
||||
}
|
||||
|
||||
size_t WriteCounter(DAddr address, bool has_timestamp, u32 value,
|
||||
std::optional<u32> subreport_) override {
|
||||
auto index = BuildQuery();
|
||||
@@ -1048,6 +1124,7 @@ public:
|
||||
auto dependant_address_opt = tfb_streamer.GetLastQueryStream(subreport);
|
||||
bool must_manage_dependance = false;
|
||||
new_query->topology = tfb_streamer.GetOutputTopology();
|
||||
new_query->patch_vertices = tfb_streamer.GetPatchVertices();
|
||||
if (dependant_address_opt) {
|
||||
auto [dep_address, stride] = *dependant_address_opt;
|
||||
new_query->dependant_address = dep_address;
|
||||
@@ -1068,6 +1145,7 @@ public:
|
||||
}
|
||||
new_query->stride = 1;
|
||||
runtime.View3DRegs([new_query, subreport](Maxwell3D& maxwell3d) {
|
||||
new_query->patch_vertices = std::max(maxwell3d.regs.patch_vertices, 1U);
|
||||
for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
|
||||
const auto& tf = maxwell3d.regs.transform_feedback;
|
||||
if (tf.buffers[i].enable == 0) {
|
||||
@@ -1131,27 +1209,39 @@ public:
|
||||
}
|
||||
}
|
||||
query->value = [&]() -> u64 {
|
||||
const auto saturating_subtract = [](u64 value, u64 amount) {
|
||||
return value > amount ? value - amount : 0;
|
||||
};
|
||||
switch (query->topology) {
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Points:
|
||||
return num_vertices;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Lines:
|
||||
return num_vertices / 2;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LineLoop:
|
||||
return (num_vertices / 2) + 1;
|
||||
return num_vertices > 1 ? num_vertices : 0;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LineStrip:
|
||||
return num_vertices - 1;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Patches:
|
||||
return saturating_subtract(num_vertices, 1);
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LinesAdjacency:
|
||||
return num_vertices / 4;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LineStripAdjacency:
|
||||
return saturating_subtract(num_vertices, 3);
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Triangles:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
|
||||
return num_vertices / 3;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
|
||||
return num_vertices / 6;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleFan:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip:
|
||||
return saturating_subtract(num_vertices, 2);
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
|
||||
return num_vertices - 2;
|
||||
return num_vertices > 4 ? (num_vertices - 4) / 2 : 0;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Quads:
|
||||
return num_vertices / 4;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::QuadStrip:
|
||||
return num_vertices > 2 ? (num_vertices - 2) / 2 : 0;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Polygon:
|
||||
return 1U;
|
||||
return num_vertices >= 3 ? 1U : 0U;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Patches:
|
||||
return num_vertices / std::max<u64>(query->patch_vertices, 1U);
|
||||
default:
|
||||
return num_vertices;
|
||||
}
|
||||
@@ -1202,16 +1292,24 @@ struct QueryCacheRuntimeImpl {
|
||||
hcr_setup.pNext = nullptr;
|
||||
hcr_setup.flags = 0;
|
||||
|
||||
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
|
||||
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
|
||||
const bool has_conditional_rendering = device.IsExtConditionalRendering();
|
||||
if (has_conditional_rendering) {
|
||||
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
|
||||
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
|
||||
}
|
||||
|
||||
VkBufferUsageFlags hcr_buffer_usage =
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
if (has_conditional_rendering) {
|
||||
hcr_buffer_usage |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
|
||||
}
|
||||
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = sizeof(u32),
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
|
||||
.usage = hcr_buffer_usage,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
@@ -1338,15 +1436,17 @@ void QueryCacheRuntime::HostConditionalRenderingCompareValueImpl(VideoCommon::Lo
|
||||
}
|
||||
}
|
||||
|
||||
void QueryCacheRuntime::HostConditionalRenderingCompareBCImpl(DAddr address, bool is_equal) {
|
||||
void QueryCacheRuntime::HostConditionalRenderingCompareBCImpl(DAddr address, bool is_equal,
|
||||
bool compare_to_zero) {
|
||||
VkBuffer to_resolve;
|
||||
u32 to_resolve_offset;
|
||||
const u32 resolve_size = compare_to_zero ? 8 : 24;
|
||||
{
|
||||
std::scoped_lock lk(impl->buffer_cache.mutex);
|
||||
static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::NoSynchronize;
|
||||
const auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
|
||||
const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
|
||||
const auto [buffer, offset] =
|
||||
impl->buffer_cache.ObtainCPUBuffer(address, 24, sync_info, post_op);
|
||||
impl->buffer_cache.ObtainCPUBuffer(address, resolve_size, sync_info, post_op);
|
||||
to_resolve = buffer->Handle();
|
||||
to_resolve_offset = static_cast<u32>(offset);
|
||||
}
|
||||
@@ -1355,7 +1455,7 @@ void QueryCacheRuntime::HostConditionalRenderingCompareBCImpl(DAddr address, boo
|
||||
PauseHostConditionalRendering();
|
||||
}
|
||||
impl->conditional_resolve_pass->Resolve(*impl->hcr_resolve_buffer, to_resolve,
|
||||
to_resolve_offset, false);
|
||||
to_resolve_offset, compare_to_zero);
|
||||
impl->hcr_setup.buffer = *impl->hcr_resolve_buffer;
|
||||
impl->hcr_setup.offset = 0;
|
||||
impl->hcr_setup.flags = is_equal ? 0 : VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT;
|
||||
@@ -1371,7 +1471,7 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValue(VideoCommon::Lookup
|
||||
if (!impl->device.IsExtConditionalRendering()) {
|
||||
return false;
|
||||
}
|
||||
HostConditionalRenderingCompareValueImpl(object_1, false);
|
||||
HostConditionalRenderingCompareBCImpl(object_1.address, true, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1420,7 +1520,8 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku
|
||||
auto driver_id = impl->device.GetDriverID();
|
||||
const bool is_gpu_high = Settings::IsGPULevelHigh();
|
||||
|
||||
if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
EndHostConditionalRendering();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1437,10 +1538,12 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku
|
||||
}
|
||||
|
||||
if (!is_gpu_high) {
|
||||
EndHostConditionalRendering();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_in_bc[0] && !is_in_bc[1]) {
|
||||
EndHostConditionalRendering();
|
||||
return true;
|
||||
}
|
||||
HostConditionalRenderingCompareBCImpl(object_1.address, equal_check);
|
||||
|
||||
@@ -63,7 +63,8 @@ public:
|
||||
|
||||
private:
|
||||
void HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object, bool is_equal);
|
||||
void HostConditionalRenderingCompareBCImpl(DAddr address, bool is_equal);
|
||||
void HostConditionalRenderingCompareBCImpl(DAddr address, bool is_equal,
|
||||
bool compare_to_zero = false);
|
||||
friend struct QueryCacheRuntimeImpl;
|
||||
std::unique_ptr<QueryCacheRuntimeImpl> impl;
|
||||
};
|
||||
|
||||
@@ -173,6 +173,28 @@ DrawParams MakeDrawParams(const MaxwellDrawState& draw_state, u32 num_instances,
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
bool SupportsPrimitiveRestart(VkPrimitiveTopology topology) {
|
||||
switch (topology) {
|
||||
case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
|
||||
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
|
||||
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
|
||||
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY:
|
||||
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY:
|
||||
case VK_PRIMITIVE_TOPOLOGY_PATCH_LIST:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPrimitiveRestartSupported(const Device& device, VkPrimitiveTopology topology) {
|
||||
return ((topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST &&
|
||||
device.IsTopologyListPrimitiveRestartSupported()) ||
|
||||
SupportsPrimitiveRestart(topology) ||
|
||||
(topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST &&
|
||||
device.IsPatchListPrimitiveRestartSupported()));
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
|
||||
@@ -225,6 +247,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
|
||||
|
||||
UpdateDynamicStates();
|
||||
|
||||
query_cache.NotifySegment(true);
|
||||
HandleTransformFeedback();
|
||||
query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
|
||||
maxwell3d->regs.zpass_pixel_count_enable);
|
||||
@@ -336,6 +359,7 @@ void RasterizerVulkan::DrawTexture() {
|
||||
|
||||
UpdateDynamicStates();
|
||||
|
||||
query_cache.NotifySegment(true);
|
||||
query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
|
||||
maxwell3d->regs.zpass_pixel_count_enable);
|
||||
const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState();
|
||||
@@ -575,11 +599,17 @@ void RasterizerVulkan::DispatchCompute() {
|
||||
}
|
||||
|
||||
void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) {
|
||||
if (type != VideoCommon::QueryType::ZPassPixelCount64) {
|
||||
switch (type) {
|
||||
case VideoCommon::QueryType::ZPassPixelCount64:
|
||||
case VideoCommon::QueryType::StreamingByteCount:
|
||||
case VideoCommon::QueryType::StreamingPrimitivesSucceeded:
|
||||
case VideoCommon::QueryType::VtgPrimitivesOut:
|
||||
query_cache.CounterReset(type);
|
||||
return;
|
||||
default:
|
||||
LOG_DEBUG(Render_Vulkan, "Unimplemented counter reset={}", type);
|
||||
return;
|
||||
}
|
||||
query_cache.CounterReset(type);
|
||||
}
|
||||
|
||||
void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
|
||||
@@ -766,6 +796,9 @@ void RasterizerVulkan::ReleaseFences(bool force) {
|
||||
|
||||
void RasterizerVulkan::FlushAndInvalidateRegion(DAddr addr, u64 size,
|
||||
VideoCommon::CacheType which) {
|
||||
if (Settings::IsGPULevelHigh()) {
|
||||
FlushRegion(addr, size, which);
|
||||
}
|
||||
InvalidateRegion(addr, size, which);
|
||||
}
|
||||
|
||||
@@ -830,6 +863,10 @@ bool RasterizerVulkan::AccelerateConditionalRendering() {
|
||||
return query_cache.AccelerateHostConditionalRendering();
|
||||
}
|
||||
|
||||
bool RasterizerVulkan::HasDrawTransformFeedback() {
|
||||
return device.IsTransformFeedbackDrawSupported();
|
||||
}
|
||||
|
||||
bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) {
|
||||
@@ -976,6 +1013,12 @@ bool AccelerateDMA::BufferToImage(const Tegra::DMA::ImageCopy& copy_info,
|
||||
|
||||
void RasterizerVulkan::UpdateDynamicStates() {
|
||||
auto& regs = maxwell3d->regs;
|
||||
auto& flags = maxwell3d->dirty.flags;
|
||||
const auto topology = maxwell3d->draw_manager->GetDrawState().topology;
|
||||
if (state_tracker.ChangePrimitiveTopology(topology)) {
|
||||
flags[Dirty::DepthBiasEnable] = true;
|
||||
flags[Dirty::PrimitiveRestartEnable] = true;
|
||||
}
|
||||
|
||||
// Core Dynamic States (Vulkan 1.0) - Always active regardless of dyna_state setting
|
||||
UpdateViewportsState(regs);
|
||||
@@ -1084,6 +1127,9 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg
|
||||
if (!state_tracker.TouchViewports()) {
|
||||
return;
|
||||
}
|
||||
|
||||
maxwell3d->dirty.flags[Dirty::Scissors] = true;
|
||||
|
||||
if (!regs.viewport_scale_offset_enabled) {
|
||||
float x = static_cast<float>(regs.surface_clip.x);
|
||||
float y = static_cast<float>(regs.surface_clip.y);
|
||||
@@ -1101,8 +1147,12 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f,
|
||||
};
|
||||
scheduler.Record([viewport](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetViewport(0, viewport);
|
||||
scheduler.Record([this, viewport](vk::CommandBuffer cmdbuf) {
|
||||
const u32 num_viewports = std::min<u32>(device.GetMaxViewports(), Maxwell::NumViewports);
|
||||
std::array<VkViewport, Maxwell::NumViewports> viewport_list{};
|
||||
viewport_list.fill(viewport);
|
||||
const vk::Span<VkViewport> viewports(viewport_list.data(), num_viewports);
|
||||
cmdbuf.SetViewport(0, viewports);
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -1142,8 +1192,12 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs
|
||||
scissor.offset.y = static_cast<int32_t>(y);
|
||||
scissor.extent.width = width;
|
||||
scissor.extent.height = height;
|
||||
scheduler.Record([scissor](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetScissor(0, scissor);
|
||||
scheduler.Record([this, scissor](vk::CommandBuffer cmdbuf) {
|
||||
const u32 num_scissors = std::min<u32>(device.GetMaxViewports(), Maxwell::NumViewports);
|
||||
std::array<VkRect2D, Maxwell::NumViewports> scissor_list{};
|
||||
scissor_list.fill(scissor);
|
||||
const vk::Span<VkRect2D> scissors(scissor_list.data(), num_scissors);
|
||||
cmdbuf.SetScissor(0, scissors);
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -1388,7 +1442,17 @@ void RasterizerVulkan::UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::R
|
||||
if (!state_tracker.TouchPrimitiveRestartEnable()) {
|
||||
return;
|
||||
}
|
||||
scheduler.Record([enable = regs.primitive_restart.enabled](vk::CommandBuffer cmdbuf) {
|
||||
|
||||
bool enable = regs.primitive_restart.enabled != 0;
|
||||
if (device.IsMoltenVK()) {
|
||||
enable = true;
|
||||
} else if (enable) {
|
||||
const auto topology =
|
||||
MaxwellToVK::PrimitiveTopology(device, maxwell3d->draw_manager->GetDrawState().topology);
|
||||
enable = IsPrimitiveRestartSupported(device, topology);
|
||||
}
|
||||
|
||||
scheduler.Record([enable](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetPrimitiveRestartEnableEXT(enable);
|
||||
});
|
||||
}
|
||||
@@ -1727,7 +1791,9 @@ void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs&
|
||||
|
||||
void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
auto& dirty{maxwell3d->dirty.flags};
|
||||
if (!dirty[Dirty::VertexInput]) {
|
||||
const bool vertex_input_dirty = dirty[Dirty::VertexInput];
|
||||
const bool vertex_buffers_dirty = dirty[VideoCommon::Dirty::VertexBuffers];
|
||||
if (!vertex_input_dirty && !vertex_buffers_dirty) {
|
||||
return;
|
||||
}
|
||||
dirty[Dirty::VertexInput] = false;
|
||||
@@ -1735,38 +1801,31 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
|
||||
boost::container::static_vector<VkVertexInputBindingDescription2EXT, 32> bindings;
|
||||
boost::container::static_vector<VkVertexInputAttributeDescription2EXT, 32> attributes;
|
||||
|
||||
// There seems to be a bug on Nvidia's driver where updating only higher attributes ends up
|
||||
// generating dirty state. Track the highest dirty attribute and update all attributes until
|
||||
// that one.
|
||||
size_t highest_dirty_attr{};
|
||||
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
|
||||
if (dirty[Dirty::VertexAttribute0 + index]) {
|
||||
highest_dirty_attr = index;
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < highest_dirty_attr; ++index) {
|
||||
const u32 max_attributes =
|
||||
static_cast<u32>(std::min<size_t>(Maxwell::NumVertexAttributes,
|
||||
device.GetMaxVertexInputAttributes()));
|
||||
const u32 max_bindings =
|
||||
static_cast<u32>(std::min<size_t>(Maxwell::NumVertexArrays,
|
||||
device.GetMaxVertexInputBindings()));
|
||||
|
||||
|
||||
for (u32 index = 0; index < max_attributes; ++index) {
|
||||
const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]};
|
||||
const u32 binding{attribute.buffer};
|
||||
dirty[Dirty::VertexAttribute0 + index] = false;
|
||||
dirty[Dirty::VertexBinding0 + static_cast<size_t>(binding)] = true;
|
||||
if (!attribute.constant) {
|
||||
attributes.push_back({
|
||||
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT,
|
||||
.pNext = nullptr,
|
||||
.location = static_cast<u32>(index),
|
||||
.binding = binding,
|
||||
.format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size),
|
||||
.offset = attribute.offset,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
|
||||
if (!dirty[Dirty::VertexBinding0 + index]) {
|
||||
if (attribute.constant || binding >= max_bindings) {
|
||||
continue;
|
||||
}
|
||||
dirty[Dirty::VertexBinding0 + index] = false;
|
||||
attributes.push_back({
|
||||
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT,
|
||||
.pNext = nullptr,
|
||||
.location = index,
|
||||
.binding = binding,
|
||||
.format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size),
|
||||
.offset = attribute.offset,
|
||||
});
|
||||
}
|
||||
|
||||
const u32 binding{static_cast<u32>(index)};
|
||||
for (u32 binding = 0; binding < max_bindings; ++binding) {
|
||||
const auto& input_binding{regs.vertex_streams[binding]};
|
||||
const bool is_instanced{regs.vertex_stream_instances.IsInstancingEnabled(binding)};
|
||||
bindings.push_back({
|
||||
@@ -1778,6 +1837,14 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
|
||||
.divisor = is_instanced ? input_binding.frequency : 1,
|
||||
});
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
|
||||
dirty[Dirty::VertexAttribute0 + index] = false;
|
||||
}
|
||||
for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||
dirty[Dirty::VertexBinding0 + index] = false;
|
||||
}
|
||||
|
||||
scheduler.Record([bindings, attributes](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetVertexInputEXT(bindings, attributes);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -122,6 +122,7 @@ public:
|
||||
void FlushCommands() override;
|
||||
void TickFrame() override;
|
||||
bool AccelerateConditionalRendering() override;
|
||||
bool HasDrawTransformFeedback() override;
|
||||
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) override;
|
||||
|
||||
@@ -324,6 +324,8 @@ void Scheduler::EndRenderPass()
|
||||
return;
|
||||
}
|
||||
|
||||
query_cache->CounterClose(VideoCommon::QueryType::StreamingByteCount);
|
||||
|
||||
// Log render pass end
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
|
||||
@@ -63,6 +63,11 @@ public:
|
||||
/// of a renderpass.
|
||||
void RequestOutsideRenderPassOperationContext();
|
||||
|
||||
/// Returns true when a render pass is currently active in the scheduler state.
|
||||
bool IsRenderPassActive() const {
|
||||
return state.renderpass != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
/// Update the pipeline to the current execution context.
|
||||
bool UpdateGraphicsPipeline(GraphicsPipeline* pipeline);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -87,6 +87,7 @@ Flags MakeInvalidationFlags() {
|
||||
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);
|
||||
tables[0][OFF(viewport_scale_offset_enabled)] = Viewports;
|
||||
tables[1][OFF(window_origin)] = Viewports;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
|
||||
@@ -176,7 +176,18 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
.pViewFormats = view_formats.data(),
|
||||
};
|
||||
if (view_formats.size() > 1) {
|
||||
image_ci.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||
image_ci.flags |=
|
||||
VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
|
||||
|
||||
const bool has_storage_compatible_view =
|
||||
std::any_of(view_formats.begin(), view_formats.end(), [&device](VkFormat view_format) {
|
||||
return device.IsFormatSupported(view_format, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT,
|
||||
FormatType::Optimal);
|
||||
});
|
||||
if (has_storage_compatible_view) {
|
||||
image_ci.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
}
|
||||
|
||||
if (device.IsKhrImageFormatListSupported()) {
|
||||
image_ci.pNext = &image_format_list;
|
||||
}
|
||||
@@ -668,11 +679,16 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
|
||||
}
|
||||
|
||||
void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle,
|
||||
bool emulate_a4b4g4r4) {
|
||||
bool emulate_bgr565, bool emulate_a4b4g4r4) {
|
||||
switch (format) {
|
||||
case PixelFormat::A1B5G5R5_UNORM:
|
||||
std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
|
||||
break;
|
||||
case PixelFormat::B5G6R5_UNORM:
|
||||
if (emulate_bgr565) {
|
||||
std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
|
||||
}
|
||||
break;
|
||||
case PixelFormat::A5B5G5R1_UNORM:
|
||||
std::ranges::transform(swizzle, swizzle.begin(), SwapSpecial);
|
||||
break;
|
||||
@@ -2119,22 +2135,21 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
|
||||
if (!info.IsRenderTarget()) {
|
||||
swizzle = info.Swizzle();
|
||||
TryTransformSwizzleIfNeeded(format, swizzle,
|
||||
!device->IsExt4444FormatsSupported());
|
||||
device->MustEmulateBGR565(),
|
||||
!device->IsExt4444FormatsSupported());
|
||||
if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
|
||||
std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
|
||||
SanitizeDepthStencilSwizzle(swizzle, device->SupportsDepthStencilSwizzleOne());
|
||||
}
|
||||
}
|
||||
const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
|
||||
if (ImageUsageFlags(format_info, format) != image.UsageFlags()) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Image view format {} has different usage flags than image format {}", format,
|
||||
image.info.format);
|
||||
}
|
||||
const VkImageUsageFlags requested_view_usage = ImageUsageFlags(format_info, format);
|
||||
const VkImageUsageFlags image_usage = image.UsageFlags();
|
||||
const VkImageUsageFlags clamped_view_usage = requested_view_usage & image_usage;
|
||||
const VkImageViewUsageCreateInfo image_view_usage{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.usage = ImageUsageFlags(format_info, format),
|
||||
.usage = clamped_view_usage,
|
||||
};
|
||||
const VkImageViewCreateInfo create_info{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
@@ -2300,23 +2315,18 @@ vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_
|
||||
|
||||
Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& tsc) {
|
||||
const auto& device = runtime.device;
|
||||
// Check if custom border colors are supported
|
||||
const bool has_custom_border_colors = runtime.device.IsCustomBorderColorsSupported();
|
||||
const bool has_format_undefined = runtime.device.IsCustomBorderColorWithoutFormatSupported();
|
||||
const bool has_custom_border_extension = runtime.device.IsExtCustomBorderColorSupported();
|
||||
const bool has_format_undefined =
|
||||
has_custom_border_extension && runtime.device.IsCustomBorderColorWithoutFormatSupported();
|
||||
const bool has_custom_border_colors =
|
||||
has_format_undefined && runtime.device.IsCustomBorderColorsSupported();
|
||||
const auto color = tsc.BorderColor();
|
||||
|
||||
// Determine border format based on available features:
|
||||
// - If customBorderColorWithoutFormat is available: use VK_FORMAT_UNDEFINED (most flexible)
|
||||
// - If only customBorderColors is available: use concrete format (R8G8B8A8_UNORM)
|
||||
// - If neither is available: use standard border colors (handled by ConvertBorderColor)
|
||||
const VkFormat border_format = has_format_undefined ? VK_FORMAT_UNDEFINED
|
||||
: VK_FORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
const VkSamplerCustomBorderColorCreateInfoEXT border_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.customBorderColor = std::bit_cast<VkClearColorValue>(color),
|
||||
.format = border_format,
|
||||
.format = VK_FORMAT_UNDEFINED,
|
||||
};
|
||||
const void* pnext = nullptr;
|
||||
if (has_custom_border_colors) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -88,13 +88,13 @@ std::pair<std::array<Shader::TransformFeedbackVarying, 256>, u32> MakeTransformF
|
||||
return 0;
|
||||
};
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(layout.stream != 0, "Stream is not zero: {}", layout.stream);
|
||||
Shader::TransformFeedbackVarying varying{
|
||||
.buffer = static_cast<u32>(buffer),
|
||||
.stride = layout.stride,
|
||||
.offset = offset * 4,
|
||||
.components = 1,
|
||||
};
|
||||
varying.stream = layout.stream;
|
||||
const u32 base_offset = offset;
|
||||
const auto attribute{get_attribute(offset)};
|
||||
if (std::ranges::find(VECTORS, Common::AlignDown(attribute, 4)) != VECTORS.end()) {
|
||||
|
||||
@@ -869,6 +869,10 @@ bool Device::HasTimelineSemaphore() const {
|
||||
return features.timeline_semaphore.timelineSemaphore;
|
||||
}
|
||||
|
||||
bool Device::MustEmulateBGR565() const {
|
||||
return Settings::values.emulate_bgr565.GetValue();
|
||||
}
|
||||
|
||||
bool Device::GetSuitability(bool requires_swapchain) {
|
||||
// Assume we will be suitable.
|
||||
bool suitable = true;
|
||||
@@ -919,6 +923,17 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
|
||||
FOR_EACH_VK_EXTENSION(EXTENSION);
|
||||
|
||||
if (supported_extensions.contains(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)) {
|
||||
loaded_extensions.erase(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
loaded_extensions.insert(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
extensions.robustness_2 = true;
|
||||
} else if (supported_extensions.contains(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)) {
|
||||
loaded_extensions.insert(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
extensions.robustness_2 = true;
|
||||
} else {
|
||||
extensions.robustness_2 = false;
|
||||
}
|
||||
|
||||
#undef FEATURE_EXTENSION
|
||||
#undef EXTENSION
|
||||
|
||||
@@ -1131,8 +1146,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
|
||||
if (u32(Settings::values.dyna_state.GetValue()) == 0) {
|
||||
LOG_INFO(Render_Vulkan, "Extended Dynamic State disabled by user setting, clearing all EDS features");
|
||||
features.custom_border_color.customBorderColors = false;
|
||||
features.custom_border_color.customBorderColorWithoutFormat = false;
|
||||
features.extended_dynamic_state.extendedDynamicState = false;
|
||||
features.extended_dynamic_state2.extendedDynamicState2 = false;
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
|
||||
@@ -1148,24 +1161,13 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
|
||||
void Device::RemoveUnsuitableExtensions() {
|
||||
// VK_EXT_custom_border_color
|
||||
// Enable extension if driver supports it, then check individual features
|
||||
// - customBorderColors: Required to use VK_BORDER_COLOR_FLOAT_CUSTOM_EXT
|
||||
// - customBorderColorWithoutFormat: Optional, allows VK_FORMAT_UNDEFINED
|
||||
// If only customBorderColors is available, we must provide a specific format
|
||||
if (extensions.custom_border_color) {
|
||||
// Verify that at least customBorderColors is available
|
||||
if (!features.custom_border_color.customBorderColors) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"VK_EXT_custom_border_color reported but customBorderColors feature not available, disabling");
|
||||
extensions.custom_border_color = false;
|
||||
}
|
||||
extensions.custom_border_color =
|
||||
features.custom_border_color.customBorderColors &&
|
||||
features.custom_border_color.customBorderColorWithoutFormat;
|
||||
}
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
|
||||
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
||||
// VK_KHR_unified_image_layouts
|
||||
extensions.unified_image_layouts = features.unified_image_layouts.unifiedImageLayouts;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.unified_image_layouts, features.unified_image_layouts,
|
||||
VK_KHR_UNIFIED_IMAGE_LAYOUTS_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_depth_bias_control
|
||||
extensions.depth_bias_control =
|
||||
@@ -1251,16 +1253,22 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_robustness2
|
||||
extensions.robustness_2 = features.robustness2.robustBufferAccess2 ||
|
||||
features.robustness2.robustImageAccess2 ||
|
||||
features.robustness2.nullDescriptor;
|
||||
features.robustness2.robustBufferAccess2 = VK_FALSE;
|
||||
features.robustness2.robustImageAccess2 = VK_FALSE;
|
||||
extensions.robustness_2 = features.robustness2.nullDescriptor;
|
||||
|
||||
const char* robustness2_extension_name =
|
||||
loaded_extensions.contains(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)
|
||||
? VK_KHR_ROBUSTNESS_2_EXTENSION_NAME
|
||||
: VK_EXT_ROBUSTNESS_2_EXTENSION_NAME;
|
||||
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.robustness_2, features.robustness2,
|
||||
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
robustness2_extension_name);
|
||||
|
||||
// VK_EXT_image_robustness
|
||||
extensions.image_robustness = features.image_robustness.robustImageAccess;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.image_robustness, features.image_robustness,
|
||||
// Image robustness
|
||||
extensions.robust_image_access = features.robust_image_access.robustImageAccess;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.robust_image_access,
|
||||
features.robust_image_access,
|
||||
VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_shader_atomic_int64
|
||||
@@ -1288,8 +1296,7 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
// VK_EXT_transform_feedback
|
||||
extensions.transform_feedback =
|
||||
features.transform_feedback.transformFeedback &&
|
||||
properties.transform_feedback.maxTransformFeedbackBuffers > 0 &&
|
||||
properties.transform_feedback.transformFeedbackQueries;
|
||||
properties.transform_feedback.maxTransformFeedbackBuffers > 0;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback,
|
||||
VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore)
|
||||
|
||||
#define FOR_EACH_VK_FEATURE_1_3(FEATURE) \
|
||||
FEATURE(EXT, ImageRobustness, IMAGE_ROBUSTNESS, image_robustness) \
|
||||
FEATURE(EXT, ImageRobustness, IMAGE_ROBUSTNESS, robust_image_access) \
|
||||
FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \
|
||||
shader_demote_to_helper_invocation) \
|
||||
FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control) \
|
||||
@@ -68,8 +68,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
FEATURE(KHR, PipelineExecutableProperties, PIPELINE_EXECUTABLE_PROPERTIES, \
|
||||
pipeline_executable_properties) \
|
||||
FEATURE(KHR, WorkgroupMemoryExplicitLayout, WORKGROUP_MEMORY_EXPLICIT_LAYOUT, \
|
||||
workgroup_memory_explicit_layout) \
|
||||
FEATURE(KHR, UnifiedImageLayouts, UNIFIED_IMAGE_LAYOUTS, unified_image_layouts)
|
||||
workgroup_memory_explicit_layout)
|
||||
|
||||
|
||||
// Define miscellaneous extensions which may be used by the implementation here.
|
||||
@@ -124,7 +123,6 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_4444_FORMATS_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \
|
||||
@@ -174,13 +172,11 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
FEATURE_NAME(depth_bias_control, depthBiasExact) \
|
||||
FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \
|
||||
FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \
|
||||
FEATURE_NAME(image_robustness, robustImageAccess) \
|
||||
FEATURE_NAME(robust_image_access, robustImageAccess) \
|
||||
FEATURE_NAME(index_type_uint8, indexTypeUint8) \
|
||||
FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \
|
||||
FEATURE_NAME(provoking_vertex, provokingVertexLast) \
|
||||
FEATURE_NAME(robustness2, nullDescriptor) \
|
||||
FEATURE_NAME(robustness2, robustBufferAccess2) \
|
||||
FEATURE_NAME(robustness2, robustImageAccess2) \
|
||||
FEATURE_NAME(shader_float16_int8, shaderFloat16) \
|
||||
FEATURE_NAME(shader_float16_int8, shaderInt8) \
|
||||
FEATURE_NAME(timeline_semaphore, timelineSemaphore) \
|
||||
@@ -542,6 +538,17 @@ public:
|
||||
return extensions.transform_feedback;
|
||||
}
|
||||
|
||||
/// Returns true if transform feedback draw commands are supported.
|
||||
bool IsTransformFeedbackDrawSupported() const {
|
||||
return extensions.transform_feedback && properties.transform_feedback.transformFeedbackDraw;
|
||||
}
|
||||
|
||||
/// Returns true if transform feedback query types are supported.
|
||||
bool IsTransformFeedbackQueriesSupported() const {
|
||||
return extensions.transform_feedback &&
|
||||
properties.transform_feedback.transformFeedbackQueries;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_transform_feedback properly.
|
||||
bool AreTransformFeedbackGeometryStreamsSupported() const {
|
||||
return features.transform_feedback.geometryStreams;
|
||||
@@ -552,36 +559,6 @@ public:
|
||||
return extensions.custom_border_color;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_image_robustness.
|
||||
bool IsExtImageRobustnessSupported() const {
|
||||
return extensions.image_robustness;
|
||||
}
|
||||
|
||||
/// Returns true if robustImageAccess is supported.
|
||||
bool IsRobustImageAccessSupported() const {
|
||||
return features.image_robustness.robustImageAccess;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_robustness2.
|
||||
bool IsExtRobustness2Supported() const {
|
||||
return extensions.robustness_2;
|
||||
}
|
||||
|
||||
/// Returns true if robustBufferAccess2 is supported.
|
||||
bool IsRobustBufferAccess2Supported() const {
|
||||
return features.robustness2.robustBufferAccess2;
|
||||
}
|
||||
|
||||
/// Returns true if robustImageAccess2 is supported.
|
||||
bool IsRobustImageAccess2Supported() const {
|
||||
return features.robustness2.robustImageAccess2;
|
||||
}
|
||||
|
||||
/// Returns true if nullDescriptor is supported.
|
||||
bool IsNullDescriptorSupported() const {
|
||||
return features.robustness2.nullDescriptor;
|
||||
}
|
||||
|
||||
/// Returns true if customBorderColors feature is available.
|
||||
bool IsCustomBorderColorsSupported() const {
|
||||
return features.custom_border_color.customBorderColors;
|
||||
@@ -805,6 +782,8 @@ public:
|
||||
return features.robustness2.nullDescriptor;
|
||||
}
|
||||
|
||||
bool MustEmulateBGR565() const;
|
||||
|
||||
bool HasExactDepthBiasControl() const {
|
||||
return features.depth_bias_control.depthBiasExact;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
||||
X(vkCmdEndDebugUtilsLabelEXT);
|
||||
X(vkCmdFillBuffer);
|
||||
X(vkCmdPipelineBarrier);
|
||||
X(vkCmdResetQueryPool);
|
||||
X(vkCmdPushConstants);
|
||||
X(vkCmdPushDescriptorSetWithTemplateKHR);
|
||||
X(vkCmdSetBlendConstants);
|
||||
|
||||
@@ -225,6 +225,7 @@ struct DeviceDispatch : InstanceDispatch {
|
||||
PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT{};
|
||||
PFN_vkCmdFillBuffer vkCmdFillBuffer{};
|
||||
PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier{};
|
||||
PFN_vkCmdResetQueryPool vkCmdResetQueryPool{};
|
||||
PFN_vkCmdPushConstants vkCmdPushConstants{};
|
||||
PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR{};
|
||||
PFN_vkCmdResolveImage vkCmdResolveImage{};
|
||||
@@ -1168,6 +1169,10 @@ public:
|
||||
dld->vkCmdEndQuery(handle, query_pool, query);
|
||||
}
|
||||
|
||||
void ResetQueryPool(VkQueryPool query_pool, u32 first_query, u32 query_count) const noexcept {
|
||||
dld->vkCmdResetQueryPool(handle, query_pool, first_query, query_count);
|
||||
}
|
||||
|
||||
void BindDescriptorSets(VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 first,
|
||||
Span<VkDescriptorSet> sets, Span<u32> dynamic_offsets) const noexcept {
|
||||
dld->vkCmdBindDescriptorSets(handle, bind_point, layout, first, sets.size(), sets.data(),
|
||||
|
||||
Reference in New Issue
Block a user