mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-25 00:40:32 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cb9e3a9da | |||
| 7e1fc154c7 | |||
| 828220977b | |||
| 0684757788 | |||
| 30c44934e7 | |||
| 81efd5f1d1 | |||
| c51b9fed0f | |||
| 5157251457 | |||
| 71b9d8d3df | |||
| b6959029ca | |||
| 81aaf67c97 | |||
| 9289f5f55a | |||
| b38556d4f6 | |||
| 1616d19279 | |||
| 40756bbc52 | |||
| 424eb62cdb | |||
| e755504ed8 | |||
| b5d82311f6 | |||
| 6b65b2b6fc | |||
| 7fcdd9c9e4 | |||
| df681d1bd2 | |||
| bee168c5fa | |||
| d754fef20e | |||
| 0408db7adb | |||
| ead7505d00 | |||
| 65568e320d | |||
| 4154787b51 | |||
| 6f3e1b4471 | |||
| 240b65d61e | |||
| 461fa8ab6e | |||
| 0ef8acd602 | |||
| dbf5ee4e1f | |||
| e248832967 | |||
| 17167a27dc | |||
| a4bcb4a1e1 | |||
| b9ec214553 | |||
| 43b28d3731 |
@@ -65,7 +65,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "dev.eden.eden_emulator"
|
||||
minSdk = 24
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionName = getGitVersion()
|
||||
versionCode = autoVersion
|
||||
|
||||
@@ -50,7 +50,6 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
GPU_UNSWIZZLE_TEXTURE_SIZE("gpu_unswizzle_texture_size"),
|
||||
GPU_UNSWIZZLE_STREAM_SIZE("gpu_unswizzle_stream_size"),
|
||||
GPU_UNSWIZZLE_CHUNK_SIZE("gpu_unswizzle_chunk_size"),
|
||||
HOMEBREW_NXLINK_SERVER_MODE("homebrew_nxlink_server_mode"),
|
||||
BAT_TEMPERATURE_UNIT("bat_temperature_unit"),
|
||||
CABINET_APPLET("cabinet_applet_mode"),
|
||||
CONTROLLER_APPLET("controller_applet_mode"),
|
||||
|
||||
+1
-10
@@ -132,15 +132,6 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.program_args_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.HOMEBREW_NXLINK_SERVER_MODE,
|
||||
titleId = R.string.nxlink_server_mode,
|
||||
descriptionId = R.string.nxlink_server_mode_description,
|
||||
choicesId = R.array.nxlinkServerModeEntries,
|
||||
valuesId = R.array.nxlinkServerModeValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||
@@ -603,7 +594,7 @@ abstract class SettingsItem(
|
||||
IntSetting.ANDROID_PIPELINE_WORKERS,
|
||||
titleId = R.string.pipeline_worker_cores,
|
||||
descriptionId = R.string.pipeline_worker_cores_description,
|
||||
min = 4,
|
||||
min = 2,
|
||||
max = 8,
|
||||
units = "cores"
|
||||
)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
@@ -169,7 +169,7 @@ class InputDialogFragment : DialogFragment() {
|
||||
NativeInput.onGamePadButtonEvent(
|
||||
controllerData.getGUID(),
|
||||
controllerData.getPort(),
|
||||
InputHandler.getButtonIdFromEvent(event),
|
||||
event.keyCode,
|
||||
action
|
||||
)
|
||||
onInputReceived(event.device)
|
||||
|
||||
-1
@@ -1288,7 +1288,6 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(ShortSetting.DEBUG_KNOBS.key)
|
||||
add(StringSetting.PROGRAM_ARGS.key)
|
||||
add(IntSetting.HOMEBREW_NXLINK_SERVER_MODE.key)
|
||||
|
||||
if (!NativeConfig.isPerGameConfigLoaded()) {
|
||||
add(HeaderSetting(R.string.gpu_logging_header))
|
||||
|
||||
@@ -49,12 +49,6 @@ object InputHandler {
|
||||
MotionEvent.AXIS_RTRIGGER
|
||||
)
|
||||
|
||||
// Currently, Android doesn't support Joy-Con D-pad buttons. We fall back to the scan code
|
||||
private const val LINUX_BUTTON_DPAD_UP = 0x220
|
||||
private const val LINUX_BUTTON_DPAD_DOWN = 0x221
|
||||
private const val LINUX_BUTTON_DPAD_LEFT = 0x222
|
||||
private const val LINUX_BUTTON_DPAD_RIGHT = 0x223
|
||||
|
||||
fun isPhysicalGameController(device: InputDevice?): Boolean {
|
||||
device ?: return false
|
||||
|
||||
@@ -93,25 +87,12 @@ object InputHandler {
|
||||
NativeInput.onGamePadButtonEvent(
|
||||
controllerData.getGUID(),
|
||||
controllerData.getPort(),
|
||||
getButtonIdFromEvent(event),
|
||||
event.keyCode,
|
||||
action
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
fun getButtonIdFromEvent(event: KeyEvent): Int {
|
||||
if (event.keyCode == 0) {
|
||||
return when (event.scanCode) {
|
||||
LINUX_BUTTON_DPAD_UP -> KeyEvent.KEYCODE_DPAD_UP
|
||||
LINUX_BUTTON_DPAD_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN
|
||||
LINUX_BUTTON_DPAD_LEFT -> KeyEvent.KEYCODE_DPAD_LEFT
|
||||
LINUX_BUTTON_DPAD_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT
|
||||
else -> return 0
|
||||
}
|
||||
}
|
||||
return event.keyCode
|
||||
}
|
||||
|
||||
fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
val controllerData =
|
||||
androidControllers[event.device.controllerNumber] ?: return false
|
||||
|
||||
@@ -630,16 +630,6 @@
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="nxlinkServerModeEntries">
|
||||
<item>Disabled</item>
|
||||
<item>Eden Log</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="nxlinkServerModeValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="installKeysResults">
|
||||
<item>""</item>
|
||||
<item>""</item>
|
||||
|
||||
@@ -436,8 +436,6 @@
|
||||
|
||||
<string name="program_args">Homebrew Args</string>
|
||||
<string name="program_args_description">Command-line arguments passed to homebrew at launch (e.g. -noglsl).</string>
|
||||
<string name="nxlink_server_mode">nxlink Server</string>
|
||||
<string name="nxlink_server_mode_description">Starts a local nxlink server for homebrew stdout/stderr streams.</string>
|
||||
|
||||
<!-- System settings strings -->
|
||||
<string name="device_name">Device name</string>
|
||||
|
||||
@@ -336,22 +336,22 @@ ALLOC_MEMBER(VaType)::Allocate(VaType size) {
|
||||
current_linear_alloc_end = alloc_start + size;
|
||||
} else { // If linear allocation overflows the AS then find a gap
|
||||
if (this->blocks.size() <= 2) {
|
||||
alloc_start = virt_start;
|
||||
ASSERT_MSG(false, "Unexpected allocator state!");
|
||||
}
|
||||
|
||||
auto search_predecessor{std::next(this->blocks.begin())};
|
||||
auto search_successor{std::next(search_predecessor)};
|
||||
|
||||
while (search_successor != this->blocks.end() &&
|
||||
(search_successor->virt - search_predecessor->virt < size ||
|
||||
search_predecessor->Mapped())) {
|
||||
search_predecessor = search_successor++;
|
||||
}
|
||||
|
||||
if (search_successor != this->blocks.end()) {
|
||||
alloc_start = search_predecessor->virt;
|
||||
} else {
|
||||
auto search_predecessor{std::next(this->blocks.begin())};
|
||||
auto search_successor{std::next(search_predecessor)};
|
||||
|
||||
while (search_successor != this->blocks.end() &&
|
||||
(search_successor->virt - search_predecessor->virt < size ||
|
||||
search_predecessor->Mapped())) {
|
||||
search_predecessor = search_successor++;
|
||||
}
|
||||
|
||||
if (search_successor != this->blocks.end()) {
|
||||
alloc_start = search_predecessor->virt;
|
||||
} else {
|
||||
return {}; // AS is full
|
||||
}
|
||||
return {}; // AS is full
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,10 +364,6 @@ ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
|
||||
}
|
||||
|
||||
ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
|
||||
const VaType virt_end = virt + size;
|
||||
this->Unmap(virt, size);
|
||||
if (virt_end >= virt && virt_end == current_linear_alloc_end) {
|
||||
current_linear_alloc_end = virt < virt_start ? virt_start : virt;
|
||||
}
|
||||
}
|
||||
} // namespace Common
|
||||
|
||||
+1
-62
@@ -4,8 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -17,10 +15,8 @@
|
||||
#include "common/logging.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <share.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -99,65 +95,10 @@ namespace {
|
||||
case FileShareFlag::ShareWriteOnly:
|
||||
return _SH_DENYRD;
|
||||
case FileShareFlag::ShareReadWrite:
|
||||
case FileShareFlag::ShareReadWriteDelete:
|
||||
return _SH_DENYNO;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::FILE* OpenWithWindowsShareDelete(const fs::path& path, FileAccessMode mode,
|
||||
FileType type) {
|
||||
DWORD desired_access{};
|
||||
DWORD creation_disposition{OPEN_EXISTING};
|
||||
int open_flags = type == FileType::BinaryFile ? _O_BINARY : _O_TEXT;
|
||||
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
desired_access = GENERIC_READ;
|
||||
open_flags |= _O_RDONLY;
|
||||
break;
|
||||
case FileAccessMode::Write:
|
||||
desired_access = GENERIC_WRITE;
|
||||
creation_disposition = CREATE_ALWAYS;
|
||||
open_flags |= _O_WRONLY;
|
||||
break;
|
||||
case FileAccessMode::Append:
|
||||
desired_access = GENERIC_WRITE;
|
||||
creation_disposition = OPEN_ALWAYS;
|
||||
open_flags |= _O_WRONLY | _O_APPEND;
|
||||
break;
|
||||
case FileAccessMode::ReadWrite:
|
||||
desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
open_flags |= _O_RDWR;
|
||||
break;
|
||||
case FileAccessMode::ReadAppend:
|
||||
desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
creation_disposition = OPEN_ALWAYS;
|
||||
open_flags |= _O_RDWR | _O_APPEND;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto handle =
|
||||
CreateFileW(path.c_str(), desired_access,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
errno = EACCES;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto fd = _open_osfhandle(reinterpret_cast<intptr_t>(handle), open_flags);
|
||||
if (fd == -1) {
|
||||
CloseHandle(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* const file = _wfdopen(fd, AccessModeToWStr(mode, type));
|
||||
if (file == nullptr) {
|
||||
_close(fd);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
@@ -313,9 +254,7 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (flag == FileShareFlag::ShareReadWriteDelete) {
|
||||
file = OpenWithWindowsShareDelete(path, mode, type);
|
||||
} else if (flag != FileShareFlag::ShareNone) {
|
||||
if (flag != FileShareFlag::ShareNone) {
|
||||
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
|
||||
} else {
|
||||
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -49,11 +49,10 @@ enum class FileType {
|
||||
};
|
||||
|
||||
enum class FileShareFlag {
|
||||
ShareNone, // Provides exclusive access to the file.
|
||||
ShareReadOnly, // Provides read only shared access to the file.
|
||||
ShareWriteOnly, // Provides write only shared access to the file.
|
||||
ShareReadWrite, // Provides read and write shared access to the file.
|
||||
ShareReadWriteDelete, // Provides read, write, and delete shared access to the file.
|
||||
ShareNone, // Provides exclusive access to the file.
|
||||
ShareReadOnly, // Provides read only shared access to the file.
|
||||
ShareWriteOnly, // Provides write only shared access to the file.
|
||||
ShareReadWrite, // Provides read and write shared access to the file.
|
||||
};
|
||||
|
||||
enum class DirEntryFilter {
|
||||
|
||||
+1
-12
@@ -702,15 +702,7 @@ struct Values {
|
||||
|
||||
// Controls
|
||||
InputSetting<std::array<PlayerInput, 10>> players;
|
||||
Setting<bool> disable_wgi_xinput{
|
||||
linkage, false, "disable_wgi_xinput", Category::Controls, Specialization::Default,
|
||||
// Only read/write disable_wgi_xinput on Windows platforms
|
||||
#ifdef _WIN32
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
};
|
||||
|
||||
Setting<bool> enable_raw_input{
|
||||
linkage, false, "enable_raw_input", Category::Controls, Specialization::Default,
|
||||
// Only read/write enable_raw_input on Windows platforms
|
||||
@@ -840,9 +832,6 @@ struct Values {
|
||||
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
|
||||
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
|
||||
Category::Debugging};
|
||||
Setting<HomebrewNxlinkServerMode> homebrew_nxlink_server_mode{
|
||||
linkage, HomebrewNxlinkServerMode::Disabled, "homebrew_nxlink_server_mode",
|
||||
Category::Debugging};
|
||||
|
||||
SwitchableSetting<u16, true> debug_knobs{linkage,
|
||||
0,
|
||||
|
||||
@@ -159,7 +159,6 @@ ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||
ENUM(HomebrewNxlinkServerMode, Disabled, EdenLog, HostStdout, File)
|
||||
ENUM(GameListMode, TreeView, GridView, CarouselView);
|
||||
ENUM(SpeedMode, Standard, Turbo, Slow);
|
||||
|
||||
|
||||
+140
-21
@@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -39,6 +40,110 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <sys/resource.h>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_URGENT_AUDIO = -19;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_AUDIO = -16;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_URGENT_DISPLAY = -8;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_DISPLAY = -4;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_FOREGROUND = -2;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_MORE_FAVORABLE = -1;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_DEFAULT = 0;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_LESS_FAVORABLE = 1;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_BACKGROUND = 10;
|
||||
[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_LOWEST = 19;
|
||||
|
||||
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
|
||||
|
||||
cpu_set_t ComputePerformanceCoreMask() {
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO(&mask);
|
||||
|
||||
cpu_set_t allowed;
|
||||
CPU_ZERO(&allowed);
|
||||
if (sched_getaffinity(gettid(), sizeof(allowed), &allowed) != 0) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::vector<std::pair<long, int>> cores;
|
||||
const int total = static_cast<int>(std::thread::hardware_concurrency());
|
||||
for (int cpu = 0; cpu < total; ++cpu) {
|
||||
if (!CPU_ISSET(cpu, &allowed)) {
|
||||
continue;
|
||||
}
|
||||
long max_frequency = 0;
|
||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) +
|
||||
"/cpufreq/cpuinfo_max_freq");
|
||||
if (!file || !(file >> max_frequency) || max_frequency <= 0) {
|
||||
CPU_ZERO(&mask);
|
||||
return mask;
|
||||
}
|
||||
cores.emplace_back(max_frequency, cpu);
|
||||
}
|
||||
if (cores.empty()) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::sort(cores.begin(), cores.end(),
|
||||
[](const auto& lhs, const auto& rhs) { return lhs.first > rhs.first; });
|
||||
|
||||
size_t taken = 0;
|
||||
long cluster_frequency = cores.front().first;
|
||||
for (const auto& [frequency, cpu] : cores) {
|
||||
if (frequency != cluster_frequency) {
|
||||
if (taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
|
||||
break;
|
||||
}
|
||||
cluster_frequency = frequency;
|
||||
}
|
||||
CPU_SET(cpu, &mask);
|
||||
++taken;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
const cpu_set_t& PerformanceCoreMask() {
|
||||
static const cpu_set_t mask = ComputePerformanceCoreMask();
|
||||
return mask;
|
||||
}
|
||||
|
||||
cpu_set_t ComputeEfficiencyCoreMask() {
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO(&mask);
|
||||
|
||||
const cpu_set_t& performance = PerformanceCoreMask();
|
||||
if (CPU_COUNT(&performance) == 0) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
cpu_set_t allowed;
|
||||
CPU_ZERO(&allowed);
|
||||
if (sched_getaffinity(gettid(), sizeof(allowed), &allowed) != 0) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
const int total = static_cast<int>(std::thread::hardware_concurrency());
|
||||
for (int cpu = 0; cpu < total; ++cpu) {
|
||||
if (CPU_ISSET(cpu, &allowed) && !CPU_ISSET(cpu, &performance)) {
|
||||
CPU_SET(cpu, &mask);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
const cpu_set_t& EfficiencyCoreMask() {
|
||||
static const cpu_set_t mask = ComputeEfficiencyCoreMask();
|
||||
return mask;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
#endif
|
||||
|
||||
#include "common/cpu_features.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#ifdef _MSC_VER
|
||||
@@ -78,6 +183,21 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
}
|
||||
}();
|
||||
set_thread_priority(find_thread(NULL), priority);
|
||||
#elif defined(__ANDROID__)
|
||||
const int nice_value = [&]() {
|
||||
switch (new_priority) {
|
||||
case ThreadPriority::Low: return ANDROID_THREAD_PRIORITY_BACKGROUND;
|
||||
case ThreadPriority::Normal: return ANDROID_THREAD_PRIORITY_DEFAULT;
|
||||
case ThreadPriority::High: return ANDROID_THREAD_PRIORITY_DISPLAY;
|
||||
case ThreadPriority::VeryHigh: return ANDROID_THREAD_PRIORITY_URGENT_DISPLAY;
|
||||
case ThreadPriority::Critical: return ANDROID_THREAD_PRIORITY_AUDIO;
|
||||
default: return ANDROID_THREAD_PRIORITY_DEFAULT;
|
||||
}
|
||||
}();
|
||||
if (setpriority(PRIO_PROCESS, static_cast<id_t>(gettid()), nice_value) != 0) {
|
||||
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
}
|
||||
#else
|
||||
pthread_t this_thread = pthread_self();
|
||||
const auto scheduling_type = SCHED_OTHER;
|
||||
@@ -132,29 +252,28 @@ void SetCurrentThreadName(const char* name) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id) {
|
||||
ASSERT(core_id < 4);
|
||||
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
|
||||
// run in ANY processor!
|
||||
auto const total_cores = std::thread::hardware_concurrency();
|
||||
if (core_id < total_cores) {
|
||||
void SetCurrentThreadToPerformanceCores() {
|
||||
#if defined(__ANDROID__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
sched_setaffinity(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(_WIN32)
|
||||
DWORD set = 1UL << core_id;
|
||||
SetThreadAffinityMask(GetCurrentThread(), set);
|
||||
#else
|
||||
// No pin functionality implemented
|
||||
#endif
|
||||
const cpu_set_t& mask = PerformanceCoreMask();
|
||||
if (CPU_COUNT(&mask) == 0) {
|
||||
return;
|
||||
}
|
||||
if (sched_setaffinity(gettid(), sizeof(mask), &mask) != 0) {
|
||||
LOG_DEBUG(Common, "Could not restrict thread to performance cores: {}", GetLastErrorMsg());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToEfficiencyCores() {
|
||||
#if defined(__ANDROID__)
|
||||
const cpu_set_t& mask = EfficiencyCoreMask();
|
||||
if (CPU_COUNT(&mask) == 0) {
|
||||
return;
|
||||
}
|
||||
if (sched_setaffinity(gettid(), sizeof(mask), &mask) != 0) {
|
||||
LOG_DEBUG(Common, "Could not restrict thread to efficiency cores: {}", GetLastErrorMsg());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
+7
-1
@@ -99,8 +99,14 @@ enum class ThreadPriority : u32 {
|
||||
Critical = 4,
|
||||
};
|
||||
|
||||
enum class ThreadPlacement : u32 {
|
||||
Default = 0,
|
||||
Background = 1,
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
void SetCurrentThreadName(const char* name);
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||
void SetCurrentThreadToPerformanceCores();
|
||||
void SetCurrentThreadToEfficiencyCores();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -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
|
||||
@@ -37,10 +37,15 @@ class StatefulThreadWorker {
|
||||
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
||||
|
||||
public:
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {},
|
||||
ThreadPlacement placement = ThreadPlacement::Default)
|
||||
: workers_queued{num_workers}, thread_name{std::move(name)} {
|
||||
const auto lambda = [this, func](std::stop_token stop_token) {
|
||||
const auto lambda = [this, func, placement](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName(thread_name.c_str());
|
||||
if (placement == ThreadPlacement::Background) {
|
||||
Common::SetCurrentThreadPriority(ThreadPriority::Low);
|
||||
Common::SetCurrentThreadToEfficiencyCores();
|
||||
}
|
||||
{
|
||||
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
||||
while (!stop_token.stop_requested()) {
|
||||
|
||||
@@ -1139,8 +1139,6 @@ add_library(core STATIC
|
||||
launch_timestamp_cache.h
|
||||
loader/deconstructed_rom_directory.cpp
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/homebrew_nxlink.cpp
|
||||
loader/homebrew_nxlink.h
|
||||
loader/kip.cpp
|
||||
loader/kip.h
|
||||
loader/loader.cpp
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/homebrew_nxlink.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
@@ -398,7 +397,6 @@ struct System::Impl {
|
||||
|
||||
stop_event.request_stop();
|
||||
core_timing.SyncPause(false);
|
||||
Loader::HomebrewNxlink::StopServer();
|
||||
Network::CancelPendingSocketOperations();
|
||||
kernel.SuspendEmulation(true);
|
||||
kernel.CloseServices();
|
||||
|
||||
@@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
||||
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
#ifdef __ANDROID__
|
||||
// Aimed specifically for Snapdragon 8 Elite devices
|
||||
// This kills performance on desktop, but boosts perf for UMA devices
|
||||
// like the S8E. Mediatek and Mali likely won't suffer.
|
||||
Common::PinCurrentThreadToPerformanceCore(core);
|
||||
#endif
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
auto& data = core_data[core];
|
||||
data.host_context = Common::Fiber::ThreadToFiber();
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
namespace FileSys {
|
||||
|
||||
enum class OpenMode : u32 {
|
||||
Default = 0,
|
||||
Read = (1 << 0),
|
||||
Write = (1 << 1),
|
||||
AllowAppend = (1 << 2),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -40,7 +40,7 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
|
||||
|
||||
VirtualFile VfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
|
||||
const auto path = Common::FS::SanitizePath(path_);
|
||||
return root->GetFileRelative(path, perms);
|
||||
return root->GetFileRelative(path);
|
||||
}
|
||||
|
||||
VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
|
||||
@@ -201,7 +201,7 @@ std::string VfsFile::GetFullPath() const {
|
||||
return GetContainingDirectory()->GetFullPath() + '/' + GetName();
|
||||
}
|
||||
|
||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
|
||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
auto vec = Common::FS::SplitPathComponents(path);
|
||||
if (vec.empty()) {
|
||||
return nullptr;
|
||||
@@ -224,10 +224,7 @@ VirtualFile VfsDirectory::GetFileRelative(std::string_view path, OpenMode perms)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (perms == OpenMode::Default) {
|
||||
return dir->GetFile(vec.back());
|
||||
}
|
||||
return dir->GetFileRelative(vec.back(), perms);
|
||||
return dir->GetFile(vec.back());
|
||||
}
|
||||
|
||||
VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
|
||||
|
||||
@@ -201,8 +201,7 @@ public:
|
||||
|
||||
// Retrieves the file located at path as if the current directory was root. Returns nullptr if
|
||||
// not found.
|
||||
virtual VirtualFile GetFileRelative(std::string_view path,
|
||||
OpenMode perms = OpenMode::Default) const;
|
||||
virtual VirtualFile GetFileRelative(std::string_view path) const;
|
||||
// Calls GetFileRelative(path) on the root of the current directory.
|
||||
virtual VirtualFile GetFileAbsolute(std::string_view path) const;
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dir
|
||||
return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
|
||||
}
|
||||
|
||||
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
|
||||
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
for (const auto& layer : dirs) {
|
||||
const auto file = layer->GetFileRelative(path, perms);
|
||||
const auto file = layer->GetFileRelative(path);
|
||||
if (file != nullptr)
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
@@ -23,8 +20,7 @@ public:
|
||||
/// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
|
||||
static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
|
||||
|
||||
VirtualFile GetFileRelative(std::string_view path,
|
||||
OpenMode perms = OpenMode::Default) const override;
|
||||
VirtualFile GetFileRelative(std::string_view path) const override;
|
||||
VirtualDir GetDirectoryRelative(std::string_view path) const override;
|
||||
VirtualFile GetFile(std::string_view file_name) const override;
|
||||
VirtualDir GetSubdirectory(std::string_view subdir_name) const override;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/vfs/vfs_real.h"
|
||||
|
||||
@@ -45,11 +46,17 @@ bool IsWithinRoot(std::string_view root, std::string_view full_path) {
|
||||
}
|
||||
|
||||
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
|
||||
if (True(mode & OpenMode::Write) || True(mode & OpenMode::AllowAppend)) {
|
||||
switch (mode) {
|
||||
case OpenMode::Read:
|
||||
return FS::FileAccessMode::Read;
|
||||
case OpenMode::Write:
|
||||
case OpenMode::ReadWrite:
|
||||
case OpenMode::AllowAppend:
|
||||
case OpenMode::All:
|
||||
return FS::FileAccessMode::ReadWrite;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
return FS::FileAccessMode::Read;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
@@ -87,11 +94,9 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
|
||||
std::optional<std::string> parent_path,
|
||||
OpenMode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
const auto open_perms = perms == OpenMode::Default ? OpenMode::Read : perms;
|
||||
std::scoped_lock lk{list_lock};
|
||||
|
||||
const CacheKey cache_key{path, open_perms};
|
||||
if (auto it = cache.find(cache_key); it != cache.end()) {
|
||||
if (auto it = cache.find(path); it != cache.end()) {
|
||||
if (auto file = it->second.lock(); file) {
|
||||
return file;
|
||||
}
|
||||
@@ -105,9 +110,8 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
|
||||
this->InsertReferenceIntoListLocked(*reference);
|
||||
|
||||
auto file = std::shared_ptr<RealVfsFile>(
|
||||
new RealVfsFile(*this, std::move(reference), path, open_perms, size,
|
||||
std::move(parent_path)));
|
||||
cache[cache_key] = file;
|
||||
new RealVfsFile(*this, std::move(reference), path, perms, size, std::move(parent_path)));
|
||||
cache[path] = file;
|
||||
|
||||
return file;
|
||||
}
|
||||
@@ -120,7 +124,7 @@ VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
CloseCachedFileReferenceLocked(path);
|
||||
cache.erase(path);
|
||||
}
|
||||
|
||||
// Current usages of CreateFile expect to delete the contents of an existing file.
|
||||
@@ -153,8 +157,8 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
|
||||
const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
CloseCachedFileReferenceLocked(old_path);
|
||||
CloseCachedFileReferenceLocked(new_path);
|
||||
cache.erase(old_path);
|
||||
cache.erase(new_path);
|
||||
}
|
||||
if (!FS::RenameFile(old_path, new_path)) {
|
||||
return nullptr;
|
||||
@@ -166,15 +170,14 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
CloseCachedFileReferenceLocked(path);
|
||||
cache.erase(path);
|
||||
}
|
||||
return FS::RemoveFile(path);
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
return std::shared_ptr<RealVfsDirectory>(
|
||||
new RealVfsDirectory(*this, path, perms == OpenMode::Default ? OpenMode::Read : perms));
|
||||
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
|
||||
@@ -219,8 +222,8 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri
|
||||
if (!reference.file) {
|
||||
this->EvictSingleReferenceLocked();
|
||||
|
||||
reference.file = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms),
|
||||
FS::FileType::BinaryFile, FS::FileShareFlag::ShareReadWriteDelete);
|
||||
reference.file =
|
||||
FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
|
||||
if (reference.file) {
|
||||
num_open_files++;
|
||||
}
|
||||
@@ -294,45 +297,15 @@ RealVfsFile::~RealVfsFile() {
|
||||
base.DropReference(std::move(reference));
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::CloseCachedFileReferenceLocked(const std::string& path) {
|
||||
for (auto it = cache.lower_bound(CacheKey{path, OpenMode::Default});
|
||||
it != cache.end() && it->first.first == path;) {
|
||||
const auto cached_file = it->second.lock();
|
||||
if (cached_file) {
|
||||
auto* const real_file = static_cast<RealVfsFile*>(cached_file.get());
|
||||
auto& reference = real_file->reference;
|
||||
if (reference && reference->file) {
|
||||
RemoveReferenceFromListLocked(*reference);
|
||||
reference->file.reset();
|
||||
num_open_files--;
|
||||
InsertReferenceIntoListLocked(*reference);
|
||||
}
|
||||
}
|
||||
it = cache.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::string RealVfsFile::GetName() const {
|
||||
#ifdef __ANDROID__
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
if (path[0] != '/') {
|
||||
return FS::Android::GetFilename(path);
|
||||
}
|
||||
#endif
|
||||
return path_components.empty() ? "" : std::string(path_components.back());
|
||||
}
|
||||
|
||||
std::string RealVfsFile::GetFullPath() const {
|
||||
#ifdef __ANDROID__
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
auto out = path;
|
||||
std::replace(out.begin(), out.end(), '\\', '/');
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
return VfsFile::GetFullPath();
|
||||
}
|
||||
|
||||
std::size_t RealVfsFile::GetSize() const {
|
||||
if (size) {
|
||||
return *size;
|
||||
@@ -438,14 +411,13 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
|
||||
|
||||
RealVfsDirectory::~RealVfsDirectory() = default;
|
||||
|
||||
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path,
|
||||
OpenMode open_perms) const {
|
||||
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
|
||||
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
||||
if (!FS::Exists(full_path) || FS::IsDir(full_path)
|
||||
|| !IsWithinRoot(FS::SanitizePath(path), full_path)) {
|
||||
return nullptr;
|
||||
}
|
||||
return base.OpenFile(full_path, open_perms == OpenMode::Default ? perms : open_perms);
|
||||
return base.OpenFile(full_path, perms);
|
||||
}
|
||||
|
||||
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include "common/intrusive_list.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
@@ -50,9 +49,8 @@ public:
|
||||
bool DeleteDirectory(std::string_view path) override;
|
||||
|
||||
private:
|
||||
using CacheKey = std::pair<std::string, OpenMode>;
|
||||
using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
|
||||
std::map<CacheKey, std::weak_ptr<VfsFile>, std::less<>> cache;
|
||||
std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
|
||||
ReferenceListType open_references;
|
||||
ReferenceListType closed_references;
|
||||
std::mutex list_lock;
|
||||
@@ -65,7 +63,6 @@ private:
|
||||
std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms,
|
||||
FileReference& reference);
|
||||
void DropReference(std::unique_ptr<FileReference>&& reference);
|
||||
void CloseCachedFileReferenceLocked(const std::string& path);
|
||||
|
||||
private:
|
||||
friend class RealVfsDirectory;
|
||||
@@ -88,7 +85,6 @@ public:
|
||||
~RealVfsFile() override;
|
||||
|
||||
std::string GetName() const override;
|
||||
std::string GetFullPath() const override;
|
||||
std::size_t GetSize() const override;
|
||||
bool Resize(std::size_t new_size) override;
|
||||
VirtualDir GetContainingDirectory() const override;
|
||||
@@ -119,8 +115,7 @@ class RealVfsDirectory : public VfsDirectory {
|
||||
public:
|
||||
~RealVfsDirectory() override;
|
||||
|
||||
VirtualFile GetFileRelative(std::string_view relative_path,
|
||||
OpenMode perms = OpenMode::Default) const override;
|
||||
VirtualFile GetFileRelative(std::string_view relative_path) const override;
|
||||
VirtualDir GetDirectoryRelative(std::string_view relative_path) const override;
|
||||
VirtualFile GetFile(std::string_view name) const override;
|
||||
VirtualDir GetSubdirectory(std::string_view name) const override;
|
||||
|
||||
@@ -670,11 +670,6 @@ public:
|
||||
size_t GetHeapRegionSize() const {
|
||||
return m_heap_region_end - m_heap_region_start;
|
||||
}
|
||||
size_t GetCurrentHeapSize() const {
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
return m_current_heap_end - m_heap_region_start;
|
||||
}
|
||||
size_t GetAliasRegionSize() const {
|
||||
return m_alias_region_end - m_alias_region_start;
|
||||
}
|
||||
|
||||
@@ -210,11 +210,6 @@ Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParamete
|
||||
m_arg_pointer = 0;
|
||||
m_arg_return_address = 0;
|
||||
m_main_thread_handle_addr = 0;
|
||||
m_process_handle_addr = 0;
|
||||
m_homebrew_next_load_path_addr = 0;
|
||||
m_homebrew_next_load_argv_addr = 0;
|
||||
m_is_homebrew_in_place_next_load = false;
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
m_code_size = params.code_num_pages * PageSize;
|
||||
m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication);
|
||||
|
||||
@@ -952,7 +947,6 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
|
||||
|
||||
stack_top = stack_bottom + stack_size;
|
||||
m_main_thread_stack_size = stack_size;
|
||||
m_main_thread_stack_top = stack_top;
|
||||
}
|
||||
|
||||
// Ensure our stack is safe to clean up on exit.
|
||||
@@ -1011,11 +1005,6 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
|
||||
if (GetInteger(m_main_thread_handle_addr) != 0) {
|
||||
this->GetMemory().Write32(m_main_thread_handle_addr, thread_handle);
|
||||
}
|
||||
if (GetInteger(m_process_handle_addr) != 0) {
|
||||
Handle process_handle;
|
||||
R_TRY(m_handle_table.Add(kernel, std::addressof(process_handle), this));
|
||||
this->GetMemory().Write32(m_process_handle_addr, process_handle);
|
||||
}
|
||||
} else {
|
||||
main_thread->GetContext().r[0] = 0;
|
||||
main_thread->GetContext().r[1] = thread_handle;
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
@@ -89,10 +87,6 @@ private:
|
||||
KProcessAddress m_arg_pointer{};
|
||||
KProcessAddress m_arg_return_address{};
|
||||
KProcessAddress m_main_thread_handle_addr{};
|
||||
KProcessAddress m_process_handle_addr{};
|
||||
KProcessAddress m_homebrew_next_load_path_addr{};
|
||||
KProcessAddress m_homebrew_next_load_argv_addr{};
|
||||
std::array<char, 16> m_homebrew_nxlink_argv_marker{};
|
||||
KHandleTable m_handle_table;
|
||||
KProcessAddress m_plr_address{};
|
||||
ThreadList m_thread_list{};
|
||||
@@ -118,7 +112,6 @@ private:
|
||||
|
||||
size_t m_code_size{};
|
||||
size_t m_main_thread_stack_size{};
|
||||
KProcessAddress m_main_thread_stack_top{};
|
||||
size_t m_max_process_memory{};
|
||||
size_t m_memory_release_hint{};
|
||||
s64 m_schedule_count{};
|
||||
@@ -146,8 +139,6 @@ private:
|
||||
bool m_is_suspended : 1 = false;
|
||||
bool m_is_immortal : 1 = false;
|
||||
bool m_is_handle_table_initialized : 1 = false;
|
||||
bool m_is_homebrew_in_place_next_load : 1 = false;
|
||||
bool m_has_homebrew_nxlink_argv_marker : 1 = false;
|
||||
|
||||
private:
|
||||
Result StartTermination(KernelCore& kernel);
|
||||
@@ -240,49 +231,6 @@ public:
|
||||
void SetMainThreadHandleAddr(KProcessAddress addr) {
|
||||
m_main_thread_handle_addr = addr;
|
||||
}
|
||||
void SetProcessHandleAddr(KProcessAddress addr) {
|
||||
m_process_handle_addr = addr;
|
||||
}
|
||||
void SetHomebrewNextLoadBufferAddrs(KProcessAddress path_addr, KProcessAddress argv_addr) {
|
||||
m_homebrew_next_load_path_addr = path_addr;
|
||||
m_homebrew_next_load_argv_addr = argv_addr;
|
||||
}
|
||||
void SetHomebrewInPlaceNextLoad(bool enabled) {
|
||||
m_is_homebrew_in_place_next_load = enabled;
|
||||
}
|
||||
void SetHomebrewNxlinkArgvMarker(std::string_view marker) {
|
||||
if (marker.size() != m_homebrew_nxlink_argv_marker.size()) {
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
return;
|
||||
}
|
||||
|
||||
marker.copy(m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size());
|
||||
m_has_homebrew_nxlink_argv_marker = true;
|
||||
}
|
||||
void ClearHomebrewNxlinkArgvMarker() {
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
}
|
||||
std::string_view GetHomebrewNxlinkArgvMarker() const {
|
||||
if (!m_has_homebrew_nxlink_argv_marker) {
|
||||
return {};
|
||||
}
|
||||
return {m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size()};
|
||||
}
|
||||
bool IsHomebrewInPlaceNextLoad() const {
|
||||
return m_is_homebrew_in_place_next_load;
|
||||
}
|
||||
KProcessAddress GetHomebrewNextLoadPathAddr() const {
|
||||
return m_homebrew_next_load_path_addr;
|
||||
}
|
||||
KProcessAddress GetHomebrewNextLoadArgvAddr() const {
|
||||
return m_homebrew_next_load_argv_addr;
|
||||
}
|
||||
size_t GetCodeSize() const {
|
||||
return m_code_size;
|
||||
}
|
||||
KProcessAddress GetMainThreadStackTop() const {
|
||||
return m_main_thread_stack_top;
|
||||
}
|
||||
|
||||
size_t GetMainStackSize() const {
|
||||
return m_main_thread_stack_size;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// SPDX-License-Identifier: GPL-2.0-or-late
|
||||
|
||||
// This file is automatically generated using svc_generator.py.
|
||||
// DO NOT MODIFY IT MANUALLY
|
||||
@@ -129,7 +128,7 @@ static void SvcWrap_QueryMemory64From32(Core::System& system, std::span<uint64_t
|
||||
}
|
||||
|
||||
static void SvcWrap_ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess64From32(system, args);
|
||||
ExitProcess64From32(system);
|
||||
}
|
||||
|
||||
static void SvcWrap_CreateThread64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
@@ -1299,7 +1298,7 @@ static void SvcWrap_QueryMemory64(Core::System& system, std::span<uint64_t, 8> a
|
||||
}
|
||||
|
||||
static void SvcWrap_ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess64(system, args);
|
||||
ExitProcess64(system);
|
||||
}
|
||||
|
||||
static void SvcWrap_CreateThread64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// SPDX-License-Identifier: GPL-2.0-or-late
|
||||
|
||||
// This file is automatically generated using svc_generator.py.
|
||||
// DO NOT MODIFY IT MANUALLY
|
||||
@@ -26,7 +25,7 @@ Result SetMemoryAttribute(Core::System& system, uint64_t address, uint64_t size,
|
||||
Result MapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result UnmapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address);
|
||||
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args);
|
||||
void ExitProcess(Core::System& system);
|
||||
Result CreateThread(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread(Core::System& system, Handle thread_handle);
|
||||
void ExitThread(Core::System& system);
|
||||
@@ -147,7 +146,7 @@ Result SetMemoryAttribute64From32(Core::System& system, uint32_t address, uint32
|
||||
Result MapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size);
|
||||
Result UnmapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size);
|
||||
Result QueryMemory64From32(Core::System& system, uint32_t out_memory_info, PageInfo* out_page_info, uint32_t address);
|
||||
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args);
|
||||
void ExitProcess64From32(Core::System& system);
|
||||
Result CreateThread64From32(Core::System& system, Handle* out_handle, uint32_t func, uint32_t arg, uint32_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread64From32(Core::System& system, Handle thread_handle);
|
||||
void ExitThread64From32(Core::System& system);
|
||||
@@ -268,7 +267,7 @@ Result SetMemoryAttribute64(Core::System& system, uint64_t address, uint64_t siz
|
||||
Result MapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result UnmapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result QueryMemory64(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address);
|
||||
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args);
|
||||
void ExitProcess64(Core::System& system);
|
||||
Result CreateThread64(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread64(Core::System& system, Handle thread_handle);
|
||||
void ExitThread64(Core::System& system);
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
@@ -25,179 +22,6 @@ constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsHomebrewInPlaceNextLoadCodeRange(const KProcess& process, u64 address, u64 size) {
|
||||
if (!process.IsHomebrewInPlaceNextLoad()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 code_start = GetInteger(process.GetEntryPoint());
|
||||
const size_t code_size = process.GetCodeSize();
|
||||
if (code_start == 0 || code_size == 0 || size > code_size || address < code_start) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return address - code_start <= code_size - size;
|
||||
}
|
||||
|
||||
struct HomebrewInPlaceMemoryBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
KMemoryState state;
|
||||
KMemoryPermission permission;
|
||||
KMemoryAttribute attribute;
|
||||
bool use_process_permission;
|
||||
};
|
||||
|
||||
Result SetHomebrewInPlaceMemoryPermissionByBlocks(KProcess& process, u64 address, u64 size,
|
||||
MemoryPermission perm, Result original_result) {
|
||||
auto& page_table = process.GetPageTable();
|
||||
const u64 end = address + size;
|
||||
const auto requested_permission = ConvertToKMemoryPermission(perm);
|
||||
std::vector<HomebrewInPlaceMemoryBlock> blocks;
|
||||
|
||||
for (u64 cursor = address; cursor < end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result = page_table.QueryInfo(std::addressof(info),
|
||||
std::addressof(page_info), cursor);
|
||||
if (query_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission query failed "
|
||||
"address=0x{:016X}, result={:#X}, original={:#X}",
|
||||
cursor, query_result.raw, original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), end);
|
||||
if (block_end <= cursor) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission walk stalled "
|
||||
"cursor=0x{:016X}, block=0x{:016X}/0x{:X}, original={:#X}",
|
||||
cursor, info.GetAddress(), info.GetSize(), original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const bool can_reprotect = True(info.GetState() & KMemoryState::FlagCanReprotect);
|
||||
const bool can_process_reprotect = True(info.GetState() & KMemoryState::FlagCode);
|
||||
if (!can_reprotect && !can_process_reprotect) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission unsupported block "
|
||||
"address=0x{:016X}, size=0x{:X}, state=0x{:08X}, svc_state={}, "
|
||||
"perm=0x{:08X}, attr=0x{:08X}, original={:#X}",
|
||||
cursor, block_end - cursor, static_cast<u32>(info.GetState()),
|
||||
static_cast<u32>(info.GetSvcState()),
|
||||
static_cast<u32>(info.GetPermission()),
|
||||
static_cast<u32>(info.GetAttribute()), original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
blocks.push_back({
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.state = info.GetState(),
|
||||
.permission = info.GetPermission(),
|
||||
.attribute = info.GetAttribute(),
|
||||
.use_process_permission = !can_reprotect && can_process_reprotect,
|
||||
});
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
if (block.permission == requested_permission) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto block_result =
|
||||
block.use_process_permission
|
||||
? page_table.SetProcessMemoryPermission(block.address, block.size, perm)
|
||||
: page_table.SetMemoryPermission(block.address, block.size, perm);
|
||||
if (block_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission block failed "
|
||||
"address=0x{:016X}, size=0x{:X}, state=0x{:08X}, perm=0x{:08X}, "
|
||||
"attr=0x{:08X}, result={:#X}, original={:#X}",
|
||||
block.address, block.size, static_cast<u32>(block.state),
|
||||
static_cast<u32>(block.permission), static_cast<u32>(block.attribute),
|
||||
block_result.raw, original_result.raw);
|
||||
R_RETURN(block_result);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
struct HomebrewInPlaceDeviceSharedBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
u16 device_use_count;
|
||||
};
|
||||
|
||||
Result UnlockHomebrewInPlaceDeviceSharedSource(KProcess& process, u64 address, u64 size,
|
||||
Result original_result) {
|
||||
auto& page_table = process.GetPageTable();
|
||||
const u64 end = address + size;
|
||||
std::vector<HomebrewInPlaceDeviceSharedBlock> blocks;
|
||||
|
||||
for (u64 cursor = address; cursor < end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result = page_table.QueryInfo(std::addressof(info),
|
||||
std::addressof(page_info), cursor);
|
||||
if (query_result.IsError()) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), end);
|
||||
if (block_end <= cursor) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const bool can_device_map = True(info.GetState() & KMemoryState::FlagCanDeviceMap);
|
||||
const bool is_clean_memory =
|
||||
can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite &&
|
||||
info.GetAttribute() == KMemoryAttribute::None && info.m_device_use_count == 0;
|
||||
const bool is_stale_device_shared =
|
||||
can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite &&
|
||||
info.GetAttribute() == KMemoryAttribute::DeviceShared && info.m_device_use_count > 0;
|
||||
if (is_clean_memory) {
|
||||
cursor = block_end;
|
||||
continue;
|
||||
}
|
||||
if (!is_stale_device_shared) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
blocks.push_back({
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.device_use_count = info.m_device_use_count,
|
||||
});
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
if (blocks.empty()) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
for (u16 unlock = 0; unlock < block.device_use_count; unlock++) {
|
||||
const auto unlock_result =
|
||||
page_table.UnlockForDeviceAddressSpace(block.address, block.size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: device-shared source unlock failed "
|
||||
"address=0x{:016X}, size=0x{:X}, remaining={}, result={:#X}, "
|
||||
"original={:#X}",
|
||||
block.address, block.size, block.device_use_count - unlock - 1,
|
||||
unlock_result.raw, original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// Checks if address + size is greater than the given address
|
||||
// This can return false if the size causes an overflow of a 64-bit type
|
||||
// or if the given size is zero.
|
||||
@@ -268,19 +92,11 @@ Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPe
|
||||
R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission);
|
||||
|
||||
// Validate that the region is in range for the current process.
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
auto& page_table = GetCurrentProcess(system.Kernel()).GetPageTable();
|
||||
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
|
||||
|
||||
// Set the memory attribute.
|
||||
const auto result = page_table.SetMemoryPermission(address, size, perm);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw &&
|
||||
IsHomebrewInPlaceNextLoadCodeRange(process, address, size)) {
|
||||
R_RETURN(
|
||||
SetHomebrewInPlaceMemoryPermissionByBlocks(process, address, size, perm, result));
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
R_RETURN(page_table.SetMemoryPermission(address, size, perm));
|
||||
}
|
||||
|
||||
Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, u32 attr) {
|
||||
@@ -319,30 +135,14 @@ Result MapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
|
||||
|
||||
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
|
||||
result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto result = page_table.MapMemory(dst_addr, src_addr, size);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) {
|
||||
if (UnlockHomebrewInPlaceDeviceSharedSource(process, src_addr, size, result).IsSuccess()) {
|
||||
const auto retry_result = page_table.MapMemory(dst_addr, src_addr, size);
|
||||
if (retry_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: svcMapMemory retry failed after "
|
||||
"DeviceShared cleanup dst=0x{:016X}, src=0x{:016X}, size=0x{:X}, "
|
||||
"result={:#X}",
|
||||
dst_addr, src_addr, size, retry_result.raw);
|
||||
}
|
||||
R_RETURN(retry_result);
|
||||
}
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
R_RETURN(page_table.MapMemory(dst_addr, src_addr, size));
|
||||
}
|
||||
|
||||
/// Unmaps a region that was previously mapped with svcMapMemory
|
||||
@@ -350,23 +150,14 @@ Result UnmapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
|
||||
|
||||
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
|
||||
result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto result = page_table.UnmapMemory(dst_addr, src_addr, size);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: svcUnmapMemory failed dst=0x{:016X}, "
|
||||
"src=0x{:016X}, size=0x{:X}, result={:#X}",
|
||||
dst_addr, src_addr, size, result.raw);
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
R_RETURN(page_table.UnmapMemory(dst_addr, src_addr, size));
|
||||
}
|
||||
|
||||
Result SetMemoryPermission64(Core::System& system, uint64_t address, uint64_t size,
|
||||
|
||||
@@ -4,216 +4,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/input.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/nvdrv/nvdrv_interface.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/resource_manager.h"
|
||||
|
||||
namespace Kernel::Svc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t HomebrewNextLoadPathSize = 0x200;
|
||||
constexpr size_t HomebrewNextLoadArgvSize = 0x800;
|
||||
|
||||
std::string ReadHomebrewString(Core::Memory::Memory& memory, KProcessAddress address,
|
||||
size_t max_size) {
|
||||
if (GetInteger(address) == 0) {
|
||||
return {};
|
||||
}
|
||||
return memory.ReadCString(Common::ProcessAddress{GetInteger(address)}, max_size);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// Exits the current process
|
||||
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
void ExitProcess(Core::System& system) {
|
||||
auto* current_process = GetCurrentProcessPointer(system.Kernel());
|
||||
auto* current_thread = GetCurrentThreadPointer(system.Kernel());
|
||||
|
||||
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessId());
|
||||
const auto next_load_path_addr = current_process->GetHomebrewNextLoadPathAddr();
|
||||
const auto next_load_argv_addr = current_process->GetHomebrewNextLoadArgvAddr();
|
||||
if (GetInteger(next_load_path_addr) != 0) {
|
||||
auto& memory = current_process->GetMemory();
|
||||
const auto next_load_path =
|
||||
ReadHomebrewString(memory, next_load_path_addr, HomebrewNextLoadPathSize);
|
||||
const auto next_load_argv =
|
||||
ReadHomebrewString(memory, next_load_argv_addr, HomebrewNextLoadArgvSize);
|
||||
if (!next_load_path.empty()) {
|
||||
auto guest_path = Common::FS::SanitizePath(next_load_path);
|
||||
constexpr std::string_view SdmcPrefix{"sdmc:"};
|
||||
FileSys::VirtualFile file{};
|
||||
|
||||
const bool is_sdmc_path = guest_path.rfind(SdmcPrefix, 0) == 0;
|
||||
const bool is_absolute_guest_path = !guest_path.empty() && guest_path.front() == '/';
|
||||
if (is_sdmc_path || is_absolute_guest_path) {
|
||||
auto relative_path =
|
||||
is_sdmc_path ? guest_path.substr(SdmcPrefix.size()) : guest_path;
|
||||
while (!relative_path.empty() && relative_path.front() == '/') {
|
||||
relative_path.erase(relative_path.begin());
|
||||
}
|
||||
|
||||
const auto host_path = Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir) /
|
||||
std::filesystem::path{Common::FS::ToU8String(relative_path)};
|
||||
const auto host_path_string = Common::FS::PathToUTF8String(host_path);
|
||||
file = Core::GetGameFileFromPath(system.GetFilesystem(), host_path_string);
|
||||
if (!file) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: failed to open guest_path='{}', host_path='{}'",
|
||||
next_load_path, host_path_string);
|
||||
}
|
||||
} else {
|
||||
file = Core::GetGameFileFromPath(system.GetFilesystem(), guest_path);
|
||||
if (!file) {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: failed to open guest_path='{}'",
|
||||
next_load_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (file) {
|
||||
const auto nvdrv =
|
||||
system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv:s");
|
||||
if (!nvdrv) {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: NVDRV service unavailable for reset");
|
||||
} else {
|
||||
nvdrv->GetModule()->ResetForProcess(current_process);
|
||||
}
|
||||
|
||||
auto& page_table = current_process->GetPageTable();
|
||||
const u64 heap_start = GetInteger(page_table.GetHeapRegionStart());
|
||||
const u64 heap_size = page_table.GetHeapRegionSize();
|
||||
const u64 heap_end = heap_start + heap_size;
|
||||
if (heap_start != 0 && heap_size != 0 && heap_end > heap_start) {
|
||||
struct DeviceSharedBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
u16 device_use_count;
|
||||
};
|
||||
|
||||
std::vector<DeviceSharedBlock> blocks;
|
||||
for (u64 cursor = heap_start; cursor < heap_end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result =
|
||||
page_table.QueryInfo(std::addressof(info), std::addressof(page_info),
|
||||
cursor);
|
||||
if (query_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup query failed "
|
||||
"address=0x{:016X}, result={:#X}",
|
||||
cursor, query_result.raw);
|
||||
break;
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), heap_end);
|
||||
if (block_end <= cursor) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup walk stalled "
|
||||
"cursor=0x{:016X}, block=0x{:016X}/0x{:X}",
|
||||
cursor, info.GetAddress(), info.GetSize());
|
||||
break;
|
||||
}
|
||||
|
||||
const bool is_device_shared =
|
||||
True(info.GetState() & KMemoryState::FlagCanDeviceMap) &&
|
||||
info.GetAttribute() == KMemoryAttribute::DeviceShared &&
|
||||
info.m_device_use_count > 0;
|
||||
if (is_device_shared) {
|
||||
blocks.push_back(DeviceSharedBlock{
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.device_use_count = info.m_device_use_count,
|
||||
});
|
||||
}
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
for (u16 unlock = 0; unlock < block.device_use_count; unlock++) {
|
||||
const auto unlock_result =
|
||||
page_table.UnlockForDeviceAddressSpace(block.address, block.size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup unlock failed "
|
||||
"address=0x{:016X}, size=0x{:X}, remaining={}, "
|
||||
"result={:#X}",
|
||||
block.address, block.size,
|
||||
block.device_use_count - unlock - 1, unlock_result.raw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Loader::LoadNroInPlace(system, *current_process, *current_thread, file,
|
||||
next_load_path, next_load_argv)) {
|
||||
const auto aruid = current_process->GetProcessId();
|
||||
if (const auto hid =
|
||||
system.ServiceManager().GetService<Service::HID::IHidServer>("hid")) {
|
||||
const auto resource_manager = hid->GetResourceManager();
|
||||
resource_manager->UnregisterAppletResourceUserId(aruid);
|
||||
const auto register_result =
|
||||
resource_manager->RegisterAppletResourceUserId(aruid, true);
|
||||
if (register_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: failed to register HID applet resource "
|
||||
"aruid={}, result={:#X}",
|
||||
aruid, register_result.raw);
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: HID service unavailable for reset");
|
||||
}
|
||||
|
||||
auto& hid_core = system.HIDCore();
|
||||
hid_core.DisableAllControllerConfiguration();
|
||||
hid_core.SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
|
||||
hid_core.ReloadInputDevices();
|
||||
|
||||
const auto activate_controller = [&](Core::HID::NpadIdType npad_id) {
|
||||
auto* controller = hid_core.GetEmulatedController(npad_id);
|
||||
if (controller == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
|
||||
Common::Input::PollingMode::Active);
|
||||
};
|
||||
|
||||
activate_controller(Core::HID::NpadIdType::Player1);
|
||||
activate_controller(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
system.Kernel().CurrentPhysicalCore().LoadContext(current_thread);
|
||||
|
||||
const auto& context = current_thread->GetContext();
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
args[i] = context.r[i];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_MSG(current_process->GetState() == KProcess::State::Running,
|
||||
"Process has already exited");
|
||||
|
||||
@@ -331,8 +132,8 @@ Result TerminateProcess(Core::System& system, Handle process_handle) {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess(system, args);
|
||||
void ExitProcess64(Core::System& system) {
|
||||
ExitProcess(system);
|
||||
}
|
||||
|
||||
Result GetProcessId64(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
|
||||
@@ -363,8 +164,8 @@ Result GetProcessInfo64(Core::System& system, int64_t* out_info, Handle process_
|
||||
R_RETURN(GetProcessInfo(system, out_info, process_handle, info_type));
|
||||
}
|
||||
|
||||
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess(system, args);
|
||||
void ExitProcess64From32(Core::System& system) {
|
||||
ExitProcess(system);
|
||||
}
|
||||
|
||||
Result GetProcessId64From32(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -258,7 +257,7 @@ Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
|
||||
npath.remove_prefix(1);
|
||||
}
|
||||
|
||||
auto file = backing->GetFileRelative(npath, mode);
|
||||
auto file = backing->GetFileRelative(npath);
|
||||
if (file == nullptr) {
|
||||
return FileSys::ResultPathNotFound;
|
||||
}
|
||||
@@ -334,16 +333,14 @@ FileSystemController::~FileSystemController() = default;
|
||||
|
||||
Result FileSystemController::RegisterProcess(
|
||||
ProcessId process_id, ProgramId program_id,
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory, std::string homebrew_initial_cwd) {
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory) {
|
||||
std::scoped_lock lk{registration_lock};
|
||||
|
||||
registrations.insert_or_assign(process_id,
|
||||
Registration{
|
||||
.program_id = program_id,
|
||||
.romfs_factory = std::move(romfs_factory),
|
||||
.save_data_factory = CreateSaveDataFactory(program_id),
|
||||
.homebrew_initial_cwd = std::move(homebrew_initial_cwd),
|
||||
});
|
||||
registrations.emplace(process_id, Registration{
|
||||
.program_id = program_id,
|
||||
.romfs_factory = std::move(romfs_factory),
|
||||
.save_data_factory = CreateSaveDataFactory(program_id),
|
||||
});
|
||||
|
||||
LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
|
||||
return ResultSuccess;
|
||||
@@ -351,8 +348,7 @@ Result FileSystemController::RegisterProcess(
|
||||
|
||||
Result FileSystemController::OpenProcess(
|
||||
ProgramId* out_program_id, std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id,
|
||||
std::string* out_homebrew_initial_cwd) {
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id) {
|
||||
std::scoped_lock lk{registration_lock};
|
||||
|
||||
const auto it = registrations.find(process_id);
|
||||
@@ -365,9 +361,6 @@ Result FileSystemController::OpenProcess(
|
||||
std::make_shared<SaveDataController>(system, it->second.save_data_factory);
|
||||
*out_romfs_controller =
|
||||
std::make_shared<RomFsController>(it->second.romfs_factory, it->second.program_id);
|
||||
if (out_homebrew_initial_cwd != nullptr) {
|
||||
*out_homebrew_initial_cwd = it->second.homebrew_initial_cwd;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
@@ -72,12 +71,11 @@ public:
|
||||
~FileSystemController();
|
||||
|
||||
Result RegisterProcess(ProcessId process_id, ProgramId program_id,
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& factory,
|
||||
std::string homebrew_initial_cwd = {});
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& factory);
|
||||
Result OpenProcess(ProgramId* out_program_id,
|
||||
std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller,
|
||||
ProcessId process_id, std::string* out_homebrew_initial_cwd = nullptr);
|
||||
std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller,
|
||||
ProcessId process_id);
|
||||
void SetPackedUpdate(ProcessId process_id, FileSys::VirtualFile update_raw);
|
||||
|
||||
std::shared_ptr<SaveDataController> OpenSaveDataController();
|
||||
@@ -138,7 +136,6 @@ private:
|
||||
ProgramId program_id;
|
||||
std::shared_ptr<FileSys::RomFSFactory> romfs_factory;
|
||||
std::shared_ptr<FileSys::SaveDataFactory> save_data_factory;
|
||||
std::string homebrew_initial_cwd;
|
||||
};
|
||||
|
||||
std::mutex registration_lock;
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/fssrv/fssrv_sf_path.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
@@ -16,29 +13,10 @@
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
static std::string ResolveHomebrewCwdRootAlias(std::string path,
|
||||
std::string_view homebrew_initial_cwd) {
|
||||
if (homebrew_initial_cwd.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const std::string normalized_path = Common::FS::SanitizePath(path);
|
||||
if (normalized_path.empty() || normalized_path == "/" ||
|
||||
normalized_path != homebrew_initial_cwd ||
|
||||
path.size() != normalized_path.size() + 2 ||
|
||||
path.substr(0, normalized_path.size()) != normalized_path ||
|
||||
path.substr(normalized_path.size()) != "//") {
|
||||
return path;
|
||||
}
|
||||
|
||||
return "/";
|
||||
}
|
||||
|
||||
IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
|
||||
std::string homebrew_initial_cwd_)
|
||||
: ServiceFramework{system_, "IFileSystem"},
|
||||
backend{std::make_unique<FileSys::Fsa::IFileSystem>(dir_)},
|
||||
size_getter{std::move(size_getter_)}, homebrew_initial_cwd{std::move(homebrew_initial_cwd_)} {
|
||||
IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_)
|
||||
: ServiceFramework{system_, "IFileSystem"}, backend{std::make_unique<FileSys::Fsa::IFileSystem>(
|
||||
dir_)},
|
||||
size_getter{std::move(size_getter_)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&IFileSystem::CreateFile>, "CreateFile"},
|
||||
{1, D<&IFileSystem::DeleteFile>, "DeleteFile"},
|
||||
@@ -65,8 +43,7 @@ Result IFileSystem::CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_H
|
||||
s32 option, s64 size) {
|
||||
LOG_DEBUG(Service_FS, "called. file={}, option={:#x}, size={:#08x}", path->str, option, size);
|
||||
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_RETURN(backend->CreateFile(FileSys::Path(fs_path.c_str()), size));
|
||||
R_RETURN(backend->CreateFile(FileSys::Path(path->str), size));
|
||||
}
|
||||
|
||||
Result IFileSystem::DeleteFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path) {
|
||||
@@ -117,8 +94,7 @@ Result IFileSystem::OpenFile(OutInterface<IFile> out_interface,
|
||||
LOG_DEBUG(Service_FS, "called. file={}, mode={}", path->str, mode);
|
||||
|
||||
FileSys::VirtualFile vfs_file{};
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(fs_path.c_str()),
|
||||
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(path->str),
|
||||
static_cast<FileSys::OpenMode>(mode)));
|
||||
|
||||
*out_interface = std::make_shared<IFile>(system, vfs_file);
|
||||
@@ -131,8 +107,7 @@ Result IFileSystem::OpenDirectory(OutInterface<IDirectory> out_interface,
|
||||
LOG_DEBUG(Service_FS, "called. directory={}, mode={}", path->str, mode);
|
||||
|
||||
FileSys::VirtualDir vfs_dir{};
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(fs_path.c_str()),
|
||||
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(path->str),
|
||||
static_cast<FileSys::OpenDirectoryMode>(mode)));
|
||||
|
||||
*out_interface = std::make_shared<IDirectory>(system, vfs_dir,
|
||||
@@ -145,8 +120,7 @@ Result IFileSystem::GetEntryType(
|
||||
LOG_DEBUG(Service_FS, "called. file={}", path->str);
|
||||
|
||||
FileSys::DirectoryEntryType vfs_entry_type{};
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(fs_path.c_str())));
|
||||
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(path->str)));
|
||||
|
||||
*out_type = static_cast<u32>(vfs_entry_type);
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/fsa/fs_i_filesystem.h"
|
||||
@@ -28,8 +23,7 @@ class IDirectory;
|
||||
|
||||
class IFileSystem final : public ServiceFramework<IFileSystem> {
|
||||
public:
|
||||
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
|
||||
std::string homebrew_initial_cwd_ = {});
|
||||
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_);
|
||||
|
||||
Result CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, s32 option,
|
||||
s64 size);
|
||||
@@ -61,7 +55,6 @@ public:
|
||||
private:
|
||||
std::unique_ptr<FileSys::Fsa::IFileSystem> backend;
|
||||
SizeGetter size_getter;
|
||||
std::string homebrew_initial_cwd;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -192,9 +192,8 @@ Result FSP_SRV::SetCurrentProcess(ClientProcessId pid) {
|
||||
|
||||
LOG_DEBUG(Service_FS, "called. current_process_id={:#016x}", current_process_id);
|
||||
|
||||
homebrew_initial_cwd.clear();
|
||||
R_RETURN(fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller,
|
||||
current_process_id, &homebrew_initial_cwd));
|
||||
R_RETURN(
|
||||
fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id));
|
||||
}
|
||||
|
||||
Result FSP_SRV::OpenFileSystemWithPatch(OutInterface<IFileSystem> out_interface,
|
||||
@@ -225,8 +224,7 @@ Result FSP_SRV::OpenSdCardFileSystem(OutInterface<IFileSystem> out_interface) {
|
||||
fsc.OpenSDMC(&sdmc_dir);
|
||||
|
||||
*out_interface = std::make_shared<IFileSystem>(
|
||||
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard),
|
||||
homebrew_initial_cwd);
|
||||
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "core/file_sys/fs_save_data_types.h"
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/filesystem/fsp/fsp_types.h"
|
||||
@@ -124,7 +123,6 @@ private:
|
||||
u32 access_log_program_index = 0;
|
||||
AccessLogMode access_log_mode = AccessLogMode::None;
|
||||
u64 program_id = 0;
|
||||
std::string homebrew_initial_cwd;
|
||||
std::shared_ptr<SaveDataController> save_data_controller;
|
||||
std::shared_ptr<RomFsController> romfs_controller;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
@@ -45,16 +40,6 @@ Container::Container(Tegra::Host1x::Host1x& host1x_) {
|
||||
|
||||
Container::~Container() = default;
|
||||
|
||||
static bool IsSameProcess(Kernel::KProcess* lhs, Kernel::KProcess* rhs) {
|
||||
if (lhs == rhs) {
|
||||
return true;
|
||||
}
|
||||
if (lhs == nullptr || rhs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return lhs->GetProcessId() == rhs->GetProcessId();
|
||||
}
|
||||
|
||||
SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
using namespace Common::Literals;
|
||||
|
||||
@@ -63,7 +48,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
if (!session.is_active) {
|
||||
continue;
|
||||
}
|
||||
if (IsSameProcess(session.process, process)) {
|
||||
if (session.process == process) {
|
||||
session.ref_count++;
|
||||
return session.id;
|
||||
}
|
||||
@@ -131,15 +116,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
|
||||
void Container::CloseSession(SessionId session_id) {
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
if (session_id.id >= impl->sessions.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& session = impl->sessions[session_id.id];
|
||||
if (!session.is_active || session.ref_count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (--session.ref_count > 0) {
|
||||
return;
|
||||
}
|
||||
@@ -157,73 +134,6 @@ void Container::CloseSession(SessionId session_id) {
|
||||
impl->id_pool.emplace_front(session_id.id);
|
||||
}
|
||||
|
||||
size_t Container::CloseSessions(std::span<const SessionId> session_ids) {
|
||||
std::vector<SessionId> valid_session_ids;
|
||||
valid_session_ids.reserve(session_ids.size());
|
||||
|
||||
{
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto session_id : session_ids) {
|
||||
if (session_id.id >= impl->sessions.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& session = impl->sessions[session_id.id];
|
||||
if (!session.is_active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto duplicate = std::ranges::any_of(
|
||||
valid_session_ids, [session_id](const auto candidate) {
|
||||
return candidate.id == session_id.id;
|
||||
});
|
||||
if (duplicate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
session.ref_count = 1;
|
||||
valid_session_ids.push_back(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto session_id : valid_session_ids) {
|
||||
CloseSession(session_id);
|
||||
}
|
||||
|
||||
return valid_session_ids.size();
|
||||
}
|
||||
|
||||
std::vector<SessionId> Container::GetSessionIdsForProcess(Kernel::KProcess* process) {
|
||||
std::vector<SessionId> session_ids;
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto& session : impl->sessions) {
|
||||
if (!session.is_active || !IsSameProcess(session.process, process)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
session_ids.push_back(session.id);
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
std::vector<SessionId> Container::GetActiveSessionIds() const {
|
||||
std::vector<SessionId> session_ids;
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto& session : impl->sessions) {
|
||||
if (session.is_active) {
|
||||
session_ids.push_back(session.id);
|
||||
}
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
bool Container::IsSessionActive(SessionId session_id) const {
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
return session_id.id < impl->sessions.size() && impl->sessions[session_id.id].is_active;
|
||||
}
|
||||
|
||||
Session* Container::GetSession(SessionId session_id) {
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
return &impl->sessions[session_id.id];
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <cstddef>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <vector>
|
||||
|
||||
#include "core/device_memory_manager.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
@@ -62,10 +59,6 @@ public:
|
||||
|
||||
SessionId OpenSession(Kernel::KProcess* process);
|
||||
void CloseSession(SessionId id);
|
||||
size_t CloseSessions(std::span<const SessionId> session_ids);
|
||||
std::vector<SessionId> GetSessionIdsForProcess(Kernel::KProcess* process);
|
||||
std::vector<SessionId> GetActiveSessionIds() const;
|
||||
bool IsSessionActive(SessionId id) const;
|
||||
|
||||
Session* GetSession(SessionId id);
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/core/heap_mapper.h"
|
||||
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||
@@ -328,65 +326,19 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
|
||||
}
|
||||
|
||||
void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
|
||||
auto* session = core.GetSession(session_id);
|
||||
auto* process = session != nullptr ? session->process : nullptr;
|
||||
auto handle_ids = [&] {
|
||||
auto handles_copy = [&] {
|
||||
std::scoped_lock lk{handles_lock};
|
||||
std::vector<Handle::Id> ids;
|
||||
ids.reserve(handles.size());
|
||||
|
||||
for (const auto& entry : handles) {
|
||||
ids.push_back(entry.first);
|
||||
}
|
||||
|
||||
return ids;
|
||||
return handles;
|
||||
}();
|
||||
|
||||
for (const auto id : handle_ids) {
|
||||
bool unlocked_pages = false;
|
||||
while (true) {
|
||||
bool last_user_reference = false;
|
||||
VAddr address = 0;
|
||||
size_t size = 0;
|
||||
{
|
||||
const auto handle = GetHandle(id);
|
||||
if (!handle) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{handle->mutex};
|
||||
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
last_user_reference = handle->dupes == 1;
|
||||
address = handle->address;
|
||||
size = handle->size;
|
||||
}
|
||||
|
||||
const auto free_info = FreeHandle(id, false);
|
||||
if (!free_info) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!unlocked_pages && process != nullptr && address != 0 && size != 0 &&
|
||||
(free_info->can_unlock || last_user_reference)) {
|
||||
const auto unlock_result =
|
||||
process->GetPageTable().UnlockForDeviceAddressSpace(address, size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"NextLoad: nvmap session cleanup unlock failed, "
|
||||
"handle={}, session={}, address=0x{:016X}, size=0x{:X}, "
|
||||
"result={:#X}",
|
||||
id, session_id.id, address, size, unlock_result.raw);
|
||||
}
|
||||
unlocked_pages = true;
|
||||
}
|
||||
|
||||
if (last_user_reference) {
|
||||
break;
|
||||
for (auto& [id, handle] : handles_copy) {
|
||||
{
|
||||
std::scoped_lock lk{handle->mutex};
|
||||
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
FreeHandle(id, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||
@@ -138,9 +133,6 @@ DeviceFD Module::Open(const std::string& device_name, NvCore::SessionId session_
|
||||
auto device = builder(fd)->second;
|
||||
|
||||
device->OnOpen(session_id, fd);
|
||||
if (container.IsSessionActive(session_id)) {
|
||||
open_file_sessions.emplace(fd, session_id);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
@@ -212,7 +204,6 @@ NvResult Module::Close(DeviceFD fd) {
|
||||
itr->second->OnClose(fd);
|
||||
|
||||
open_files.erase(itr);
|
||||
open_file_sessions.erase(fd);
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
@@ -237,98 +228,4 @@ NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
static bool ContainsSession(std::span<const NvCore::SessionId> session_ids,
|
||||
NvCore::SessionId session_id) {
|
||||
return std::ranges::any_of(session_ids, [session_id](const auto candidate) {
|
||||
return candidate.id == session_id.id;
|
||||
});
|
||||
}
|
||||
|
||||
static void AppendUniqueSession(std::vector<NvCore::SessionId>& session_ids,
|
||||
NvCore::SessionId session_id) {
|
||||
if (!ContainsSession(session_ids, session_id)) {
|
||||
session_ids.push_back(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Module::CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids) {
|
||||
std::vector<DeviceFD> fds;
|
||||
fds.reserve(open_file_sessions.size());
|
||||
|
||||
for (const auto& [fd, session_id] : open_file_sessions) {
|
||||
if (ContainsSession(session_ids, session_id)) {
|
||||
fds.push_back(fd);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto fd : fds) {
|
||||
Close(fd);
|
||||
}
|
||||
|
||||
return fds.size();
|
||||
}
|
||||
|
||||
void Module::CloseSession(NvCore::SessionId session_id) {
|
||||
container.CloseSession(session_id);
|
||||
}
|
||||
|
||||
void Module::TrackSessionAruid(NvCore::SessionId session_id, u64 aruid) {
|
||||
const bool active = container.IsSessionActive(session_id);
|
||||
if (active) {
|
||||
session_aruids[session_id.id] = aruid;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NvCore::SessionId> Module::GetSessionIdsForAruid(u64 aruid) const {
|
||||
std::vector<NvCore::SessionId> session_ids;
|
||||
for (const auto& [session_id, session_aruid] : session_aruids) {
|
||||
if (session_aruid == aruid) {
|
||||
session_ids.push_back(NvCore::SessionId{session_id});
|
||||
}
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
size_t Module::ResetForProcess(Kernel::KProcess* process) {
|
||||
const auto process_id = process != nullptr ? process->GetProcessId() : 0;
|
||||
auto session_ids = container.GetSessionIdsForProcess(process);
|
||||
|
||||
if (process_id != 0) {
|
||||
for (const auto session_id : GetSessionIdsForAruid(process_id)) {
|
||||
AppendUniqueSession(session_ids, session_id);
|
||||
}
|
||||
}
|
||||
|
||||
const auto active_session_ids = container.GetActiveSessionIds();
|
||||
const auto active_before = active_session_ids.size();
|
||||
const bool has_active_candidate =
|
||||
std::ranges::any_of(session_ids, [this](const auto session_id) {
|
||||
return container.IsSessionActive(session_id);
|
||||
});
|
||||
bool used_active_sessions = false;
|
||||
if (!has_active_candidate && !active_session_ids.empty()) {
|
||||
for (const auto session_id : active_session_ids) {
|
||||
AppendUniqueSession(session_ids, session_id);
|
||||
}
|
||||
used_active_sessions = true;
|
||||
}
|
||||
|
||||
const auto closed_files = CloseFilesForSessions(session_ids);
|
||||
const auto closed_sessions = container.CloseSessions(session_ids);
|
||||
for (const auto session_id : session_ids) {
|
||||
if (!container.IsSessionActive(session_id)) {
|
||||
session_aruids.erase(session_id.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (used_active_sessions) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"NextLoad: NVDRV reset used active sessions because process-owned "
|
||||
"sessions were not found, process_id={}, sessions={}, files={}, active_before={}",
|
||||
process_id, closed_sessions, closed_files, active_before);
|
||||
}
|
||||
return closed_sessions;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
@@ -27,7 +26,6 @@ class System;
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KProcess;
|
||||
}
|
||||
|
||||
namespace Service::Nvidia {
|
||||
@@ -91,9 +89,6 @@ public:
|
||||
NvResult Close(DeviceFD fd);
|
||||
|
||||
NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);
|
||||
void CloseSession(NvCore::SessionId session_id);
|
||||
void TrackSessionAruid(NvCore::SessionId session_id, u64 aruid);
|
||||
size_t ResetForProcess(Kernel::KProcess* process);
|
||||
|
||||
NvCore::Container& GetContainer() {
|
||||
return container;
|
||||
@@ -111,17 +106,12 @@ private:
|
||||
using FilesContainerType = ankerl::unordered_dense::map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
|
||||
/// Mapping of file descriptors to the devices they reference.
|
||||
FilesContainerType open_files;
|
||||
ankerl::unordered_dense::map<DeviceFD, NvCore::SessionId> open_file_sessions;
|
||||
ankerl::unordered_dense::map<size_t, u64> session_aruids;
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
EventInterface events_interface;
|
||||
|
||||
ankerl::unordered_dense::map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
|
||||
|
||||
size_t CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids);
|
||||
std::vector<NvCore::SessionId> GetSessionIdsForAruid(u64 aruid) const;
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system);
|
||||
|
||||
@@ -212,10 +212,7 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) {
|
||||
void NVDRV::SetAruid(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
pid = rp.Pop<u64>();
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#X}", pid);
|
||||
if (is_initialized) {
|
||||
nvdrv->TrackSessionAruid(session_id, pid);
|
||||
}
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#x}", pid);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
@@ -40,7 +40,6 @@ namespace Network {
|
||||
namespace {
|
||||
|
||||
enum class CallType {
|
||||
Connect,
|
||||
Send,
|
||||
Other,
|
||||
};
|
||||
@@ -132,7 +131,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
case WSAENOTCONN:
|
||||
return Errno::NOTCONN;
|
||||
case WSAEWOULDBLOCK:
|
||||
return call_type == CallType::Connect ? Errno::INPROGRESS : Errno::AGAIN;
|
||||
return Errno::AGAIN;
|
||||
case WSAECONNREFUSED:
|
||||
return Errno::CONNREFUSED;
|
||||
case WSAECONNABORTED:
|
||||
@@ -564,7 +563,6 @@ int TranslateTypeToNative(Type type) {
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(UDPLITE)
|
||||
#elif defined(_WIN32)
|
||||
#define NETWORK_PROTOCOL_TRANSLATE_LIST \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \
|
||||
/*NETWORK_PROTOCOL_TRANSLATE_ELEM(HOPOPTS)*/ \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \
|
||||
@@ -890,7 +888,7 @@ Errno Socket::Connect(SockAddrIn addr_in) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
return GetAndLogLastError(CallType::Connect);
|
||||
return GetAndLogLastError();
|
||||
}
|
||||
|
||||
std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/loader/homebrew_nxlink.h"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stop_token>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Loader::HomebrewNxlink {
|
||||
namespace {
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
constexpr u16 NxlinkClientPort = 28771;
|
||||
constexpr size_t ReceiveBufferSize = 1024;
|
||||
constexpr size_t MaxLogLineSize = 16 * 1024;
|
||||
|
||||
struct LogConnection {
|
||||
explicit LogConnection(boost::asio::io_context& io_context_) : socket{io_context_} {}
|
||||
|
||||
tcp::socket socket;
|
||||
std::array<char, ReceiveBufferSize> buffer{};
|
||||
std::string line;
|
||||
};
|
||||
|
||||
struct LogServerState {
|
||||
LogServerState() : acceptor{io_context} {}
|
||||
|
||||
boost::asio::io_context io_context;
|
||||
tcp::acceptor acceptor;
|
||||
};
|
||||
|
||||
std::mutex server_mutex;
|
||||
std::shared_ptr<LogServerState> server_state;
|
||||
std::optional<std::jthread> server_thread;
|
||||
|
||||
std::optional<std::string> GetLastArgvToken(std::string_view argv_string) {
|
||||
while (!argv_string.empty() && argv_string.back() == '\0') {
|
||||
argv_string.remove_suffix(1);
|
||||
}
|
||||
|
||||
std::optional<std::string_view> last_token;
|
||||
bool in_token = false;
|
||||
bool quoted = false;
|
||||
size_t token_begin = 0;
|
||||
size_t token_size = 0;
|
||||
|
||||
for (size_t i = 0; i <= argv_string.size(); i++) {
|
||||
const char c = i < argv_string.size() ? argv_string[i] : '\0';
|
||||
|
||||
if (!in_token) {
|
||||
if (c == '\0' || std::isspace(static_cast<unsigned char>(c))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
in_token = true;
|
||||
token_size = 0;
|
||||
if (c == '"') {
|
||||
quoted = true;
|
||||
token_begin = i + 1;
|
||||
} else {
|
||||
quoted = false;
|
||||
token_begin = i;
|
||||
token_size = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool token_end =
|
||||
quoted ? c == '"' || c == '\0'
|
||||
: c == '\0' || std::isspace(static_cast<unsigned char>(c));
|
||||
if (token_end) {
|
||||
if (token_size != 0) {
|
||||
last_token = argv_string.substr(token_begin, token_size);
|
||||
}
|
||||
in_token = false;
|
||||
quoted = false;
|
||||
token_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
token_size++;
|
||||
}
|
||||
|
||||
if (!last_token) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::string{*last_token};
|
||||
}
|
||||
|
||||
void AppendArgvToken(std::string& argv_string, std::string_view token) {
|
||||
while (!argv_string.empty() && argv_string.back() == '\0') {
|
||||
argv_string.pop_back();
|
||||
}
|
||||
if (!argv_string.empty()) {
|
||||
argv_string.push_back(' ');
|
||||
}
|
||||
argv_string.append(token);
|
||||
}
|
||||
|
||||
void FlushLogLine(std::string& line) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
if (!line.empty()) {
|
||||
LOG_INFO(Loader, "{}", line);
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ConsumeLogBytes(LogConnection& connection, std::string_view bytes) {
|
||||
for (const char byte : bytes) {
|
||||
if (byte == '\n') {
|
||||
FlushLogLine(connection.line);
|
||||
continue;
|
||||
}
|
||||
|
||||
connection.line.push_back(byte);
|
||||
if (connection.line.size() >= MaxLogLineSize) {
|
||||
FlushLogLine(connection.line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StartRead(std::shared_ptr<LogConnection> connection) {
|
||||
connection->socket.async_read_some(
|
||||
boost::asio::buffer(connection->buffer),
|
||||
[connection](const boost::system::error_code& error, size_t bytes_read) {
|
||||
if (error.failed()) {
|
||||
FlushLogLine(connection->line);
|
||||
return;
|
||||
}
|
||||
|
||||
ConsumeLogBytes(*connection, {connection->buffer.data(), bytes_read});
|
||||
StartRead(connection);
|
||||
});
|
||||
}
|
||||
|
||||
void StartAccept(std::shared_ptr<LogServerState> state) {
|
||||
auto connection = std::make_shared<LogConnection>(state->io_context);
|
||||
state->acceptor.async_accept(
|
||||
connection->socket,
|
||||
[state, connection](const boost::system::error_code& error) {
|
||||
if (!error.failed()) {
|
||||
LOG_INFO(Loader, "Homebrew nxlink log client connected");
|
||||
StartRead(connection);
|
||||
}
|
||||
if (state->acceptor.is_open()) {
|
||||
StartAccept(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StartLogServer() {
|
||||
std::scoped_lock lock{server_mutex};
|
||||
if (server_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto state = std::make_shared<LogServerState>();
|
||||
const tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), NxlinkClientPort};
|
||||
|
||||
boost::system::error_code error;
|
||||
state->acceptor.open(endpoint.protocol(), error);
|
||||
if (!error.failed()) {
|
||||
state->acceptor.set_option(tcp::acceptor::reuse_address(true), error);
|
||||
}
|
||||
if (!error.failed()) {
|
||||
state->acceptor.bind(endpoint, error);
|
||||
}
|
||||
if (!error.failed()) {
|
||||
state->acceptor.listen(boost::asio::socket_base::max_listen_connections, error);
|
||||
}
|
||||
if (error.failed()) {
|
||||
LOG_WARNING(Loader, "Homebrew nxlink log server could not listen on 127.0.0.1:{}: {}",
|
||||
NxlinkClientPort, error.message());
|
||||
return;
|
||||
}
|
||||
|
||||
StartAccept(state);
|
||||
server_state = state;
|
||||
server_thread.emplace([state](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("HomebrewNxlink");
|
||||
std::stop_callback stop_callback{stop_token, [state] {
|
||||
boost::system::error_code ignored;
|
||||
state->acceptor.close(ignored);
|
||||
state->io_context.stop();
|
||||
}};
|
||||
|
||||
LOG_INFO(Loader, "Homebrew nxlink log server listening on 127.0.0.1:{}",
|
||||
NxlinkClientPort);
|
||||
state->io_context.run();
|
||||
LOG_INFO(Loader, "Homebrew nxlink log server stopped");
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsArgvMarker(std::string_view token) {
|
||||
if (token.size() != ArgvMarkerSize || token.substr(8) != ArgvMarkerSuffix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
if (!std::isxdigit(static_cast<unsigned char>(token[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsLoopbackArgvMarker(std::string_view token) {
|
||||
if (!IsArgvMarker(token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
if (std::tolower(static_cast<unsigned char>(token[i])) !=
|
||||
std::tolower(static_cast<unsigned char>(LoopbackArgvMarker[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> GetArgvMarker(std::string_view argv_string) {
|
||||
auto last_token = GetLastArgvToken(argv_string);
|
||||
if (!last_token || !IsArgvMarker(*last_token)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return last_token;
|
||||
}
|
||||
|
||||
std::optional<std::string> PrepareArgv(std::string& argv_string,
|
||||
std::string_view inherited_marker,
|
||||
bool append_loopback_marker) {
|
||||
auto marker = GetArgvMarker(argv_string);
|
||||
if (!marker && IsArgvMarker(inherited_marker)) {
|
||||
AppendArgvToken(argv_string, inherited_marker);
|
||||
marker = std::string{inherited_marker};
|
||||
}
|
||||
if (!marker && append_loopback_marker) {
|
||||
AppendArgvToken(argv_string, LoopbackArgvMarker);
|
||||
marker = std::string{LoopbackArgvMarker};
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
void ApplyServerMode(Settings::HomebrewNxlinkServerMode mode,
|
||||
const std::optional<std::string>& active_marker) {
|
||||
switch (mode) {
|
||||
case Settings::HomebrewNxlinkServerMode::Disabled:
|
||||
StopServer();
|
||||
return;
|
||||
case Settings::HomebrewNxlinkServerMode::EdenLog:
|
||||
if (active_marker && IsLoopbackArgvMarker(*active_marker)) {
|
||||
StartLogServer();
|
||||
return;
|
||||
}
|
||||
StopServer();
|
||||
if (active_marker) {
|
||||
LOG_INFO(Loader,
|
||||
"Homebrew nxlink server mode overridden by argv marker '{}'; not starting "
|
||||
"local nxlink log server",
|
||||
*active_marker);
|
||||
} else {
|
||||
LOG_WARNING(Loader,
|
||||
"Homebrew nxlink log server requested, but no argv marker is active");
|
||||
}
|
||||
return;
|
||||
case Settings::HomebrewNxlinkServerMode::HostStdout:
|
||||
case Settings::HomebrewNxlinkServerMode::File:
|
||||
StopServer();
|
||||
LOG_WARNING(Loader, "Homebrew nxlink server mode {} is not implemented",
|
||||
static_cast<int>(mode));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void StopServer() {
|
||||
std::shared_ptr<LogServerState> state;
|
||||
std::optional<std::jthread> thread;
|
||||
{
|
||||
std::scoped_lock lock{server_mutex};
|
||||
state = std::move(server_state);
|
||||
thread = std::move(server_thread);
|
||||
}
|
||||
|
||||
if (state) {
|
||||
boost::system::error_code ignored;
|
||||
state->acceptor.close(ignored);
|
||||
state->io_context.stop();
|
||||
}
|
||||
if (thread) {
|
||||
thread->request_stop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Loader::HomebrewNxlink
|
||||
@@ -1,30 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "common/settings_enums.h"
|
||||
|
||||
namespace Loader::HomebrewNxlink {
|
||||
|
||||
constexpr size_t ArgvMarkerSize = 16;
|
||||
constexpr std::string_view ArgvMarkerSuffix = "_NXLINK_";
|
||||
constexpr std::string_view LoopbackArgvMarker = "0100007F_NXLINK_";
|
||||
|
||||
bool IsArgvMarker(std::string_view token);
|
||||
bool IsLoopbackArgvMarker(std::string_view token);
|
||||
std::optional<std::string> GetArgvMarker(std::string_view argv_string);
|
||||
std::optional<std::string> PrepareArgv(std::string& argv_string,
|
||||
std::string_view inherited_marker,
|
||||
bool append_loopback_marker = false);
|
||||
|
||||
void ApplyServerMode(Settings::HomebrewNxlinkServerMode mode,
|
||||
const std::optional<std::string>& active_marker);
|
||||
void StopServer();
|
||||
|
||||
} // namespace Loader::HomebrewNxlink
|
||||
+51
-503
@@ -4,20 +4,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/random.h"
|
||||
@@ -26,14 +23,11 @@
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
#include "core/hardware_properties.h"
|
||||
#include "core/hle/api_version.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/k_page_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/homebrew_nxlink.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -158,432 +152,8 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
|
||||
}
|
||||
|
||||
static std::string MakeHomebrewSdmcPath(std::string path) {
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
while (!path.empty() && path.front() == '/') {
|
||||
path.erase(path.begin());
|
||||
}
|
||||
return "sdmc:/" + path;
|
||||
}
|
||||
|
||||
static std::optional<std::string> TryMakeHomebrewSdmcPathFromHostPath(std::string path) {
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
|
||||
std::string sdmc_root =
|
||||
Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir));
|
||||
std::replace(sdmc_root.begin(), sdmc_root.end(), '\\', '/');
|
||||
while (!sdmc_root.empty() && sdmc_root.back() == '/') {
|
||||
sdmc_root.pop_back();
|
||||
}
|
||||
|
||||
if (!sdmc_root.empty() && path.size() > sdmc_root.size() &&
|
||||
path.compare(0, sdmc_root.size(), sdmc_root) == 0 && path[sdmc_root.size()] == '/') {
|
||||
return MakeHomebrewSdmcPath(path.substr(sdmc_root.size() + 1));
|
||||
}
|
||||
|
||||
constexpr std::string_view SdmcMarker{"/sdmc/"};
|
||||
if (const auto pos = path.rfind(SdmcMarker); pos != std::string::npos) {
|
||||
return MakeHomebrewSdmcPath(path.substr(pos + SdmcMarker.size()));
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::string MakeHomebrewArgv0(std::string nro_path, std::string file_name) {
|
||||
if (nro_path.empty()) {
|
||||
nro_path = std::move(file_name);
|
||||
}
|
||||
|
||||
std::replace(nro_path.begin(), nro_path.end(), '\\', '/');
|
||||
|
||||
if (nro_path.rfind("sdmc:/", 0) == 0) {
|
||||
return nro_path;
|
||||
}
|
||||
|
||||
if (auto sdmc_path = TryMakeHomebrewSdmcPathFromHostPath(nro_path)) {
|
||||
return *sdmc_path;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (nro_path.find('%') != std::string::npos) {
|
||||
const auto percent_decode = [](std::string value) {
|
||||
const auto hex_value = [](char c) -> int {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
std::string decoded;
|
||||
decoded.reserve(value.size());
|
||||
for (size_t i = 0; i < value.size(); i++) {
|
||||
if (value[i] == '%' && i + 2 < value.size()) {
|
||||
const int high = hex_value(value[i + 1]);
|
||||
const int low = hex_value(value[i + 2]);
|
||||
if (high >= 0 && low >= 0) {
|
||||
decoded.push_back(static_cast<char>((high << 4) | low));
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
decoded.push_back(value[i]);
|
||||
}
|
||||
return decoded;
|
||||
};
|
||||
|
||||
if (auto sdmc_path = TryMakeHomebrewSdmcPathFromHostPath(percent_decode(nro_path))) {
|
||||
return *sdmc_path;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!nro_path.empty() && nro_path.front() == '/') {
|
||||
return "sdmc:" + nro_path;
|
||||
}
|
||||
|
||||
return nro_path.empty() ? "homebrew" : nro_path;
|
||||
}
|
||||
|
||||
static std::string QuoteHomebrewArgvComponent(const std::string& argument) {
|
||||
if (argument.find_first_of(" \t\r\n") == std::string::npos) {
|
||||
return argument;
|
||||
}
|
||||
|
||||
std::string quoted{"\""};
|
||||
quoted += argument;
|
||||
quoted.push_back('"');
|
||||
return quoted;
|
||||
}
|
||||
|
||||
static std::string GetHomebrewInitialCwd(const std::string& argv0) {
|
||||
constexpr std::string_view SdmcPrefix = "sdmc:";
|
||||
if (argv0.substr(0, SdmcPrefix.size()) != SdmcPrefix) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto last_slash = argv0.find_last_of('/');
|
||||
if (last_slash == std::string_view::npos || last_slash < SdmcPrefix.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string cwd = argv0.substr(SdmcPrefix.size(), last_slash - SdmcPrefix.size());
|
||||
if (cwd.empty()) {
|
||||
cwd = "/";
|
||||
}
|
||||
while (cwd.size() > 1 && cwd.back() == '/') {
|
||||
cwd.pop_back();
|
||||
}
|
||||
return cwd;
|
||||
}
|
||||
|
||||
constexpr size_t HomebrewNextLoadPathSize = 0x200;
|
||||
constexpr size_t HomebrewNextLoadArgvSize = 0x800;
|
||||
constexpr u32 HomebrewSvcExitProcessInstruction = 0xD40000E1;
|
||||
constexpr u32 HomebrewEntryEndOfList = 0;
|
||||
constexpr u32 HomebrewEntryMainThreadHandle = 1;
|
||||
constexpr u32 HomebrewEntryNextLoadPath = 2;
|
||||
constexpr u32 HomebrewEntryOverrideHeap = 3;
|
||||
constexpr u32 HomebrewEntryArgv = 5;
|
||||
constexpr u32 HomebrewEntrySyscallAvailableHint = 6;
|
||||
constexpr u32 HomebrewEntryAppletType = 7;
|
||||
constexpr u32 HomebrewEntryProcessHandle = 10;
|
||||
constexpr u32 HomebrewEntryRandomSeed = 14;
|
||||
constexpr u32 HomebrewEntryHosVersion = 16;
|
||||
constexpr u32 HomebrewEntrySyscallAvailableHint2 = 17;
|
||||
constexpr u32 HomebrewAppletTypeApplication = 0;
|
||||
constexpr u64 HomebrewAllSvcHints = ~u64{0};
|
||||
constexpr u32 HomebrewHosVersion = (u32{HLE::ApiVersion::HOS_VERSION_MAJOR} << 16) |
|
||||
(u32{HLE::ApiVersion::HOS_VERSION_MINOR} << 8) |
|
||||
u32{HLE::ApiVersion::HOS_VERSION_MICRO};
|
||||
|
||||
struct HomebrewConfigEntry {
|
||||
u32_le key;
|
||||
u32_le flags;
|
||||
u64_le value[2];
|
||||
};
|
||||
static_assert(sizeof(HomebrewConfigEntry) == 0x18);
|
||||
constexpr size_t HomebrewBaseConfigEntryCount = 10;
|
||||
constexpr size_t HomebrewInPlaceConfigEntryCount = 11;
|
||||
constexpr size_t HomebrewBaseConfigTableSize =
|
||||
HomebrewBaseConfigEntryCount * sizeof(HomebrewConfigEntry);
|
||||
constexpr size_t HomebrewInPlaceConfigTableSize =
|
||||
HomebrewInPlaceConfigEntryCount * sizeof(HomebrewConfigEntry);
|
||||
|
||||
struct HomebrewNroImage {
|
||||
Kernel::CodeSet codeset;
|
||||
size_t image_size{};
|
||||
size_t args_offset{};
|
||||
std::optional<size_t> exit_process_offset;
|
||||
std::optional<std::string> nxlink_argv_marker;
|
||||
std::string argv_string;
|
||||
};
|
||||
|
||||
static void SetHomebrewConfigPointers(Kernel::KProcess& process, u64 config_addr,
|
||||
u64 next_load_path_addr, u64 next_load_argv_addr) {
|
||||
constexpr size_t MainThreadHandleEntryIndex = 0;
|
||||
constexpr size_t ProcessHandleEntryIndex = 1;
|
||||
constexpr size_t EntryValueOffset = offsetof(HomebrewConfigEntry, value);
|
||||
|
||||
process.SetArgPointer(Kernel::KProcessAddress{config_addr});
|
||||
process.SetMainThreadHandleAddr(Kernel::KProcessAddress{
|
||||
config_addr + MainThreadHandleEntryIndex * sizeof(HomebrewConfigEntry) + EntryValueOffset});
|
||||
process.SetProcessHandleAddr(Kernel::KProcessAddress{
|
||||
config_addr + ProcessHandleEntryIndex * sizeof(HomebrewConfigEntry) + EntryValueOffset});
|
||||
process.SetHomebrewNextLoadBufferAddrs(Kernel::KProcessAddress{next_load_path_addr},
|
||||
Kernel::KProcessAddress{next_load_argv_addr});
|
||||
}
|
||||
|
||||
static std::optional<HomebrewNroImage> BuildHomebrewNroImage(const std::vector<u8>& data,
|
||||
std::string nro_path,
|
||||
std::string file_name,
|
||||
std::string launch_argv,
|
||||
std::string_view inherited_marker,
|
||||
bool append_loopback_nxlink_marker) {
|
||||
if (data.size() < sizeof(NroHeader)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
NroHeader nro_header{};
|
||||
std::memcpy(&nro_header, data.data(), sizeof(NroHeader));
|
||||
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (data.size() < nro_header.file_size ||
|
||||
nro_header.module_header_offset + sizeof(ModHeader) > PageAlignSize(nro_header.file_size)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
|
||||
std::memcpy(program_image.data(), data.data(), nro_header.file_size);
|
||||
|
||||
Kernel::CodeSet codeset;
|
||||
for (std::size_t i = 0; i < nro_header.segments.size(); ++i) {
|
||||
codeset.segments[i].addr = nro_header.segments[i].offset;
|
||||
codeset.segments[i].offset = nro_header.segments[i].offset;
|
||||
codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size);
|
||||
}
|
||||
|
||||
u32 bss_size{PageAlignSize(nro_header.bss_size)};
|
||||
ModHeader mod_header{};
|
||||
std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset,
|
||||
sizeof(ModHeader));
|
||||
|
||||
if (mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')) {
|
||||
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
|
||||
}
|
||||
|
||||
codeset.DataSegment().size += bss_size;
|
||||
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
|
||||
|
||||
HomebrewNroImage image{.codeset = std::move(codeset)};
|
||||
const auto argv0 = MakeHomebrewArgv0(std::move(nro_path), std::move(file_name));
|
||||
|
||||
if (!launch_argv.empty()) {
|
||||
image.argv_string = std::move(launch_argv);
|
||||
} else {
|
||||
image.argv_string = QuoteHomebrewArgvComponent(argv0);
|
||||
}
|
||||
image.nxlink_argv_marker = HomebrewNxlink::PrepareArgv(
|
||||
image.argv_string, inherited_marker, append_loopback_nxlink_marker);
|
||||
if (image.argv_string.empty() || image.argv_string.back() != '\0') {
|
||||
image.argv_string.push_back('\0');
|
||||
}
|
||||
|
||||
const auto& code = image.codeset.CodeSegment();
|
||||
const size_t code_end = (std::min)(program_image.size(), code.offset + code.size);
|
||||
for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) {
|
||||
u32 instruction{};
|
||||
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
|
||||
if (instruction == HomebrewSvcExitProcessInstruction) {
|
||||
image.exit_process_offset = offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t entries_and_buffers =
|
||||
Common::AlignUp(HomebrewInPlaceConfigTableSize + HomebrewNextLoadPathSize +
|
||||
HomebrewNextLoadArgvSize + image.argv_string.size(),
|
||||
Core::Memory::YUZU_PAGESIZE);
|
||||
|
||||
image.args_offset = program_image.size();
|
||||
image.codeset.DataSegment().size += static_cast<u32>(entries_and_buffers);
|
||||
program_image.resize(image.args_offset + entries_and_buffers);
|
||||
image.image_size = program_image.size();
|
||||
image.codeset.memory = std::move(program_image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, Kernel::KThread& thread,
|
||||
const FileSys::VirtualFile& nro_file, const std::string& nro_path,
|
||||
const std::string& launch_argv) {
|
||||
if (!nro_file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAS_NCE
|
||||
if (Settings::IsNceEnabled()) {
|
||||
LOG_WARNING(Loader,
|
||||
"Homebrew next-load: in-place handoff unavailable because NCE is enabled");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t live_threads = 0;
|
||||
for (auto& candidate : process.GetThreadList()) {
|
||||
if (candidate.GetState() != Kernel::ThreadState::Terminated) {
|
||||
live_threads++;
|
||||
}
|
||||
}
|
||||
if (live_threads != 1) {
|
||||
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because live_threads={}",
|
||||
live_threads);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto stack_top = process.GetMainThreadStackTop();
|
||||
if (GetInteger(stack_top) == 0) {
|
||||
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because stack_top=0");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto nxlink_server_mode = Settings::values.homebrew_nxlink_server_mode.GetValue();
|
||||
const bool append_loopback_nxlink_marker =
|
||||
nxlink_server_mode != Settings::HomebrewNxlinkServerMode::Disabled;
|
||||
auto image = BuildHomebrewNroImage(
|
||||
nro_file->ReadAllBytes(), nro_path, nro_file->GetName(), launch_argv,
|
||||
process.GetHomebrewNxlinkArgvMarker(), append_loopback_nxlink_marker);
|
||||
if (!image) {
|
||||
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because '{}' is invalid",
|
||||
nro_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto capacity = process.GetCodeSize();
|
||||
if (image->image_size > capacity) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because image_size=0x{:X} exceeds "
|
||||
"capacity=0x{:X}",
|
||||
image->image_size, capacity);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 base = GetInteger(process.GetEntryPoint());
|
||||
const u64 heap_addr = GetInteger(process.GetPageTable().GetHeapRegionStart());
|
||||
const size_t heap_size = process.GetPageTable().GetBasePageTable().GetCurrentHeapSize();
|
||||
if (heap_addr == 0 || heap_size == 0) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because heap override is "
|
||||
"unavailable (addr=0x{:016X}, size=0x{:X})",
|
||||
heap_addr, heap_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 config_addr = base + image->args_offset;
|
||||
const u64 next_load_path_addr = config_addr + HomebrewInPlaceConfigTableSize;
|
||||
const u64 next_load_argv_addr = next_load_path_addr + HomebrewNextLoadPathSize;
|
||||
const u64 argv_addr = next_load_argv_addr + HomebrewNextLoadArgvSize;
|
||||
const u64 argv_entry_addr = image->argv_string.empty() ? 0 : argv_addr;
|
||||
const std::string argv0 = MakeHomebrewArgv0(nro_path, nro_file->GetName());
|
||||
const std::string homebrew_initial_cwd = GetHomebrewInitialCwd(argv0);
|
||||
u64 program_id{};
|
||||
AppLoader_NRO loader{nro_file};
|
||||
if (loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) {
|
||||
LOG_WARNING(Loader, "NextLoad: in-place handoff could not read NRO program id");
|
||||
}
|
||||
|
||||
Kernel::Handle main_thread_handle{};
|
||||
if (process.GetHandleTable().Add(system.Kernel(), std::addressof(main_thread_handle), &thread)
|
||||
.IsError()) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because main thread handle failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
Kernel::Handle process_handle{};
|
||||
if (process.GetHandleTable().Add(system.Kernel(), std::addressof(process_handle), &process)
|
||||
.IsError()) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because process handle failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!process.GetMemory().ZeroBlock(Common::ProcessAddress{heap_addr}, heap_size)) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because heap clear failed "
|
||||
"(addr=0x{:016X}, size=0x{:X})",
|
||||
heap_addr, heap_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
process.LoadModule(system.Kernel(), std::move(image->codeset), process.GetEntryPoint());
|
||||
|
||||
const HomebrewConfigEntry entries[HomebrewInPlaceConfigEntryCount] = {
|
||||
{HomebrewEntryMainThreadHandle, 0, {main_thread_handle, 0}},
|
||||
{HomebrewEntryProcessHandle, 0, {process_handle, 0}},
|
||||
{HomebrewEntryNextLoadPath, 0, {next_load_path_addr, next_load_argv_addr}},
|
||||
{HomebrewEntryOverrideHeap, 0, {heap_addr, heap_size}},
|
||||
{HomebrewEntryAppletType, 0, {HomebrewAppletTypeApplication, 0}},
|
||||
{HomebrewEntryArgv, 0, {0, argv_entry_addr}},
|
||||
{HomebrewEntrySyscallAvailableHint, 0, {HomebrewAllSvcHints, HomebrewAllSvcHints}},
|
||||
{HomebrewEntryRandomSeed, 0,
|
||||
{process.GetRandomEntropy(0), process.GetRandomEntropy(1)}},
|
||||
{HomebrewEntryHosVersion, 0, {HomebrewHosVersion, 0}},
|
||||
{HomebrewEntrySyscallAvailableHint2, 0, {HomebrewAllSvcHints, 0}},
|
||||
{HomebrewEntryEndOfList, 0, {0, 0}},
|
||||
};
|
||||
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries));
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, image->argv_string.data(),
|
||||
image->argv_string.size());
|
||||
process.GetMemory().Write32(Common::ProcessAddress{GetInteger(thread.GetTlsAddress()) + 0x110},
|
||||
main_thread_handle);
|
||||
|
||||
process.SetArgReturnAddress(Kernel::KProcessAddress{
|
||||
image->exit_process_offset ? base + *image->exit_process_offset : 0});
|
||||
SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr);
|
||||
if (image->nxlink_argv_marker) {
|
||||
process.SetHomebrewNxlinkArgvMarker(*image->nxlink_argv_marker);
|
||||
} else {
|
||||
process.ClearHomebrewNxlinkArgvMarker();
|
||||
}
|
||||
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, image->nxlink_argv_marker);
|
||||
system.GetFileSystemController().RegisterProcess(
|
||||
process.GetProcessId(), program_id,
|
||||
std::make_unique<FileSys::RomFSFactory>(loader, system.GetContentProvider(),
|
||||
system.GetFileSystemController()),
|
||||
homebrew_initial_cwd);
|
||||
process.SetHomebrewInPlaceNextLoad(true);
|
||||
|
||||
auto& context = thread.GetContext();
|
||||
context = {};
|
||||
context.r[0] = config_addr;
|
||||
context.r[1] = UINT64_MAX;
|
||||
context.r[18] = Common::Random::Random64(0) | 1;
|
||||
context.lr = image->exit_process_offset ? base + *image->exit_process_offset : 0;
|
||||
context.pc = base;
|
||||
context.sp = GetInteger(stack_top);
|
||||
context.fpcr = 0;
|
||||
context.fpsr = 0;
|
||||
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
if (auto* arm = process.GetArmInterface(core); arm != nullptr) {
|
||||
arm->ClearInstructionCache();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
const std::vector<u8>& data, std::string nro_path,
|
||||
std::string file_name) {
|
||||
const std::vector<u8>& data) {
|
||||
if (data.size() < sizeof(NroHeader)) {
|
||||
return {};
|
||||
}
|
||||
@@ -625,49 +195,47 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
|
||||
codeset.DataSegment().size += bss_size;
|
||||
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
|
||||
struct ConfigEntry {
|
||||
u32_le key;
|
||||
u32_le flags;
|
||||
u64_le value[2];
|
||||
};
|
||||
static_assert(sizeof(ConfigEntry) == 0x18);
|
||||
// AArch64 encoding for svc #0x7 (ExitProcess).
|
||||
constexpr u32 kSvcExitProcessInstruction = 0xD40000E1;
|
||||
constexpr size_t kNumEntries = 4; // MainThreadHandle, AppletType, Argv, EndOfList
|
||||
constexpr size_t kConfigTableSize = kNumEntries * sizeof(ConfigEntry);
|
||||
std::string argv_string;
|
||||
size_t args_offset_in_image = 0;
|
||||
std::optional<size_t> exit_process_offset_in_image;
|
||||
const auto& program_args = Settings::values.program_args.GetValue();
|
||||
const std::string argv0 = MakeHomebrewArgv0(std::move(nro_path), std::move(file_name));
|
||||
argv_string = QuoteHomebrewArgvComponent(argv0);
|
||||
if (!program_args.empty()) {
|
||||
argv_string.push_back(' ');
|
||||
argv_string = "homebrew ";
|
||||
argv_string += program_args;
|
||||
}
|
||||
const auto nxlink_server_mode = Settings::values.homebrew_nxlink_server_mode.GetValue();
|
||||
const bool append_loopback_nxlink_marker =
|
||||
nxlink_server_mode != Settings::HomebrewNxlinkServerMode::Disabled;
|
||||
const auto nxlink_argv_marker =
|
||||
HomebrewNxlink::PrepareArgv(argv_string, {}, append_loopback_nxlink_marker);
|
||||
if (argv_string.empty() || argv_string.back() != '\0') {
|
||||
argv_string.push_back('\0');
|
||||
}
|
||||
|
||||
const auto& code_segment = codeset.CodeSegment();
|
||||
const size_t code_end =
|
||||
(std::min)(program_image.size(), code_segment.offset + code_segment.size);
|
||||
for (size_t offset = code_segment.offset; offset + sizeof(u32) <= code_end;
|
||||
offset += sizeof(u32)) {
|
||||
u32 instruction{};
|
||||
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
|
||||
if (instruction == HomebrewSvcExitProcessInstruction) {
|
||||
exit_process_offset_in_image = offset;
|
||||
break;
|
||||
const auto& code = codeset.CodeSegment();
|
||||
const size_t code_end = (std::min)(program_image.size(), code.offset + code.size);
|
||||
for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) {
|
||||
u32 instruction{};
|
||||
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
|
||||
if (instruction == kSvcExitProcessInstruction) {
|
||||
exit_process_offset_in_image = offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exit_process_offset_in_image) {
|
||||
LOG_WARNING(Loader,
|
||||
"Unable to find svcExitProcess in NRO; returning from main may fault");
|
||||
}
|
||||
}
|
||||
if (!exit_process_offset_in_image) {
|
||||
LOG_WARNING(Loader, "Unable to find svcExitProcess in NRO; returning from main may fault");
|
||||
}
|
||||
|
||||
const size_t entries_and_buffers =
|
||||
Common::AlignUp(HomebrewBaseConfigTableSize + HomebrewNextLoadPathSize +
|
||||
HomebrewNextLoadArgvSize + argv_string.size(),
|
||||
Core::Memory::YUZU_PAGESIZE);
|
||||
const size_t entries_and_argv =
|
||||
Common::AlignUp(kConfigTableSize + argv_string.size(), Core::Memory::YUZU_PAGESIZE);
|
||||
|
||||
args_offset_in_image = program_image.size();
|
||||
codeset.DataSegment().size += static_cast<u32>(entries_and_buffers);
|
||||
program_image.resize(args_offset_in_image + entries_and_buffers);
|
||||
args_offset_in_image = program_image.size();
|
||||
codeset.DataSegment().size += static_cast<u32>(entries_and_argv);
|
||||
program_image.resize(args_offset_in_image + entries_and_argv);
|
||||
}
|
||||
size_t image_size = program_image.size();
|
||||
|
||||
#ifdef HAS_NCE
|
||||
@@ -695,9 +263,6 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
image_size += patch_segment.size;
|
||||
}
|
||||
#endif
|
||||
// In-place NextLoad reuses the original code mapping; leave room for larger homebrew cores.
|
||||
constexpr size_t HomebrewCodeArenaSize = 192 * 1024 * 1024;
|
||||
image_size = (std::max)(image_size, HomebrewCodeArenaSize);
|
||||
|
||||
// Enable direct memory mapping in case of NCE.
|
||||
const u64 fastmem_base = [&]() -> size_t {
|
||||
@@ -732,44 +297,31 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
// Load codeset for current process
|
||||
codeset.memory = std::move(program_image);
|
||||
process.LoadModule(system.Kernel(), std::move(codeset), process.GetEntryPoint());
|
||||
process.SetHomebrewInPlaceNextLoad(false);
|
||||
if (nxlink_argv_marker) {
|
||||
process.SetHomebrewNxlinkArgvMarker(*nxlink_argv_marker);
|
||||
} else {
|
||||
process.ClearHomebrewNxlinkArgvMarker();
|
||||
}
|
||||
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, nxlink_argv_marker);
|
||||
{
|
||||
if (!argv_string.empty()) {
|
||||
constexpr u32 kEntryEndOfList = 0;
|
||||
constexpr u32 kEntryMainThreadHandle = 1;
|
||||
constexpr u32 kEntryArgv = 5;
|
||||
constexpr u32 kEntryAppletType = 7;
|
||||
constexpr u32 kAppletTypeApplication = 0;
|
||||
|
||||
const u64 base = GetInteger(process.GetEntryPoint());
|
||||
const u64 config_addr = base + args_offset_in_image;
|
||||
const u64 next_load_path_addr = config_addr + HomebrewBaseConfigTableSize;
|
||||
const u64 next_load_argv_addr = next_load_path_addr + HomebrewNextLoadPathSize;
|
||||
const u64 argv_addr = next_load_argv_addr + HomebrewNextLoadArgvSize;
|
||||
const u64 argv_entry_addr = argv_string.empty() ? 0 : argv_addr;
|
||||
const u64 argv_addr = config_addr + kConfigTableSize;
|
||||
|
||||
const HomebrewConfigEntry entries[HomebrewBaseConfigEntryCount] = {
|
||||
{HomebrewEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run()
|
||||
{HomebrewEntryProcessHandle, 0, {0, 0}}, // Value[0] patched in Run()
|
||||
{HomebrewEntryNextLoadPath, 0, {next_load_path_addr, next_load_argv_addr}},
|
||||
{HomebrewEntryAppletType, 0, {HomebrewAppletTypeApplication, 0}},
|
||||
{HomebrewEntryArgv, 0, {0, argv_entry_addr}},
|
||||
{HomebrewEntrySyscallAvailableHint, 0, {HomebrewAllSvcHints, HomebrewAllSvcHints}},
|
||||
{HomebrewEntryRandomSeed, 0,
|
||||
{process.GetRandomEntropy(0), process.GetRandomEntropy(1)}},
|
||||
{HomebrewEntryHosVersion, 0, {HomebrewHosVersion, 0}},
|
||||
{HomebrewEntrySyscallAvailableHint2, 0, {HomebrewAllSvcHints, 0}},
|
||||
{HomebrewEntryEndOfList, 0, {0, 0}},
|
||||
const ConfigEntry entries[kNumEntries] = {
|
||||
{kEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run()
|
||||
{kEntryAppletType, 0, {kAppletTypeApplication, 0}},
|
||||
{kEntryArgv, 0, {0, argv_addr}},
|
||||
{kEntryEndOfList, 0, {0, 0}},
|
||||
};
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries,
|
||||
sizeof(entries));
|
||||
if (!argv_string.empty()) {
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(),
|
||||
argv_string.size());
|
||||
}
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries));
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(), argv_string.size());
|
||||
constexpr size_t kMainThreadHandleValueOffset = offsetof(ConfigEntry, value);
|
||||
process.SetArgPointer(Kernel::KProcessAddress{config_addr});
|
||||
if (exit_process_offset_in_image) {
|
||||
process.SetArgReturnAddress(Kernel::KProcessAddress{base + *exit_process_offset_in_image});
|
||||
}
|
||||
SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr);
|
||||
process.SetMainThreadHandleAddr(Kernel::KProcessAddress{config_addr + kMainThreadHandleValueOffset});
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -777,8 +329,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
|
||||
bool AppLoader_NRO::LoadNro(Core::System& system, Kernel::KProcess& process,
|
||||
const FileSys::VfsFile& nro_file) {
|
||||
return LoadNroImpl(system, process, nro_file.ReadAllBytes(), nro_file.GetFullPath(),
|
||||
nro_file.GetName());
|
||||
return LoadNroImpl(system, process, nro_file.ReadAllBytes());
|
||||
}
|
||||
|
||||
AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::System& system) {
|
||||
@@ -792,13 +343,10 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
|
||||
|
||||
u64 program_id{};
|
||||
ReadProgramId(program_id);
|
||||
const std::string argv0 = MakeHomebrewArgv0(file->GetFullPath(), file->GetName());
|
||||
const std::string homebrew_initial_cwd = GetHomebrewInitialCwd(argv0);
|
||||
system.GetFileSystemController().RegisterProcess(
|
||||
process.GetProcessId(), program_id,
|
||||
std::make_unique<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
|
||||
system.GetFileSystemController()),
|
||||
homebrew_initial_cwd);
|
||||
system.GetFileSystemController()));
|
||||
|
||||
is_loaded = true;
|
||||
return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
@@ -22,15 +19,10 @@ class NACP;
|
||||
|
||||
namespace Kernel {
|
||||
class KProcess;
|
||||
class KThread;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
|
||||
[[nodiscard]] bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process,
|
||||
Kernel::KThread& thread, const FileSys::VirtualFile& nro_file,
|
||||
const std::string& nro_path, const std::string& launch_argv);
|
||||
|
||||
/// Loads an NRO file
|
||||
class AppLoader_NRO final : public AppLoader {
|
||||
public:
|
||||
|
||||
@@ -100,7 +100,6 @@ void EmulatedConsole::ReloadInput() {
|
||||
motion.gyro = emulated_motion.GetGyroscope();
|
||||
motion.rotation = emulated_motion.GetRotations();
|
||||
motion.orientation = emulated_motion.GetOrientation();
|
||||
motion.quaternion = emulated_motion.GetQuaternion();
|
||||
motion.is_at_rest = !emulated_motion.IsMoving(motion_sensitivity);
|
||||
|
||||
// Unique index for identifying touch device source
|
||||
|
||||
@@ -648,13 +648,6 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
|
||||
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, Settings::values.enable_raw_input ? "1" : "0");
|
||||
|
||||
#ifdef _WIN32
|
||||
if (Settings::values.disable_wgi_xinput) {
|
||||
SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, "0", SDL_HINT_OVERRIDE);
|
||||
SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_WGI, "0", SDL_HINT_OVERRIDE);
|
||||
}
|
||||
#endif
|
||||
|
||||
// SDL3 defaults Steam Controller Bluetooth HIDAPI support to off, which can disable gyro.
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1");
|
||||
|
||||
@@ -302,8 +302,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
INSERT(Settings, device_name, tr("Device Name"), tr("The name of the console."));
|
||||
INSERT(Settings, program_args, tr("Homebrew Args"),
|
||||
tr("Command-line arguments passed to homebrew at launch (e.g. -noglsl)."));
|
||||
INSERT(Settings, homebrew_nxlink_server_mode, tr("nxlink Server"),
|
||||
tr("Starts a local nxlink server for homebrew stdout/stderr streams."));
|
||||
INSERT(Settings, custom_rtc, tr("Custom RTC Date:"),
|
||||
tr("This option allows to change the clock of the console.\n"
|
||||
"Can be used to manipulate time in games."));
|
||||
|
||||
@@ -159,9 +159,7 @@ void GameListModel::RemoveFavorite(u64 program_id) {
|
||||
|
||||
void GameListModel::Repopulate() {
|
||||
current_worker.reset();
|
||||
if (!QtCommon::system->IsPoweredOn()) {
|
||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||
}
|
||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||
PopulateAsync(UISettings::values.game_dirs);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ add_library(video_core STATIC
|
||||
control/channel_state_cache.h
|
||||
control/scheduler.cpp
|
||||
control/scheduler.h
|
||||
delayed_destruction_ring.h
|
||||
deferred_destruction_queue.h
|
||||
dirty_flags.cpp
|
||||
dirty_flags.h
|
||||
dma_pusher.cpp
|
||||
|
||||
@@ -31,44 +31,77 @@ BufferCache<P>::BufferCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, R
|
||||
immediately_free = (Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive);
|
||||
#endif
|
||||
if (!runtime.CanReportMemoryUsage()) {
|
||||
minimum_memory = DEFAULT_EXPECTED_MEMORY;
|
||||
critical_memory = DEFAULT_CRITICAL_MEMORY;
|
||||
memory_budget = FALLBACK_MEMORY_BUDGET;
|
||||
return;
|
||||
}
|
||||
|
||||
const s64 device_local_memory = static_cast<s64>(runtime.GetDeviceLocalMemory());
|
||||
const s64 min_spacing_expected = device_local_memory - 1_GiB;
|
||||
const s64 min_spacing_critical = device_local_memory - 512_MiB;
|
||||
const s64 mem_threshold = (std::min)(device_local_memory, TARGET_THRESHOLD);
|
||||
const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
|
||||
const s64 min_vacancy_critical = (2 * mem_threshold) / 10;
|
||||
minimum_memory = static_cast<u64>(
|
||||
(std::max)((std::min)(device_local_memory - min_vacancy_expected, min_spacing_expected),
|
||||
DEFAULT_EXPECTED_MEMORY));
|
||||
critical_memory = static_cast<u64>(
|
||||
(std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical),
|
||||
DEFAULT_CRITICAL_MEMORY));
|
||||
memory_budget = runtime.GetDeviceLocalMemory();
|
||||
}
|
||||
|
||||
template <class P>
|
||||
BufferCache<P>::~BufferCache() = default;
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::RunGarbageCollector() {
|
||||
const bool aggressive_gc = total_used_memory >= critical_memory;
|
||||
const u64 ticks_to_destroy = aggressive_gc ? 60 : 120;
|
||||
int num_iterations = aggressive_gc ? 64 : 32;
|
||||
const auto clean_up = [this, &num_iterations](BufferId buffer_id) {
|
||||
if (num_iterations == 0) {
|
||||
u64 BufferCache<P>::DeviceUsage(bool force_refresh) {
|
||||
if (!runtime.CanReportAllocationUsage()) {
|
||||
return total_used_memory;
|
||||
}
|
||||
if (force_refresh || usage_refresh_countdown == 0) {
|
||||
cached_device_usage = runtime.GetDeviceAllocationUsage();
|
||||
usage_refresh_countdown = USAGE_REFRESH_INTERVAL;
|
||||
} else {
|
||||
--usage_refresh_countdown;
|
||||
}
|
||||
return cached_device_usage;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
u64 BufferCache<P>::ReclaimMemory(u64 target_bytes, bool allow_download) {
|
||||
if (target_bytes == 0 || in_reclaim) {
|
||||
return 0;
|
||||
}
|
||||
in_reclaim = true;
|
||||
sentenced_buffers.Reclaim(runtime.CompletedSyncPoint());
|
||||
u64 freed = 0;
|
||||
const auto clean_up = [&](BufferId buffer_id) {
|
||||
if (freed >= target_bytes) {
|
||||
return true;
|
||||
}
|
||||
--num_iterations;
|
||||
auto& buffer = slot_buffers[buffer_id];
|
||||
if (!allow_download && IsRegionGpuModified(buffer.CpuAddr(), buffer.SizeBytes())) {
|
||||
return false;
|
||||
}
|
||||
const u64 buffer_bytes = Common::AlignUp(buffer.SizeBytes(), 1024);
|
||||
DownloadBufferMemory(buffer);
|
||||
DeleteBuffer(buffer_id);
|
||||
freed += buffer_bytes;
|
||||
return false;
|
||||
};
|
||||
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, clean_up);
|
||||
const u64 cold_tick =
|
||||
frame_tick > RECLAIM_GUARD_FRAMES ? frame_tick - RECLAIM_GUARD_FRAMES : 0;
|
||||
lru_cache.ForEachItemBelow(cold_tick, clean_up);
|
||||
if (freed < target_bytes) {
|
||||
lru_cache.ForEachItemBelow(frame_tick > 0 ? frame_tick - 1 : 0, clean_up);
|
||||
}
|
||||
sentenced_buffers.Reclaim(runtime.CompletedSyncPoint());
|
||||
in_reclaim = false;
|
||||
usage_refresh_countdown = 0;
|
||||
reclaim_stalled = freed == 0;
|
||||
return freed;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::EnsureHeadroom(bool allow_download) {
|
||||
if (reclaim_stalled) {
|
||||
return;
|
||||
}
|
||||
const u64 limit = memory_budget > RECLAIM_HEADROOM ? memory_budget - RECLAIM_HEADROOM : 0;
|
||||
const u64 usage = DeviceUsage(false);
|
||||
if (usage <= limit) {
|
||||
return;
|
||||
}
|
||||
const u64 target = (limit / 100) * RECLAIM_TARGET_PERCENT;
|
||||
ReclaimMemory((std::min)(usage - target, total_used_memory), allow_download);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -96,15 +129,11 @@ void BufferCache<P>::TickFrame() {
|
||||
const bool skip_preferred = hits * 256 < shots * 251;
|
||||
channel_state->uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0;
|
||||
|
||||
// If we can obtain the memory info, use it instead of the estimate.
|
||||
if (runtime.CanReportMemoryUsage()) {
|
||||
total_used_memory = runtime.GetDeviceMemoryUsage();
|
||||
}
|
||||
if (total_used_memory >= minimum_memory) {
|
||||
RunGarbageCollector();
|
||||
}
|
||||
usage_refresh_countdown = 0;
|
||||
reclaim_stalled = false;
|
||||
EnsureHeadroom(true);
|
||||
++frame_tick;
|
||||
delayed_destruction_ring.Tick();
|
||||
sentenced_buffers.Reclaim(runtime.CompletedSyncPoint());
|
||||
|
||||
for (auto& buffer : async_buffers_death_ring) {
|
||||
runtime.FreeDeferredStagingBuffer(buffer);
|
||||
@@ -1576,6 +1605,7 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
|
||||
|
||||
template <class P>
|
||||
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
|
||||
EnsureHeadroom(false);
|
||||
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE);
|
||||
device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE);
|
||||
wanted_size = static_cast<u32>(device_addr_end - device_addr);
|
||||
@@ -1613,7 +1643,7 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
|
||||
total_used_memory += Common::AlignUp(size, 1024);
|
||||
buffer.setLRUID(lru_cache.Insert(buffer_id, frame_tick));
|
||||
} else {
|
||||
total_used_memory -= Common::AlignUp(size, 1024);
|
||||
total_used_memory -= std::min<u64>(total_used_memory, Common::AlignUp(size, 1024));
|
||||
lru_cache.Free(buffer.getLRUID());
|
||||
}
|
||||
const DAddr device_addr_begin = buffer.CpuAddr();
|
||||
@@ -1872,7 +1902,7 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
|
||||
#ifdef YUZU_LEGACY
|
||||
if (!do_not_mark || !immediately_free)
|
||||
#endif
|
||||
delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
|
||||
sentenced_buffers.Push(std::move(slot_buffers[buffer_id]), runtime.CurrentSyncPoint());
|
||||
|
||||
slot_buffers.erase(buffer_id);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -30,7 +31,7 @@
|
||||
#include "common/slot_vector.h"
|
||||
#include "video_core/buffer_cache/buffer_base.h"
|
||||
#include "video_core/control/channel_state_cache.h"
|
||||
#include "video_core/delayed_destruction_ring.h"
|
||||
#include "video_core/deferred_destruction_queue.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
@@ -182,13 +183,15 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
|
||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
|
||||
|
||||
#ifdef YUZU_LEGACY
|
||||
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
|
||||
static constexpr u64 RECLAIM_HEADROOM = 384_MiB;
|
||||
#else
|
||||
static constexpr s64 TARGET_THRESHOLD = 4_GiB;
|
||||
static constexpr u64 RECLAIM_HEADROOM = 512_MiB;
|
||||
#endif
|
||||
|
||||
static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB;
|
||||
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
|
||||
static constexpr u64 FALLBACK_MEMORY_BUDGET = 2_GiB;
|
||||
static constexpr u32 USAGE_REFRESH_INTERVAL = 16;
|
||||
static constexpr u64 RECLAIM_GUARD_FRAMES = 8;
|
||||
static constexpr u64 RECLAIM_TARGET_PERCENT = 88;
|
||||
|
||||
// Debug Flags.
|
||||
|
||||
@@ -215,6 +218,8 @@ public:
|
||||
|
||||
void TickFrame();
|
||||
|
||||
u64 ReclaimMemory(u64 target_bytes, bool allow_download);
|
||||
|
||||
void WriteMemory(DAddr device_addr, u64 size);
|
||||
|
||||
void CachedWriteMemory(DAddr device_addr, u64 size);
|
||||
@@ -358,7 +363,9 @@ private:
|
||||
((device_addr + size) & ~Core::DEVICE_PAGEMASK);
|
||||
}
|
||||
|
||||
void RunGarbageCollector();
|
||||
u64 DeviceUsage(bool force_refresh);
|
||||
|
||||
void EnsureHeadroom(bool allow_download);
|
||||
|
||||
void BindHostIndexBuffer();
|
||||
|
||||
@@ -475,12 +482,7 @@ private:
|
||||
Tegra::MaxwellDeviceMemoryManager& device_memory;
|
||||
|
||||
Common::SlotVector<Buffer> slot_buffers;
|
||||
#ifdef YUZU_LEGACY
|
||||
static constexpr size_t TICKS_TO_DESTROY = 6;
|
||||
#else
|
||||
static constexpr size_t TICKS_TO_DESTROY = 8;
|
||||
#endif
|
||||
DelayedDestructionRing<Buffer, TICKS_TO_DESTROY> delayed_destruction_ring;
|
||||
DeferredDestructionQueue<Buffer> sentenced_buffers;
|
||||
|
||||
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
|
||||
|
||||
@@ -515,8 +517,11 @@ private:
|
||||
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
|
||||
u64 frame_tick = 0;
|
||||
u64 total_used_memory = 0;
|
||||
u64 minimum_memory = 0;
|
||||
u64 critical_memory = 0;
|
||||
u64 memory_budget = 0;
|
||||
u64 cached_device_usage = 0;
|
||||
u32 usage_refresh_countdown = 0;
|
||||
bool in_reclaim = false;
|
||||
bool reclaim_stalled = false;
|
||||
BufferId inline_buffer_id;
|
||||
#ifdef YUZU_LEGACY
|
||||
bool immediately_free = false;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/container/deque.hpp>
|
||||
#include <boost/container/options.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
template <typename T>
|
||||
class DeferredDestructionQueue {
|
||||
public:
|
||||
void Push(T&& object, u64 sync_point) {
|
||||
entries.emplace_back(std::move(object), sync_point);
|
||||
}
|
||||
|
||||
void Reclaim(u64 completed_sync_point) {
|
||||
while (!entries.empty() && entries.front().sync_point <= completed_sync_point) {
|
||||
entries.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t Size() const noexcept {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Empty() const noexcept {
|
||||
return entries.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
Entry(T&& object_, u64 sync_point_) noexcept
|
||||
: object{std::move(object_)}, sync_point{sync_point_} {}
|
||||
|
||||
T object;
|
||||
u64 sync_point;
|
||||
};
|
||||
|
||||
using EntryDequeOptions =
|
||||
boost::container::deque_options<boost::container::block_size<8u>>::type;
|
||||
|
||||
boost::container::deque<Entry, void, EntryDequeOptions> entries;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -1,34 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
/// Container to push objects to be destroyed a few ticks in the future
|
||||
template <typename T, size_t TICKS_TO_DESTROY>
|
||||
class DelayedDestructionRing {
|
||||
public:
|
||||
void Tick() {
|
||||
index = (index + 1) % TICKS_TO_DESTROY;
|
||||
elements[index].clear();
|
||||
}
|
||||
|
||||
void Push(T&& object) {
|
||||
elements[index].push_back(std::move(object));
|
||||
}
|
||||
|
||||
private:
|
||||
size_t index = 0;
|
||||
std::array<std::vector<T>, TICKS_TO_DESTROY> elements;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -18,7 +18,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "video_core/delayed_destruction_ring.h"
|
||||
#include "video_core/deferred_destruction_queue.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
#include "video_core/host1x/syncpoint_manager.h"
|
||||
@@ -50,7 +50,8 @@ public:
|
||||
/// Notify the fence manager about a new frame
|
||||
void TickFrame() {
|
||||
std::unique_lock lock(ring_guard);
|
||||
delayed_destruction_ring.Tick();
|
||||
++retire_tick;
|
||||
sentenced_fences.Reclaim(retire_tick > RETIRE_DELAY ? retire_tick - RETIRE_DELAY : 0);
|
||||
}
|
||||
|
||||
// Unlike other fences, this one doesn't
|
||||
@@ -186,7 +187,7 @@ private:
|
||||
}
|
||||
{
|
||||
std::unique_lock lock(ring_guard);
|
||||
delayed_destruction_ring.Push(std::move(current_fence));
|
||||
sentenced_fences.Push(std::move(current_fence), retire_tick);
|
||||
}
|
||||
fences.pop();
|
||||
}
|
||||
@@ -219,7 +220,7 @@ private:
|
||||
}
|
||||
{
|
||||
std::unique_lock lock(ring_guard);
|
||||
delayed_destruction_ring.Push(std::move(current_fence));
|
||||
sentenced_fences.Push(std::move(current_fence), retire_tick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,7 +265,9 @@ private:
|
||||
|
||||
std::jthread fence_thread;
|
||||
|
||||
DelayedDestructionRing<TFence, 8> delayed_destruction_ring;
|
||||
static constexpr u64 RETIRE_DELAY = 8;
|
||||
u64 retire_tick = 1;
|
||||
DeferredDestructionQueue<TFence> sentenced_fences;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -30,6 +30,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
|
||||
thread = std::jthread([&](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("GPU");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
system.RegisterHostThread();
|
||||
|
||||
auto current_context = context.Acquire();
|
||||
|
||||
@@ -32,6 +32,7 @@ set(SHADER_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
|
||||
|
||||
@@ -1384,11 +1384,7 @@ void DecompressBlock(ivec3 coord) {
|
||||
p = Cf / 65535.0f;
|
||||
}
|
||||
|
||||
#ifdef VULKAN
|
||||
imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar);
|
||||
#else
|
||||
imageStore(dest_image, coord + ivec3(i, j, 0), clamp(p, 0.0f, 1.0f).gbar);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#version 450 core
|
||||
|
||||
layout(binding = 0) uniform sampler2D img_in;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
ivec2 dst_offset;
|
||||
ivec2 src_offset;
|
||||
ivec2 scale;
|
||||
};
|
||||
|
||||
void main() {
|
||||
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
|
||||
const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x);
|
||||
const ivec2 coord = msaa_coord * scale + sample_offset + src_offset;
|
||||
gl_FragDepth = texelFetch(img_in, coord, 0).r;
|
||||
}
|
||||
+87
-92
@@ -417,6 +417,14 @@ void HLE_TransformFeedbackSetup::Execute(Core::System& system, Engines::Maxwell3
|
||||
default: return std::monostate{};
|
||||
}
|
||||
}
|
||||
[[nodiscard]] inline bool CanBeHLEProgram(u64 hash) noexcept {
|
||||
switch (hash) {
|
||||
#define HLE_MACRO_ELEM(HASH, TY, VAL) case HASH: return true;
|
||||
HLE_MACRO_LIST
|
||||
#undef HLE_MACRO_ELEM
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void MacroInterpreterImpl::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method) {
|
||||
Reset();
|
||||
@@ -782,17 +790,13 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
|
||||
const bool valid_operation = !is_a_zero && !is_b_zero;
|
||||
[[maybe_unused]] const bool is_move_operation = !is_a_zero && is_b_zero;
|
||||
const bool has_zero_register = is_a_zero || is_b_zero;
|
||||
const bool zero_reg_skip =
|
||||
optimizer.zero_reg_skip && !is_a_zero && is_b_zero &&
|
||||
(opcode.alu_operation == Macro::ALUOperation::Xor ||
|
||||
opcode.alu_operation == Macro::ALUOperation::Or ||
|
||||
(optimizer.can_skip_carry && (opcode.alu_operation == Macro::ALUOperation::Add ||
|
||||
opcode.alu_operation == Macro::ALUOperation::Subtract)));
|
||||
const bool no_zero_reg_skip = opcode.alu_operation == Macro::ALUOperation::AddWithCarry ||
|
||||
opcode.alu_operation == Macro::ALUOperation::SubtractWithBorrow;
|
||||
|
||||
Xbyak::Reg32 src_a;
|
||||
Xbyak::Reg32 src_b;
|
||||
|
||||
if (!zero_reg_skip) {
|
||||
if (!optimizer.zero_reg_skip || no_zero_reg_skip) {
|
||||
src_a = Compile_GetRegister(opcode.src_a, RESULT);
|
||||
src_b = Compile_GetRegister(opcode.src_b, eax);
|
||||
} else {
|
||||
@@ -808,7 +812,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
|
||||
|
||||
switch (opcode.alu_operation) {
|
||||
case Macro::ALUOperation::Add:
|
||||
if (zero_reg_skip) {
|
||||
if (optimizer.zero_reg_skip) {
|
||||
if (valid_operation) {
|
||||
add(src_a, src_b);
|
||||
}
|
||||
@@ -826,7 +830,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
|
||||
setc(byte[STATE + offsetof(JITState, carry_flag)]);
|
||||
break;
|
||||
case Macro::ALUOperation::Subtract:
|
||||
if (zero_reg_skip) {
|
||||
if (optimizer.zero_reg_skip) {
|
||||
if (valid_operation) {
|
||||
sub(src_a, src_b);
|
||||
has_emitted = true;
|
||||
@@ -845,7 +849,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
|
||||
setc(byte[STATE + offsetof(JITState, carry_flag)]);
|
||||
break;
|
||||
case Macro::ALUOperation::Xor:
|
||||
if (zero_reg_skip) {
|
||||
if (optimizer.zero_reg_skip) {
|
||||
if (valid_operation) {
|
||||
xor_(src_a, src_b);
|
||||
}
|
||||
@@ -854,7 +858,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
|
||||
}
|
||||
break;
|
||||
case Macro::ALUOperation::Or:
|
||||
if (zero_reg_skip) {
|
||||
if (optimizer.zero_reg_skip) {
|
||||
if (valid_operation) {
|
||||
or_(src_a, src_b);
|
||||
}
|
||||
@@ -863,7 +867,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
|
||||
}
|
||||
break;
|
||||
case Macro::ALUOperation::And:
|
||||
if (zero_reg_skip) {
|
||||
if (optimizer.zero_reg_skip) {
|
||||
if (!has_zero_register) {
|
||||
and_(src_a, src_b);
|
||||
}
|
||||
@@ -872,7 +876,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
|
||||
}
|
||||
break;
|
||||
case Macro::ALUOperation::AndNot:
|
||||
if (zero_reg_skip) {
|
||||
if (optimizer.zero_reg_skip) {
|
||||
if (!is_a_zero) {
|
||||
not_(src_b);
|
||||
and_(src_a, src_b);
|
||||
@@ -883,7 +887,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
|
||||
}
|
||||
break;
|
||||
case Macro::ALUOperation::Nand:
|
||||
if (zero_reg_skip) {
|
||||
if (optimizer.zero_reg_skip) {
|
||||
if (!is_a_zero) {
|
||||
and_(src_a, src_b);
|
||||
not_(src_a);
|
||||
@@ -1341,89 +1345,80 @@ static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) {
|
||||
macro_file.write(reinterpret_cast<const char*>(code.data()), code.size_bytes());
|
||||
}
|
||||
|
||||
void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 method,
|
||||
std::span<const u32> parameters) {
|
||||
const auto execute_variant = [&system, &maxwell3d, ¶meters,
|
||||
method](AnyCachedMacro& cached) {
|
||||
if (std::holds_alternative<MacroInterpreterImpl>(cached) ||
|
||||
std::holds_alternative<std::unique_ptr<DynamicCachedMacro>>(cached) ||
|
||||
Settings::values.disable_macro_hle) {
|
||||
maxwell3d.RefreshParameters();
|
||||
}
|
||||
|
||||
if (auto program = std::get_if<HLE_DrawArraysIndirect>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_DrawIndexedIndirect>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_MultiDrawIndexedIndirectCount>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_MultiLayerClear>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_C713C83D8F63CCF3>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_D7333D26E0A93EDE>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_BindShader>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_SetRasterBoundingBox>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_ClearConstBuffer>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_ClearMemory>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_TransformFeedbackSetup>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<HLE_DrawIndirectByteCount>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<MacroInterpreterImpl>(&cached))
|
||||
return program->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto program = std::get_if<std::unique_ptr<DynamicCachedMacro>>(&cached))
|
||||
return program->get()->Execute(system, maxwell3d, parameters, method);
|
||||
|
||||
UNREACHABLE();
|
||||
void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters) {
|
||||
auto const execute_variant = [&system, &maxwell3d, ¶meters, method](AnyCachedMacro& acm) {
|
||||
if (auto a = std::get_if<HLE_DrawArraysIndirect>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_DrawIndexedIndirect>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_MultiDrawIndexedIndirectCount>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_MultiLayerClear>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_C713C83D8F63CCF3>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_D7333D26E0A93EDE>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_BindShader>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_SetRasterBoundingBox>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_ClearConstBuffer>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_ClearMemory>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_TransformFeedbackSetup>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<HLE_DrawIndirectByteCount>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<MacroInterpreterImpl>(&acm))
|
||||
return a->Execute(system, maxwell3d, parameters, method);
|
||||
if (auto a = std::get_if<std::unique_ptr<DynamicCachedMacro>>(&acm))
|
||||
return a->get()->Execute(system, maxwell3d, parameters, method);
|
||||
};
|
||||
if (auto const it = macro_cache.find(method); it != macro_cache.end()) {
|
||||
execute_variant(it->second.program);
|
||||
return;
|
||||
}
|
||||
|
||||
// Macro not compiled, check if it's uploaded and if so, compile it
|
||||
std::span<const u32> code;
|
||||
auto macro_code = uploaded_macro_code.find(method);
|
||||
if (macro_code == uploaded_macro_code.end()) {
|
||||
for (auto it = uploaded_macro_code.begin(); it != uploaded_macro_code.end(); ++it) {
|
||||
const auto& [method_base, uploaded_code] = *it;
|
||||
if (method >= method_base && (method - method_base) < uploaded_code.size()) {
|
||||
macro_code = it;
|
||||
break;
|
||||
auto& ci = it->second;
|
||||
if (!CanBeHLEProgram(ci.hash) || Settings::values.disable_macro_hle)
|
||||
maxwell3d.RefreshParameters(); //LLE must reload parameters
|
||||
execute_variant(ci.program);
|
||||
} else {
|
||||
// Macro not compiled, check if it's uploaded and if so, compile it
|
||||
std::optional<u32> mid_method;
|
||||
const auto macro_code = uploaded_macro_code.find(method);
|
||||
if (macro_code == uploaded_macro_code.end()) {
|
||||
for (const auto& [method_base, code] : uploaded_macro_code) {
|
||||
if (method >= method_base && (method - method_base) < code.size()) {
|
||||
mid_method = method_base;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mid_method.has_value()) {
|
||||
ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (macro_code == uploaded_macro_code.end()) {
|
||||
ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method);
|
||||
return;
|
||||
auto& ci = macro_cache[method];
|
||||
if (mid_method) {
|
||||
const auto& macro_cached = uploaded_macro_code[mid_method.value()];
|
||||
const auto rebased_method = method - mid_method.value();
|
||||
auto& code = uploaded_macro_code[method];
|
||||
code.resize(macro_cached.size() - rebased_method);
|
||||
std::memcpy(code.data(), macro_cached.data() + rebased_method, code.size() * sizeof(u32));
|
||||
ci.hash = Common::HashValue(code);
|
||||
ci.program = Compile(system, maxwell3d, code);
|
||||
} else {
|
||||
ci.program = Compile(system, maxwell3d, macro_code->second);
|
||||
ci.hash = Common::HashValue(macro_code->second);
|
||||
}
|
||||
if (CanBeHLEProgram(ci.hash) && !Settings::values.disable_macro_hle) {
|
||||
ci.program = GetHLEProgram(ci.hash);
|
||||
} else {
|
||||
maxwell3d.RefreshParameters();
|
||||
}
|
||||
execute_variant(ci.program);
|
||||
if (Settings::values.dump_macros) {
|
||||
Dump(ci.hash, macro_code->second, !std::holds_alternative<std::monostate>(ci.program));
|
||||
}
|
||||
|
||||
const auto rebased_method = method - macro_code->first;
|
||||
std::vector<u32> rebased_code(macro_code->second.begin() + rebased_method,
|
||||
macro_code->second.end());
|
||||
const auto [it, inserted] = uploaded_macro_code.emplace(method, std::move(rebased_code));
|
||||
ASSERT(inserted);
|
||||
macro_code = it;
|
||||
}
|
||||
code = macro_code->second;
|
||||
|
||||
auto& ci = macro_cache[method];
|
||||
ci.hash = Common::HashRange(code.begin(), code.end());
|
||||
if (!Settings::values.disable_macro_hle) {
|
||||
ci.program = GetHLEProgram(ci.hash);
|
||||
}
|
||||
if (std::holds_alternative<std::monostate>(ci.program)) {
|
||||
ci.program = Compile(system, maxwell3d, code);
|
||||
}
|
||||
|
||||
execute_variant(ci.program);
|
||||
if (Settings::values.dump_macros) {
|
||||
Dump(ci.hash, code, !std::holds_alternative<std::monostate>(ci.program));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,17 @@ public:
|
||||
void PostCopyBarrier();
|
||||
void Finish();
|
||||
|
||||
void TickFrame(Common::SlotVector<Buffer>&) noexcept {}
|
||||
void TickFrame(Common::SlotVector<Buffer>&) noexcept {
|
||||
++sync_point;
|
||||
}
|
||||
|
||||
u64 CurrentSyncPoint() const noexcept {
|
||||
return sync_point;
|
||||
}
|
||||
|
||||
u64 CompletedSyncPoint() const noexcept {
|
||||
return sync_point > SYNC_POINT_DELAY ? sync_point - SYNC_POINT_DELAY : 0;
|
||||
}
|
||||
|
||||
void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value);
|
||||
|
||||
@@ -128,6 +138,14 @@ public:
|
||||
|
||||
u64 GetDeviceMemoryUsage() const;
|
||||
|
||||
u64 GetDeviceAllocationUsage() const {
|
||||
return GetDeviceMemoryUsage();
|
||||
}
|
||||
|
||||
bool CanReportAllocationUsage() const {
|
||||
return device.CanReportMemoryUsage();
|
||||
}
|
||||
|
||||
void BindFastUniformBuffer(size_t stage, u32 binding_index, u32 size) {
|
||||
const GLuint handle = fast_uniforms[stage][binding_index].handle;
|
||||
const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size);
|
||||
@@ -213,9 +231,13 @@ private:
|
||||
GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV,
|
||||
};
|
||||
|
||||
static constexpr u64 SYNC_POINT_DELAY = 8;
|
||||
|
||||
const Device& device;
|
||||
StagingBufferPool& staging_buffer_pool;
|
||||
|
||||
u64 sync_point = 1;
|
||||
|
||||
bool has_fast_buffer_sub_data = false;
|
||||
bool use_assembly_shaders = false;
|
||||
bool has_unified_vertex_buffers = false;
|
||||
|
||||
@@ -87,6 +87,14 @@ public:
|
||||
|
||||
u64 GetDeviceMemoryUsage() const;
|
||||
|
||||
u64 GetDeviceAllocationUsage() const {
|
||||
return GetDeviceMemoryUsage();
|
||||
}
|
||||
|
||||
bool CanReportAllocationUsage() const {
|
||||
return device.CanReportMemoryUsage();
|
||||
}
|
||||
|
||||
bool CanReportMemoryUsage() const {
|
||||
return device.CanReportMemoryUsage();
|
||||
}
|
||||
@@ -139,7 +147,19 @@ public:
|
||||
|
||||
bool HasNativeASTC() const noexcept;
|
||||
|
||||
void TickFrame() {}
|
||||
void TickFrame() {
|
||||
++sync_point;
|
||||
}
|
||||
|
||||
u64 CurrentSyncPoint() const noexcept {
|
||||
return sync_point;
|
||||
}
|
||||
|
||||
u64 CompletedSyncPoint() const noexcept {
|
||||
return sync_point > SYNC_POINT_DELAY ? sync_point - SYNC_POINT_DELAY : 0;
|
||||
}
|
||||
|
||||
void WaitSyncPoint(u64) {}
|
||||
|
||||
StateTracker& GetStateTracker() {
|
||||
return state_tracker;
|
||||
@@ -174,6 +194,9 @@ private:
|
||||
std::array<OGLFramebuffer, 4> rescale_read_fbos;
|
||||
const Settings::ResolutionScalingInfo& resolution;
|
||||
u64 device_access_memory;
|
||||
|
||||
static constexpr u64 SYNC_POINT_DELAY = 8;
|
||||
u64 sync_point = 1;
|
||||
};
|
||||
|
||||
class Image : public VideoCommon::ImageBase {
|
||||
@@ -370,6 +393,7 @@ struct TextureCacheParams {
|
||||
static constexpr bool HAS_EMULATED_COPIES = true;
|
||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
||||
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
|
||||
static constexpr bool HAS_TIMELINE_SYNC_POINTS = false;
|
||||
|
||||
using Runtime = OpenGL::TextureCacheRuntime;
|
||||
using Image = OpenGL::Image;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
|
||||
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
|
||||
#include "video_core/host_shaders/convert_msaa_to_non_msaa_frag_spv.h"
|
||||
#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_frag_spv.h"
|
||||
#include "video_core/host_shaders/convert_non_msaa_to_msaa_frag_spv.h"
|
||||
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
|
||||
#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
|
||||
@@ -519,7 +520,8 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image,
|
||||
VkFormat format, u32 base_level) {
|
||||
VkFormat format, u32 base_level,
|
||||
VkImageAspectFlags aspect_mask) {
|
||||
return device.CreateImageView(VkImageViewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -534,7 +536,7 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
|
||||
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
},
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.aspectMask = aspect_mask,
|
||||
.baseMipLevel = base_level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
@@ -610,6 +612,8 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
||||
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
||||
convert_msaa_to_non_msaa_frag(BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_FRAG_SPV)),
|
||||
convert_non_msaa_to_msaa_frag(BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_FRAG_SPV)),
|
||||
convert_non_msaa_to_msaa_depth_frag(
|
||||
BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_FRAG_SPV)),
|
||||
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
||||
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
|
||||
|
||||
@@ -895,16 +899,34 @@ void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_i
|
||||
const s32 scale_y = 1 << samples_y;
|
||||
const VkSampleCountFlagBits samples =
|
||||
msaa_to_non_msaa ? VK_SAMPLE_COUNT_1_BIT : SampleCountFlag(num_samples);
|
||||
const auto dst_surface_type = VideoCore::Surface::GetFormatType(dst_format);
|
||||
const bool is_depth = dst_surface_type == VideoCore::Surface::SurfaceType::Depth ||
|
||||
dst_surface_type == VideoCore::Surface::SurfaceType::DepthStencil;
|
||||
const bool has_stencil = dst_surface_type == VideoCore::Surface::SurfaceType::DepthStencil;
|
||||
const VkImageAspectFlags view_aspect =
|
||||
is_depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
VkImageAspectFlags barrier_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
if (is_depth) {
|
||||
barrier_aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (has_stencil) {
|
||||
barrier_aspect |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
}
|
||||
RenderPassKey renderpass_key{};
|
||||
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
|
||||
renderpass_key.color_formats[0] = dst_format;
|
||||
renderpass_key.depth_format = VideoCore::Surface::PixelFormat::Invalid;
|
||||
if (is_depth) {
|
||||
renderpass_key.depth_format = dst_format;
|
||||
} else {
|
||||
renderpass_key.color_formats[0] = dst_format;
|
||||
renderpass_key.depth_format = VideoCore::Surface::PixelFormat::Invalid;
|
||||
}
|
||||
renderpass_key.samples = samples;
|
||||
const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key);
|
||||
const MSAACopyPipelineKey key{
|
||||
.renderpass = renderpass,
|
||||
.samples = samples,
|
||||
.msaa_to_non_msaa = msaa_to_non_msaa,
|
||||
.is_depth = is_depth,
|
||||
};
|
||||
const VkPipeline pipeline = FindOrEmplaceMSAACopyPipeline(key);
|
||||
const VkPipelineLayout layout = *msaa_copy_pipeline_layout;
|
||||
@@ -920,10 +942,10 @@ void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_i
|
||||
ASSERT(copy.dst_subresource.num_layers == 1);
|
||||
vk::ImageView src_view =
|
||||
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format,
|
||||
static_cast<u32>(copy.src_subresource.base_level));
|
||||
static_cast<u32>(copy.src_subresource.base_level), view_aspect);
|
||||
vk::ImageView dst_view =
|
||||
MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format,
|
||||
static_cast<u32>(copy.dst_subresource.base_level));
|
||||
static_cast<u32>(copy.dst_subresource.base_level), view_aspect);
|
||||
const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y};
|
||||
const VkExtent2D dst_extent{copy.extent.width, copy.extent.height};
|
||||
const VkRect2D render_area{
|
||||
@@ -949,50 +971,64 @@ void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_i
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, pipeline, layout, sampler, renderpass,
|
||||
framebuffer_handle = *framebuffer, src_view_handle = *src_view,
|
||||
src = src_image, dst = dst_image, render_area,
|
||||
src = src_image, dst = dst_image, render_area, is_depth, barrier_aspect,
|
||||
push_constants](vk::CommandBuffer cmdbuf) {
|
||||
constexpr VkImageSubresourceRange color_range{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
const VkImageSubresourceRange src_range{
|
||||
.aspectMask = barrier_aspect,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
};
|
||||
const VkImageSubresourceRange dst_range = src_range;
|
||||
const VkAccessFlags attachment_read =
|
||||
is_depth ? VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
|
||||
: VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
||||
const VkAccessFlags attachment_write =
|
||||
is_depth ? VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
|
||||
: VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
const VkPipelineStageFlags depth_stage =
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
const VkPipelineStageFlags attachment_stage =
|
||||
is_depth ? depth_stage
|
||||
: static_cast<VkPipelineStageFlags>(
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
|
||||
const std::array pre_barriers{
|
||||
VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.srcAccessMask = attachment_write | VK_ACCESS_SHADER_WRITE_BIT |
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = src,
|
||||
.subresourceRange = color_range,
|
||||
.subresourceRange = src_range,
|
||||
},
|
||||
VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.srcAccessMask = attachment_write | VK_ACCESS_SHADER_WRITE_BIT |
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = attachment_read | attachment_write,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst,
|
||||
.subresourceRange = color_range,
|
||||
.subresourceRange = dst_range,
|
||||
},
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | attachment_stage,
|
||||
0, nullptr, nullptr, pre_barriers);
|
||||
const VkRenderPassBeginInfo renderpass_bi{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
@@ -1025,16 +1061,16 @@ void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_i
|
||||
const VkImageMemoryBarrier post_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.srcAccessMask = attachment_write,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst,
|
||||
.subresourceRange = color_range,
|
||||
.subresourceRange = dst_range,
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
cmdbuf.PipelineBarrier(attachment_stage,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
@@ -1423,9 +1459,36 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
|
||||
return *msaa_copy_pipelines[std::distance(msaa_copy_keys.begin(), it)];
|
||||
}
|
||||
msaa_copy_keys.push_back(key);
|
||||
const std::array stages = MakeStages(*clear_color_vert, key.msaa_to_non_msaa
|
||||
? *convert_msaa_to_non_msaa_frag
|
||||
: *convert_non_msaa_to_msaa_frag);
|
||||
const VkShaderModule frag_module =
|
||||
key.msaa_to_non_msaa
|
||||
? *convert_msaa_to_non_msaa_frag
|
||||
: (key.is_depth ? *convert_non_msaa_to_msaa_depth_frag
|
||||
: *convert_non_msaa_to_msaa_frag);
|
||||
const std::array stages = MakeStages(*clear_color_vert, frag_module);
|
||||
const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.depthTestEnable = VK_TRUE,
|
||||
.depthWriteEnable = VK_TRUE,
|
||||
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
|
||||
.depthBoundsTestEnable = VK_FALSE,
|
||||
.stencilTestEnable = VK_FALSE,
|
||||
.front = {},
|
||||
.back = {},
|
||||
.minDepthBounds = 0.0f,
|
||||
.maxDepthBounds = 0.0f,
|
||||
};
|
||||
static constexpr VkPipelineColorBlendStateCreateInfo no_color_blend_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.logicOpEnable = VK_FALSE,
|
||||
.logicOp = VK_LOGIC_OP_CLEAR,
|
||||
.attachmentCount = 0,
|
||||
.pAttachments = nullptr,
|
||||
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
|
||||
};
|
||||
const VkPipelineMultisampleStateCreateInfo multisample_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -1450,8 +1513,9 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
|
||||
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.pMultisampleState = &multisample_ci,
|
||||
.pDepthStencilState = nullptr,
|
||||
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
|
||||
.pDepthStencilState = key.is_depth ? &depth_stencil_ci : nullptr,
|
||||
.pColorBlendState = key.is_depth ? &no_color_blend_ci
|
||||
: &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
|
||||
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.layout = *msaa_copy_pipeline_layout,
|
||||
.renderPass = key.renderpass,
|
||||
|
||||
@@ -51,6 +51,7 @@ struct MSAACopyPipelineKey {
|
||||
VkRenderPass renderpass;
|
||||
VkSampleCountFlagBits samples;
|
||||
bool msaa_to_non_msaa;
|
||||
bool is_depth;
|
||||
};
|
||||
|
||||
struct BlitMSAAPipelineKey {
|
||||
@@ -180,6 +181,7 @@ private:
|
||||
vk::ShaderModule convert_s8d24_to_abgr8_frag;
|
||||
vk::ShaderModule convert_msaa_to_non_msaa_frag;
|
||||
vk::ShaderModule convert_non_msaa_to_msaa_frag;
|
||||
vk::ShaderModule convert_non_msaa_to_msaa_depth_frag;
|
||||
vk::Sampler linear_sampler;
|
||||
vk::Sampler nearest_sampler;
|
||||
|
||||
|
||||
@@ -164,7 +164,9 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
}
|
||||
|
||||
provoking_vertex_last.Assign(use_last_provoking_vertex ? 1 : 0);
|
||||
conservative_raster_enable.Assign(regs.conservative_raster_enable != 0 ? 1 : 0);
|
||||
if (!features.has_dynamic_state3_conservative_raster_mode) {
|
||||
conservative_raster_enable.Assign(regs.conservative_raster_enable != 0 ? 1 : 0);
|
||||
}
|
||||
smooth_lines.Assign(regs.line_anti_alias_enable != 0 ? 1 : 0);
|
||||
alpha_to_coverage_enabled.Assign(regs.anti_alias_alpha_control.alpha_to_coverage != 0 ? 1 : 0);
|
||||
alpha_to_one_enabled.Assign(regs.anti_alias_alpha_control.alpha_to_one != 0 ? 1 : 0);
|
||||
@@ -360,18 +362,35 @@ void FixedPipelineState::DynamicState::Refresh2(const Maxwell& regs,
|
||||
depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
bool IsDepthClipEnabled(const Maxwell& regs) {
|
||||
const auto clip = regs.viewport_clip_control.geometry_clip.Value();
|
||||
return clip == Maxwell::ViewportClipControl::GeometryClip::Passthrough ||
|
||||
clip == Maxwell::ViewportClipControl::GeometryClip::FrustumXYZ ||
|
||||
clip == Maxwell::ViewportClipControl::GeometryClip::FrustumZ;
|
||||
}
|
||||
|
||||
bool IsDepthClampEnabled(const Maxwell& regs, bool has_depth_clip_enable) {
|
||||
if (!IsDepthClipEnabled(regs)) {
|
||||
return true;
|
||||
}
|
||||
if (!has_depth_clip_enable) {
|
||||
return false;
|
||||
}
|
||||
return regs.viewport_clip_control.pixel_min_z.Value() != 0 ||
|
||||
regs.viewport_clip_control.pixel_max_z.Value() != 0;
|
||||
}
|
||||
|
||||
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_depth_clip_enable) {
|
||||
depth_clip_disabled.Assign(IsDepthClipEnabled(regs) ? 0 : 1);
|
||||
}
|
||||
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);
|
||||
depth_clamp_disabled.Assign(
|
||||
IsDepthClampEnabled(regs, features.has_depth_clip_enable) ? 0 : 1);
|
||||
}
|
||||
if (!features.has_dynamic_state3_line_stipple_enable) {
|
||||
line_stipple_enable.Assign(regs.line_stipple_enable);
|
||||
|
||||
@@ -30,6 +30,8 @@ struct DynamicFeatures {
|
||||
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_conservative_raster_mode;
|
||||
bool has_depth_clip_enable;
|
||||
bool has_dynamic_state3_logic_op_enable;
|
||||
bool has_dynamic_state3_line_stipple_enable;
|
||||
bool has_dynamic_vertex_input;
|
||||
@@ -165,6 +167,7 @@ struct FixedPipelineState {
|
||||
BitField<10, 1, u32> logic_op_enable;
|
||||
BitField<11, 1, u32> depth_clamp_disabled;
|
||||
BitField<12, 1, u32> line_stipple_enable;
|
||||
BitField<13, 1, u32> depth_clip_disabled;
|
||||
};
|
||||
union {
|
||||
u32 raw2;
|
||||
@@ -298,6 +301,9 @@ static_assert(std::has_unique_object_representations_v<FixedPipelineState>);
|
||||
static_assert(std::is_trivially_copyable_v<FixedPipelineState>);
|
||||
static_assert(std::is_trivially_constructible_v<FixedPipelineState>);
|
||||
|
||||
bool IsDepthClipEnabled(const Maxwell& regs);
|
||||
bool IsDepthClampEnabled(const Maxwell& regs, bool has_depth_clip_enable);
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
namespace std {
|
||||
|
||||
@@ -246,7 +246,6 @@ protected:
|
||||
StagingBufferPool& staging_pool;
|
||||
|
||||
vk::Buffer buffer{};
|
||||
MemoryCommit memory_commit{};
|
||||
VkIndexType index_type{};
|
||||
u32 num_indices = 0;
|
||||
};
|
||||
@@ -376,6 +375,10 @@ u64 BufferCacheRuntime::GetDeviceMemoryUsage() const {
|
||||
return device.GetDeviceMemoryUsage();
|
||||
}
|
||||
|
||||
u64 BufferCacheRuntime::GetDeviceAllocationUsage() const {
|
||||
return device.GetMemoryBudgetInfo().allocation_bytes;
|
||||
}
|
||||
|
||||
bool BufferCacheRuntime::CanReportMemoryUsage() const {
|
||||
return device.CanReportMemoryUsage();
|
||||
}
|
||||
@@ -404,6 +407,16 @@ u64 BufferCacheRuntime::KnownGpuTick() {
|
||||
return scheduler.GetMasterSemaphore().KnownGpuTick();
|
||||
}
|
||||
|
||||
u64 BufferCacheRuntime::CurrentSyncPoint() const noexcept {
|
||||
return scheduler.GetMasterSemaphore().CurrentTick();
|
||||
}
|
||||
|
||||
u64 BufferCacheRuntime::CompletedSyncPoint() const {
|
||||
auto& master_semaphore = scheduler.GetMasterSemaphore();
|
||||
master_semaphore.Refresh();
|
||||
return master_semaphore.KnownGpuTick();
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::Wait(u64 buffer_tick) {
|
||||
scheduler.Wait(buffer_tick);
|
||||
}
|
||||
@@ -641,6 +654,7 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer,
|
||||
offset = 0;
|
||||
size = 0;
|
||||
}
|
||||
scheduler.MarkTransformFeedbackUsed();
|
||||
scheduler.Record([index, buffer, offset, size](vk::CommandBuffer cmdbuf) {
|
||||
const VkDeviceSize vk_offset = offset;
|
||||
const VkDeviceSize vk_size = size;
|
||||
@@ -653,19 +667,26 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<
|
||||
// Already logged in the rasterizer
|
||||
return;
|
||||
}
|
||||
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
|
||||
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
|
||||
const u32 count = std::min<u32>(static_cast<u32>(bindings.buffers.size()),
|
||||
VideoCommon::NUM_TRANSFORM_FEEDBACK_BUFFERS);
|
||||
std::array<VkBuffer, VideoCommon::NUM_TRANSFORM_FEEDBACK_BUFFERS> handles{};
|
||||
std::array<VkDeviceSize, VideoCommon::NUM_TRANSFORM_FEEDBACK_BUFFERS> offsets{};
|
||||
std::array<VkDeviceSize, VideoCommon::NUM_TRANSFORM_FEEDBACK_BUFFERS> sizes{};
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
auto handle = bindings.buffers[i]->Handle();
|
||||
if (handle == VK_NULL_HANDLE) {
|
||||
ReserveNullBuffer();
|
||||
handle = *null_buffer;
|
||||
bindings.offsets[i] = 0;
|
||||
bindings.sizes[i] = 0;
|
||||
} else {
|
||||
offsets[i] = bindings.offsets[i];
|
||||
sizes[i] = bindings.sizes[i];
|
||||
}
|
||||
buffer_handles[i] = handle;
|
||||
handles[i] = handle;
|
||||
}
|
||||
scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindTransformFeedbackBuffersEXT(0, u32(buffer_handles_.size()), buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data());
|
||||
scheduler.MarkTransformFeedbackUsed();
|
||||
scheduler.Record([count, handles, offsets, sizes](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindTransformFeedbackBuffersEXT(0, count, handles.data(), offsets.data(),
|
||||
sizes.data());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -100,10 +100,20 @@ public:
|
||||
|
||||
void Finish();
|
||||
|
||||
u64 CurrentSyncPoint() const noexcept;
|
||||
|
||||
u64 CompletedSyncPoint() const;
|
||||
|
||||
u64 GetDeviceLocalMemory() const;
|
||||
|
||||
u64 GetDeviceMemoryUsage() const;
|
||||
|
||||
u64 GetDeviceAllocationUsage() const;
|
||||
|
||||
bool CanReportAllocationUsage() const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CanReportMemoryUsage() const;
|
||||
|
||||
u32 GetUniformBufferAlignment() const;
|
||||
|
||||
@@ -37,7 +37,7 @@ void InnerFence::Wait() {
|
||||
if (is_stubbed) {
|
||||
return;
|
||||
}
|
||||
scheduler.Wait(wait_tick);
|
||||
scheduler.WaitSubmitted(wait_tick);
|
||||
}
|
||||
|
||||
FenceManager::FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
|
||||
|
||||
@@ -757,16 +757,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.lineWidth = 1.0f,
|
||||
// TODO(alekpop): Transfer from regs
|
||||
};
|
||||
const bool smooth_lines_supported =
|
||||
device.IsExtLineRasterizationSupported() && device.SupportsSmoothLines();
|
||||
const bool stippled_lines_supported =
|
||||
device.IsExtLineRasterizationSupported() && device.SupportsStippledRectangularLines();
|
||||
const VkLineRasterizationModeEXT line_raster_mode =
|
||||
device.GetLineRasterizationMode(key.state.smooth_lines != 0);
|
||||
const bool stippled_lines_supported = device.SupportsStippleForMode(line_raster_mode);
|
||||
VkPipelineRasterizationLineStateCreateInfoEXT line_state{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.lineRasterizationMode = key.state.smooth_lines != 0 && smooth_lines_supported
|
||||
? VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT
|
||||
: VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT,
|
||||
.lineRasterizationMode = line_raster_mode,
|
||||
.stippledLineEnable =
|
||||
(dynamic.line_stipple_enable && stippled_lines_supported) ? VK_TRUE : VK_FALSE,
|
||||
.lineStippleFactor = key.state.line_stipple_factor,
|
||||
@@ -805,6 +802,16 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
if (device.IsExtProvokingVertexSupported()) {
|
||||
provoking_vertex.pNext = std::exchange(rasterization_ci.pNext, &provoking_vertex);
|
||||
}
|
||||
VkPipelineRasterizationDepthClipStateCreateInfoEXT depth_clip_state{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.depthClipEnable = static_cast<VkBool32>(dynamic.depth_clip_disabled == 0 ? VK_TRUE
|
||||
: VK_FALSE),
|
||||
};
|
||||
if (device.IsExtDepthClipEnableSupported()) {
|
||||
depth_clip_state.pNext = std::exchange(rasterization_ci.pNext, &depth_clip_state);
|
||||
}
|
||||
|
||||
const bool supports_alpha_output = fragment_has_color0_output;
|
||||
const bool alpha_to_one_supported = device.SupportsAlphaToOne();
|
||||
|
||||
@@ -305,7 +305,7 @@ size_t GetTotalPipelineWorkers() {
|
||||
std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL;
|
||||
#ifdef __ANDROID__
|
||||
const int configured = AndroidSettings::values.pipeline_worker_count.GetValue();
|
||||
const int clamped = std::clamp(configured, 4, 8);
|
||||
const int clamped = std::clamp(configured, 2, 8);
|
||||
const size_t desired = static_cast<size_t>(clamped);
|
||||
if (desired == 0) {
|
||||
return 1ULL;
|
||||
@@ -349,8 +349,9 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
|
||||
use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()},
|
||||
workers(device.HasBrokenParallelShaderCompiling() ? 1ULL : GetTotalPipelineWorkers(),
|
||||
"VkPipelineBuilder"),
|
||||
serialization_thread(1, "VkPipelineSerialization") {
|
||||
"VkPipelineBuilder", {}, Common::ThreadPlacement::Background),
|
||||
serialization_thread(1, "VkPipelineSerialization", {},
|
||||
Common::ThreadPlacement::Background) {
|
||||
const auto& float_control{device.FloatControlProperties()};
|
||||
const VkDriverId driver_id{device.GetDriverID()};
|
||||
const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()};
|
||||
@@ -514,6 +515,11 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
dynamic_features.has_dynamic_state3_depth_clamp_enable =
|
||||
dynamic_features.has_extended_dynamic_state_3_enables &&
|
||||
device.SupportsDynamicState3DepthClampEnable();
|
||||
dynamic_features.has_dynamic_state3_conservative_raster_mode =
|
||||
dynamic_features.has_extended_dynamic_state_3_enables &&
|
||||
device.SupportsDynamicState3ConservativeRasterizationMode();
|
||||
dynamic_features.has_depth_clip_enable =
|
||||
device.IsExtDepthClipEnableSupported();
|
||||
dynamic_features.has_dynamic_state3_logic_op_enable =
|
||||
dynamic_features.has_extended_dynamic_state_3_enables &&
|
||||
device.SupportsDynamicState3LogicOpEnable();
|
||||
@@ -526,7 +532,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
device.IsExtVertexInputDynamicStateSupported() &&
|
||||
Settings::values.vertex_input_dynamic_state.GetValue();
|
||||
|
||||
dynamic_features.has_provoking_vertex = device.IsExtProvokingVertexSupported();
|
||||
dynamic_features.has_provoking_vertex =
|
||||
device.IsExtProvokingVertexSupported();
|
||||
dynamic_features.has_provoking_vertex_first_mode =
|
||||
device.SupportsProvokingVertexFirstMode();
|
||||
dynamic_features.has_provoking_vertex_last_mode =
|
||||
|
||||
@@ -296,9 +296,6 @@ void PresentManager::RecreateSwapchain(Frame* frame) {
|
||||
}
|
||||
|
||||
void PresentManager::SetImageCount() {
|
||||
// We cannot have more than 7 images in flight at any given time.
|
||||
// FRAMES_IN_FLIGHT is 8, and the cache TICKS_TO_DESTROY is 8.
|
||||
// Mali drivers will give us 6.
|
||||
image_count = std::min<size_t>(swapchain.GetImageCount(), 7);
|
||||
}
|
||||
|
||||
|
||||
@@ -919,7 +919,7 @@ private:
|
||||
return;
|
||||
}
|
||||
has_flushed_end_pending = true;
|
||||
// Refresh buffers state before beginning transform feedback so counters are up-to-date
|
||||
scheduler.MarkTransformFeedbackUsed();
|
||||
UpdateBuffers();
|
||||
if (!has_started || buffers_count == 0) {
|
||||
// No counter buffers available: begin without counters
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
@@ -203,7 +204,8 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
|
||||
: gpu{gpu_}, device_memory{device_memory_}, device{device_},
|
||||
memory_allocator{memory_allocator_}, state_tracker{state_tracker_}, scheduler{scheduler_},
|
||||
staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler),
|
||||
guest_descriptor_queue(device), compute_pass_descriptor_queue(device),
|
||||
guest_descriptor_queue(device, UpdateDescriptorQueue::GUEST_FRAME_PAYLOAD_SIZE),
|
||||
compute_pass_descriptor_queue(device, UpdateDescriptorQueue::COMPUTE_FRAME_PAYLOAD_SIZE),
|
||||
blit_image(device, scheduler, state_tracker, descriptor_pool), render_pass_cache(device),
|
||||
texture_cache_runtime{
|
||||
device, scheduler, memory_allocator, staging_pool,
|
||||
@@ -221,9 +223,23 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
|
||||
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
|
||||
wfi_event(device.GetLogical().CreateEvent()) {
|
||||
scheduler.SetQueryCache(query_cache);
|
||||
memory_allocator.SetReclaimCallback([this](u64 bytes) -> u64 {
|
||||
u64 freed = staging_pool.ReclaimMemory(bytes);
|
||||
if (freed < bytes) {
|
||||
freed += texture_cache.ReclaimMemory(bytes - freed, false);
|
||||
}
|
||||
if (freed < bytes) {
|
||||
freed += buffer_cache.ReclaimMemory(bytes - freed, false);
|
||||
}
|
||||
auto& master_semaphore = scheduler.GetMasterSemaphore();
|
||||
master_semaphore.Refresh();
|
||||
vk::TickDeletionQueue(master_semaphore.KnownGpuTick());
|
||||
return freed;
|
||||
});
|
||||
}
|
||||
|
||||
RasterizerVulkan::~RasterizerVulkan() {
|
||||
memory_allocator.SetReclaimCallback(nullptr);
|
||||
scheduler.WaitWorker();
|
||||
scheduler.Finish();
|
||||
}
|
||||
@@ -880,6 +896,9 @@ void RasterizerVulkan::FlushCommands() {
|
||||
|
||||
void RasterizerVulkan::TickFrame() {
|
||||
draw_counter = 0;
|
||||
auto& master_semaphore = scheduler.GetMasterSemaphore();
|
||||
master_semaphore.Refresh();
|
||||
vk::TickDeletionQueue(master_semaphore.KnownGpuTick());
|
||||
guest_descriptor_queue.TickFrame();
|
||||
compute_pass_descriptor_queue.TickFrame();
|
||||
fence_manager.TickFrame();
|
||||
@@ -1451,7 +1470,10 @@ void RasterizerVulkan::UpdateLineWidth(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
}
|
||||
const float width =
|
||||
regs.line_anti_alias_enable ? regs.line_width_smooth : regs.line_width_aliased;
|
||||
scheduler.Record([width](vk::CommandBuffer cmdbuf) { cmdbuf.SetLineWidth(width); });
|
||||
const float clamped_width = device.ClampLineWidth(width);
|
||||
scheduler.Record([clamped_width](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetLineWidth(clamped_width);
|
||||
});
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
@@ -1548,7 +1570,10 @@ void RasterizerVulkan::UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs&
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.Record([enable = regs.line_stipple_enable](vk::CommandBuffer cmdbuf) {
|
||||
const VkLineRasterizationModeEXT mode =
|
||||
device.GetLineRasterizationMode(regs.line_anti_alias_enable != 0);
|
||||
const bool enable = regs.line_stipple_enable != 0 && device.SupportsStippleForMode(mode);
|
||||
scheduler.Record([enable](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetLineStippleEnableEXT(enable);
|
||||
});
|
||||
}
|
||||
@@ -1562,28 +1587,24 @@ void RasterizerVulkan::UpdateLineRasterizationMode(Tegra::Engines::Maxwell3D::Re
|
||||
}
|
||||
|
||||
if (!device.SupportsDynamicState3LineRasterizationMode()) {
|
||||
static std::once_flag warn_missing_rect;
|
||||
std::call_once(warn_missing_rect, [] {
|
||||
static std::once_flag warn_missing_dynamic_state;
|
||||
std::call_once(warn_missing_dynamic_state, [] {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Driver lacks rectangular line rasterization support; skipping dynamic "
|
||||
"line state updates");
|
||||
"Driver lacks dynamic line rasterization mode; the pipeline static value "
|
||||
"is used instead");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const bool wants_smooth = regs.line_anti_alias_enable != 0;
|
||||
VkLineRasterizationModeEXT mode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT;
|
||||
if (wants_smooth) {
|
||||
if (device.SupportsSmoothLines()) {
|
||||
mode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT;
|
||||
} else {
|
||||
static std::once_flag warn_missing_smooth;
|
||||
std::call_once(warn_missing_smooth, [] {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Line anti-aliasing requested but smoothLines feature unavailable; "
|
||||
"using rectangular rasterization");
|
||||
});
|
||||
}
|
||||
const VkLineRasterizationModeEXT mode = device.GetLineRasterizationMode(wants_smooth);
|
||||
if (wants_smooth && mode != VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT) {
|
||||
static std::once_flag warn_missing_smooth;
|
||||
std::call_once(warn_missing_smooth, [] {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Line anti-aliasing requested but smoothLines feature unavailable; "
|
||||
"falling back to the closest supported mode");
|
||||
});
|
||||
}
|
||||
scheduler.Record([mode](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetLineRasterizationModeEXT(mode);
|
||||
@@ -1643,12 +1664,7 @@ void RasterizerVulkan::UpdateDepthClampEnable(Tegra::Engines::Maxwell3D::Regs& r
|
||||
if (!device.SupportsDynamicState3DepthClampEnable()) {
|
||||
return;
|
||||
}
|
||||
bool is_enabled = !(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);
|
||||
const bool is_enabled = IsDepthClampEnabled(regs, device.IsExtDepthClipEnableSupported());
|
||||
scheduler.Record(
|
||||
[is_enabled](vk::CommandBuffer cmdbuf) { cmdbuf.SetDepthClampEnableEXT(is_enabled); });
|
||||
}
|
||||
|
||||
@@ -179,7 +179,6 @@ private:
|
||||
void UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateConservativeRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLineStipple(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLineRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateDepthBiasEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLogicOpEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
|
||||
@@ -47,6 +47,7 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
|
||||
master_semaphore{std::make_unique<MasterSemaphore>(device)},
|
||||
command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} {
|
||||
|
||||
vk::SetDeletionTimeline(master_semaphore->CurrentTick());
|
||||
AcquireNewChunk();
|
||||
AllocateWorkerCommandBuffer();
|
||||
worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); });
|
||||
@@ -322,6 +323,7 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
|
||||
InvalidateState();
|
||||
|
||||
const u64 signal_value = master_semaphore->NextTick();
|
||||
vk::SetDeletionTimeline(master_semaphore->CurrentTick());
|
||||
RecordWithUploadBuffer([signal_semaphore, wait_semaphore, signal_value,
|
||||
this](vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) {
|
||||
static constexpr VkMemoryBarrier WRITE_BARRIER{
|
||||
@@ -398,7 +400,7 @@ void Scheduler::EndRenderPass()
|
||||
Record([num_images = num_renderpass_images,
|
||||
images = renderpass_images,
|
||||
ranges = renderpass_image_ranges,
|
||||
has_transform_feedback = device.IsExtTransformFeedbackSupported()](
|
||||
has_transform_feedback = state.uses_transform_feedback](
|
||||
vk::CommandBuffer cmdbuf) {
|
||||
std::array<VkImageMemoryBarrier, 9> barriers;
|
||||
for (size_t i = 0; i < num_images; ++i) {
|
||||
@@ -453,6 +455,7 @@ void Scheduler::EndRenderPass()
|
||||
});
|
||||
|
||||
state.renderpass = VkRenderPass{};
|
||||
state.uses_transform_feedback = false;
|
||||
num_renderpass_images = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ public:
|
||||
return state.renderpass != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
/// Flags that transform feedback writes have been recorded since the last render pass end.
|
||||
void MarkTransformFeedbackUsed() noexcept {
|
||||
state.uses_transform_feedback = true;
|
||||
}
|
||||
|
||||
/// Update the pipeline to the current execution context.
|
||||
bool UpdateGraphicsPipeline(GraphicsPipeline* pipeline);
|
||||
|
||||
@@ -131,33 +136,16 @@ public:
|
||||
}
|
||||
master_semaphore->Wait(tick);
|
||||
}
|
||||
if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (last_target_fps != target_fps) {
|
||||
frame_interval = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(1.0 / target_fps));
|
||||
max_frame_count = static_cast<int>(0.1 * target_fps);
|
||||
last_target_fps = target_fps;
|
||||
frame_counter = 0;
|
||||
start_time = now;
|
||||
}
|
||||
frame_counter++;
|
||||
auto target_time = start_time + frame_interval * frame_counter;
|
||||
if (target_time >= now) {
|
||||
auto sleep_time = target_time - now;
|
||||
if (sleep_time > std::chrono::milliseconds(15)) {
|
||||
std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1));
|
||||
}
|
||||
while (std::chrono::steady_clock::now() < target_time) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
} else if (frame_counter > max_frame_count) {
|
||||
frame_counter = 0;
|
||||
start_time = now;
|
||||
}
|
||||
}
|
||||
ApplyFramePacing(target_fps);
|
||||
}
|
||||
|
||||
void WaitSubmitted(u64 tick, double target_fps = 0.0) {
|
||||
if (tick > 0 && tick < master_semaphore->CurrentTick()) {
|
||||
master_semaphore->Wait(tick);
|
||||
}
|
||||
ApplyFramePacing(target_fps);
|
||||
}
|
||||
|
||||
/// Returns the master timeline semaphore.
|
||||
[[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept {
|
||||
return *master_semaphore;
|
||||
}
|
||||
@@ -165,6 +153,35 @@ public:
|
||||
std::mutex submit_mutex;
|
||||
|
||||
private:
|
||||
void ApplyFramePacing(double target_fps) {
|
||||
if (!Settings::values.use_speed_limit.GetValue() || target_fps <= 0.0) {
|
||||
return;
|
||||
}
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (last_target_fps != target_fps) {
|
||||
frame_interval = std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
||||
std::chrono::duration<double>(1.0 / target_fps));
|
||||
max_frame_count = static_cast<int>(0.1 * target_fps);
|
||||
last_target_fps = target_fps;
|
||||
frame_counter = 0;
|
||||
start_time = now;
|
||||
}
|
||||
frame_counter++;
|
||||
auto target_time = start_time + frame_interval * frame_counter;
|
||||
if (target_time >= now) {
|
||||
auto sleep_time = target_time - now;
|
||||
if (sleep_time > std::chrono::milliseconds(15)) {
|
||||
std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1));
|
||||
}
|
||||
while (std::chrono::steady_clock::now() < target_time) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
} else if (frame_counter > max_frame_count) {
|
||||
frame_counter = 0;
|
||||
start_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
class Command {
|
||||
public:
|
||||
virtual ~Command() = default;
|
||||
@@ -255,6 +272,7 @@ private:
|
||||
bool is_rescaling = false;
|
||||
bool rescaling_defined = false;
|
||||
bool needs_state_enable_refresh = false;
|
||||
bool uses_transform_feedback = false;
|
||||
};
|
||||
|
||||
struct DeferredClear {
|
||||
|
||||
@@ -252,25 +252,62 @@ void StagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, size_t log2) {
|
||||
constexpr size_t deletions_per_tick = 16;
|
||||
auto& staging = cache[log2];
|
||||
auto& entries = staging.entries;
|
||||
const size_t old_size = entries.size();
|
||||
if (entries.empty()) {
|
||||
staging.delete_index = 0;
|
||||
staging.iterate_index = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto is_deletable = [this](const StagingBuffer& entry) {
|
||||
return scheduler.IsFree(entry.tick);
|
||||
};
|
||||
const size_t begin_offset = staging.delete_index;
|
||||
const size_t end_offset = (std::min)(begin_offset + deletions_per_tick, old_size);
|
||||
const size_t begin_offset = (std::min)(staging.delete_index, entries.size());
|
||||
const size_t end_offset = (std::min)(begin_offset + deletions_per_tick, entries.size());
|
||||
const auto begin = entries.begin() + begin_offset;
|
||||
const auto end = entries.begin() + end_offset;
|
||||
entries.erase(std::remove_if(begin, end, is_deletable), end);
|
||||
const auto surviving_end = std::remove_if(begin, end, is_deletable);
|
||||
const size_t removed = static_cast<size_t>(std::distance(surviving_end, end));
|
||||
entries.erase(surviving_end, end);
|
||||
|
||||
const size_t new_size = entries.size();
|
||||
staging.delete_index += deletions_per_tick;
|
||||
if (staging.delete_index >= new_size) {
|
||||
staging.delete_index = end_offset - removed;
|
||||
if (staging.delete_index >= entries.size()) {
|
||||
staging.delete_index = 0;
|
||||
}
|
||||
if (staging.iterate_index > new_size) {
|
||||
if (staging.iterate_index > entries.size()) {
|
||||
staging.iterate_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u64 StagingBufferPool::ReclaimMemory(u64 target_bytes) {
|
||||
u64 freed = 0;
|
||||
const auto is_deletable = [this](const StagingBuffer& entry) {
|
||||
return scheduler.IsFree(entry.tick);
|
||||
};
|
||||
const auto reclaim_cache = [&](StagingBuffersCache& cache) {
|
||||
for (size_t level = NUM_LEVELS; level-- > 0 && freed < target_bytes;) {
|
||||
auto& staging = cache[level];
|
||||
auto& entries = staging.entries;
|
||||
if (entries.empty()) {
|
||||
continue;
|
||||
}
|
||||
const u64 entry_bytes = 1ULL << level;
|
||||
auto it = entries.begin();
|
||||
while (it != entries.end() && freed < target_bytes) {
|
||||
if (is_deletable(*it)) {
|
||||
it = entries.erase(it);
|
||||
freed += entry_bytes;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
staging.delete_index = 0;
|
||||
staging.iterate_index = 0;
|
||||
}
|
||||
};
|
||||
reclaim_cache(device_local_cache);
|
||||
reclaim_cache(upload_cache);
|
||||
reclaim_cache(download_cache);
|
||||
return freed;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -42,6 +45,8 @@ public:
|
||||
|
||||
void TickFrame();
|
||||
|
||||
u64 ReclaimMemory(u64 target_bytes);
|
||||
|
||||
private:
|
||||
struct StreamBufferCommit {
|
||||
size_t upper_bound;
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/api-level.h>
|
||||
#endif
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
@@ -176,34 +172,26 @@ bool Swapchain::AcquireNextImage() {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto wait_with_frame_pacing = [this] {
|
||||
#ifdef __ANDROID__
|
||||
scheduler.WaitSubmitted(resource_ticks[image_index]);
|
||||
#else
|
||||
switch (Settings::values.frame_pacing_mode.GetValue()) {
|
||||
case Settings::FramePacingMode::Target_Auto:
|
||||
scheduler.Wait(resource_ticks[image_index]);
|
||||
scheduler.WaitSubmitted(resource_ticks[image_index]);
|
||||
break;
|
||||
case Settings::FramePacingMode::Target_30:
|
||||
scheduler.Wait(resource_ticks[image_index], 30.0);
|
||||
scheduler.WaitSubmitted(resource_ticks[image_index], 30.0);
|
||||
break;
|
||||
case Settings::FramePacingMode::Target_60:
|
||||
scheduler.Wait(resource_ticks[image_index], 60.0);
|
||||
scheduler.WaitSubmitted(resource_ticks[image_index], 60.0);
|
||||
break;
|
||||
case Settings::FramePacingMode::Target_90:
|
||||
scheduler.Wait(resource_ticks[image_index], 90.0);
|
||||
scheduler.WaitSubmitted(resource_ticks[image_index], 90.0);
|
||||
break;
|
||||
case Settings::FramePacingMode::Target_120:
|
||||
scheduler.Wait(resource_ticks[image_index], 120.0);
|
||||
scheduler.WaitSubmitted(resource_ticks[image_index], 120.0);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (android_get_device_api_level() >= 30) {
|
||||
scheduler.Wait(resource_ticks[image_index]);
|
||||
} else {
|
||||
wait_with_frame_pacing();
|
||||
}
|
||||
#else
|
||||
wait_with_frame_pacing();
|
||||
#endif
|
||||
|
||||
resource_ticks[image_index] = scheduler.CurrentTick();
|
||||
|
||||
@@ -144,11 +144,6 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
info.size.depth == 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool WillUseWidenedAstcFormat(const Device& device, const ImageInfo& info) {
|
||||
return WillUseAcceleratedAstcDecode(device, info) &&
|
||||
!VideoCore::Surface::IsPixelFormatSRGB(info.format);
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info,
|
||||
std::optional<VkFormat> format_override = {}) {
|
||||
auto format_info =
|
||||
@@ -212,7 +207,11 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
return device.IsFormatSupported(view_format, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT,
|
||||
FormatType::Optimal);
|
||||
});
|
||||
if (has_storage_compatible_view) {
|
||||
const bool storage_allowed_for_samples =
|
||||
image_ci.samples == VK_SAMPLE_COUNT_1_BIT ||
|
||||
(device.GetStorageImageSampleCounts() &
|
||||
static_cast<VkSampleCountFlags>(image_ci.samples)) != 0;
|
||||
if (has_storage_compatible_view && storage_allowed_for_samples) {
|
||||
image_ci.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
}
|
||||
|
||||
@@ -896,6 +895,15 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
||||
0, nullptr, nullptr, write_barriers);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CanBlitNatively(const Device& device, PixelFormat format) {
|
||||
static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal;
|
||||
static constexpr VkFormatFeatureFlags BLIT_USAGE =
|
||||
VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
|
||||
const VkFormat vk_format =
|
||||
MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, format).format;
|
||||
return device.IsFormatSupported(vk_format, BLIT_USAGE, OPTIMAL_FORMAT);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& scheduler_,
|
||||
@@ -1228,27 +1236,19 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
|
||||
blit_image_helper.ResolveDepthStencil(dst_framebuffer, src, dst_region, src_region);
|
||||
return;
|
||||
}
|
||||
if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
|
||||
const auto format = src.format;
|
||||
const auto can_blit_depth_stencil = [this, format] {
|
||||
switch (format) {
|
||||
case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
|
||||
case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
|
||||
return device.IsBlitDepth24Stencil8Supported();
|
||||
case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
|
||||
return device.IsBlitDepth32Stencil8Supported();
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}();
|
||||
// Use shader-based depth/stencil blits if hardware doesn't support the format
|
||||
// Note: MSAA resolves (MSAA->single) use vkCmdResolveImage which works fine
|
||||
if (!can_blit_depth_stencil) {
|
||||
UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
|
||||
blit_image_helper.BlitDepthStencil(dst_framebuffer, src, dst_region, src_region,
|
||||
filter, operation);
|
||||
static constexpr VkImageAspectFlags DEPTH_STENCIL_ASPECTS =
|
||||
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
if ((aspect_mask & DEPTH_STENCIL_ASPECTS) != 0 && !CanBlitNatively(device, src.format)) {
|
||||
if (aspect_mask != DEPTH_STENCIL_ASPECTS) {
|
||||
UNIMPLEMENTED_MSG("Host cannot blit format {} and no helper path exists for aspect "
|
||||
"mask 0x{:x}",
|
||||
src.format, aspect_mask);
|
||||
return;
|
||||
}
|
||||
UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
|
||||
blit_image_helper.BlitDepthStencil(dst_framebuffer, src, dst_region, src_region, filter,
|
||||
operation);
|
||||
return;
|
||||
}
|
||||
ASSERT(!(is_dst_msaa && !is_src_msaa));
|
||||
ASSERT(operation == Fermi2D::Operation::SrcCopy);
|
||||
@@ -1643,7 +1643,14 @@ void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
|
||||
const u32 num_samples = msaa_to_non_msaa ? src.info.num_samples : dst.info.num_samples;
|
||||
if (dst.AspectMask() != VK_IMAGE_ASPECT_COLOR_BIT ||
|
||||
VideoCore::Surface::IsPixelFormatInteger(dst.info.format)) {
|
||||
UNIMPLEMENTED_MSG("Copying images with different samples is not supported.");
|
||||
const u64 key{(static_cast<u64>(dst.AspectMask()) << 32) |
|
||||
static_cast<u64>(dst.info.format)};
|
||||
if (unsupported_msaa_resolves.insert(key).second) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"MSAA resolve unsupported: format={}, aspect={:#x}, samples {}->{}",
|
||||
dst.info.format, dst.AspectMask(), src.info.num_samples,
|
||||
dst.info.num_samples);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ENABLE_MSAA_RESOLVE_CONSUME && msaa_to_non_msaa && copies.size() == 1 &&
|
||||
@@ -1753,6 +1760,20 @@ void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
|
||||
src.info.format, num_samples, copies, msaa_to_non_msaa);
|
||||
}
|
||||
|
||||
u64 TextureCacheRuntime::CurrentSyncPoint() const noexcept {
|
||||
return scheduler.CurrentTick();
|
||||
}
|
||||
|
||||
u64 TextureCacheRuntime::CompletedSyncPoint() const {
|
||||
auto& master_semaphore = scheduler.GetMasterSemaphore();
|
||||
master_semaphore.Refresh();
|
||||
return master_semaphore.KnownGpuTick();
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::WaitSyncPoint(u64 sync_point) {
|
||||
scheduler.Wait(sync_point);
|
||||
}
|
||||
|
||||
u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
|
||||
return device.GetDeviceLocalMemory();
|
||||
}
|
||||
@@ -1761,6 +1782,10 @@ u64 TextureCacheRuntime::GetDeviceMemoryUsage() const {
|
||||
return device.GetDeviceMemoryUsage();
|
||||
}
|
||||
|
||||
u64 TextureCacheRuntime::GetDeviceAllocationUsage() const {
|
||||
return device.GetMemoryBudgetInfo().allocation_bytes;
|
||||
}
|
||||
|
||||
bool TextureCacheRuntime::CanReportMemoryUsage() const {
|
||||
return device.CanReportMemoryUsage();
|
||||
}
|
||||
@@ -1770,6 +1795,7 @@ std::optional<size_t> TextureCacheRuntime::GetSamplerHeapBudget() const {
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::TickFrame() {
|
||||
device.TickAllocatorFrame();
|
||||
std::erase_if(pending_msaa_images, [this](const auto& pending) {
|
||||
return scheduler.IsFree(pending.first);
|
||||
});
|
||||
@@ -1780,12 +1806,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
||||
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
|
||||
runtime{&runtime_},
|
||||
original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
|
||||
WillUseWidenedAstcFormat(runtime_.device, info)
|
||||
? std::span<const VkFormat>{}
|
||||
: runtime->ViewFormats(info.format),
|
||||
WillUseWidenedAstcFormat(runtime_.device, info)
|
||||
? std::make_optional(VK_FORMAT_R32G32B32A32_SFLOAT)
|
||||
: std::nullopt)),
|
||||
runtime->ViewFormats(info.format))),
|
||||
aspect_mask(ImageAspectMask(info.format)) {
|
||||
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
|
||||
switch (Settings::values.accelerate_astc.GetValue()) {
|
||||
@@ -1816,9 +1837,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
||||
Settings::values.astc_recompression.GetValue() ==
|
||||
Settings::AstcRecompression::Uncompressed) {
|
||||
const auto& device = runtime->device.GetLogical();
|
||||
const VkFormat storage_format = WillUseWidenedAstcFormat(runtime->device, info)
|
||||
? VK_FORMAT_R32G32B32A32_SFLOAT
|
||||
: VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
const VkFormat storage_format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
for (s32 level = 0; level < info.resources.levels; ++level) {
|
||||
storage_image_views[level] =
|
||||
MakeStorageView(device, level, *original_image, storage_format);
|
||||
@@ -1892,9 +1911,11 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
|
||||
ScaleDown(true);
|
||||
}
|
||||
|
||||
const bool wants_msaa_upload = info.num_samples > 1
|
||||
&& (aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0
|
||||
const bool is_color_upload = (aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0
|
||||
&& !VideoCore::Surface::IsPixelFormatInteger(info.format);
|
||||
const bool is_depth_upload = (aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
|
||||
const bool wants_msaa_upload =
|
||||
info.num_samples > 1 && (is_color_upload || is_depth_upload);
|
||||
|
||||
if (wants_msaa_upload) {
|
||||
ImageInfo temp_info = info;
|
||||
@@ -1933,10 +1954,10 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
|
||||
image_copies.push_back(image_copy);
|
||||
}
|
||||
|
||||
runtime->TransitionImageLayout(*this);
|
||||
runtime->blit_image_helper.CopyMSAA(runtime->render_pass_cache, Handle(), info.format,
|
||||
temp_vk_image, info.format, info.num_samples,
|
||||
image_copies, false);
|
||||
initialized = true;
|
||||
runtime->pending_msaa_images.emplace_back(scheduler->CurrentTick(), std::move(temp_image));
|
||||
|
||||
if (is_rescaled) {
|
||||
@@ -1947,6 +1968,9 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
|
||||
|
||||
if (info.num_samples > 1) {
|
||||
LOG_WARNING(Render_Vulkan, "MSAA upload not implemented for format {}", info.format);
|
||||
if (runtime != nullptr) {
|
||||
runtime->TransitionImageLayout(*this);
|
||||
}
|
||||
if (is_rescaled) {
|
||||
ScaleUp();
|
||||
}
|
||||
@@ -2204,9 +2228,7 @@ VkImageView Image::StorageImageView(s32 level) noexcept {
|
||||
auto format_info =
|
||||
MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
|
||||
if (WillUseAcceleratedAstcDecode(runtime->device, info)) {
|
||||
format_info.format = WillUseWidenedAstcFormat(runtime->device, info)
|
||||
? VK_FORMAT_R32G32B32A32_SFLOAT
|
||||
: VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
format_info.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
}
|
||||
view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image),
|
||||
format_info.format);
|
||||
@@ -2382,11 +2404,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
|
||||
SanitizeDepthStencilSwizzle(swizzle, device->SupportsDepthStencilSwizzleOne());
|
||||
}
|
||||
}
|
||||
uses_widened_astc_format = WillUseWidenedAstcFormat(*device, image.info);
|
||||
auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
|
||||
if (uses_widened_astc_format) {
|
||||
format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
}
|
||||
if (device->ApiVersion() >= VK_API_VERSION_1_3) {
|
||||
const VkFormatProperties3 properties3 =
|
||||
device->GetPhysical().GetFormatProperties3(format_info.format);
|
||||
@@ -2529,14 +2547,12 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type,
|
||||
Shader::ImageFormat image_format) {
|
||||
if (image_handle) {
|
||||
if (image_format == Shader::ImageFormat::Typeless) {
|
||||
if (!typeless_storage_view) {
|
||||
auto& view{typeless_storage_views[static_cast<size_t>(texture_type)]};
|
||||
if (!view) {
|
||||
auto info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
|
||||
if (uses_widened_astc_format) {
|
||||
info.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
}
|
||||
typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT, texture_type);
|
||||
view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT, texture_type);
|
||||
}
|
||||
return *typeless_storage_view;
|
||||
return *view;
|
||||
}
|
||||
const bool is_signed = image_format == Shader::ImageFormat::R8_SINT
|
||||
|| image_format == Shader::ImageFormat::R16_SINT;
|
||||
|
||||
@@ -60,10 +60,22 @@ public:
|
||||
|
||||
void TickFrame();
|
||||
|
||||
u64 CurrentSyncPoint() const noexcept;
|
||||
|
||||
u64 CompletedSyncPoint() const;
|
||||
|
||||
void WaitSyncPoint(u64 sync_point);
|
||||
|
||||
u64 GetDeviceLocalMemory() const;
|
||||
|
||||
u64 GetDeviceMemoryUsage() const;
|
||||
|
||||
u64 GetDeviceAllocationUsage() const;
|
||||
|
||||
bool CanReportAllocationUsage() const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CanReportMemoryUsage() const;
|
||||
|
||||
std::optional<size_t> GetSamplerHeapBudget() const;
|
||||
@@ -156,6 +168,7 @@ public:
|
||||
std::array<vk::Buffer, indexing_slots> buffers{};
|
||||
std::vector<std::pair<u64, vk::Image>> pending_msaa_images;
|
||||
ankerl::unordered_dense::map<VkImage, ResolveShadow> resolve_shadows;
|
||||
ankerl::unordered_dense::set<u64> unsupported_msaa_resolves;
|
||||
};
|
||||
|
||||
class Framebuffer {
|
||||
@@ -426,7 +439,7 @@ private:
|
||||
|
||||
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> image_views;
|
||||
std::optional<StorageViews> storage_views;
|
||||
vk::ImageView typeless_storage_view;
|
||||
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> typeless_storage_views;
|
||||
vk::ImageView depth_view;
|
||||
vk::ImageView stencil_view;
|
||||
vk::ImageView color_view;
|
||||
@@ -436,7 +449,6 @@ private:
|
||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
u32 buffer_size = 0;
|
||||
|
||||
bool uses_widened_astc_format = false;
|
||||
bool supports_depth_comparison = false;
|
||||
};
|
||||
|
||||
@@ -487,6 +499,7 @@ struct TextureCacheParams {
|
||||
static constexpr bool HAS_EMULATED_COPIES = false;
|
||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
||||
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
|
||||
static constexpr bool HAS_TIMELINE_SYNC_POINTS = true;
|
||||
|
||||
using Runtime = Vulkan::TextureCacheRuntime;
|
||||
using Image = Vulkan::Image;
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_)
|
||||
: device{device_}
|
||||
UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, size_t frame_payload_size_)
|
||||
: device{device_}, frame_payload_size{frame_payload_size_},
|
||||
payload(frame_payload_size_ * FRAMES_IN_FLIGHT)
|
||||
{
|
||||
payload_start = payload.data();
|
||||
payload_cursor = payload.data();
|
||||
@@ -29,19 +30,19 @@ void UpdateDescriptorQueue::TickFrame() {
|
||||
if (++frame_index >= FRAMES_IN_FLIGHT) {
|
||||
frame_index = 0;
|
||||
}
|
||||
payload_start = payload.data() + frame_index * FRAME_PAYLOAD_SIZE;
|
||||
payload_start = payload.data() + frame_index * frame_payload_size;
|
||||
payload_cursor = payload_start;
|
||||
}
|
||||
|
||||
void UpdateDescriptorQueue::Acquire(Scheduler& scheduler, size_t required_entries) {
|
||||
static constexpr size_t DEFAULT_REQUIRED_ENTRIES = 0x400;
|
||||
const size_t reserve = required_entries > 0 ? required_entries : DEFAULT_REQUIRED_ENTRIES;
|
||||
ASSERT_MSG(reserve < FRAME_PAYLOAD_SIZE, "Descriptor reservation {} >= frame capacity {}",
|
||||
reserve, FRAME_PAYLOAD_SIZE);
|
||||
ASSERT_MSG(reserve < frame_payload_size, "Descriptor reservation {} >= frame capacity {}",
|
||||
reserve, frame_payload_size);
|
||||
const size_t used = static_cast<size_t>(std::distance(payload_start, payload_cursor));
|
||||
if (used + reserve >= FRAME_PAYLOAD_SIZE) {
|
||||
if (used + reserve >= frame_payload_size) {
|
||||
LOG_WARNING(Render_Vulkan, "Payload overflow (used={}, reserve={}, capacity={})",
|
||||
used, reserve, FRAME_PAYLOAD_SIZE);
|
||||
used, reserve, frame_payload_size);
|
||||
scheduler.WaitWorker();
|
||||
payload_cursor = payload_start;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
namespace Vulkan {
|
||||
@@ -30,11 +30,12 @@ class UpdateDescriptorQueue final {
|
||||
// This should be plenty for the vast majority of cases. Most desktop platforms only
|
||||
// provide up to 3 swapchain images.
|
||||
static constexpr size_t FRAMES_IN_FLIGHT = 8;
|
||||
static constexpr size_t FRAME_PAYLOAD_SIZE = 0x20000;
|
||||
static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT;
|
||||
|
||||
public:
|
||||
explicit UpdateDescriptorQueue(const Device& device_);
|
||||
static constexpr size_t GUEST_FRAME_PAYLOAD_SIZE = 0x80000;
|
||||
static constexpr size_t COMPUTE_FRAME_PAYLOAD_SIZE = 0x20000;
|
||||
|
||||
explicit UpdateDescriptorQueue(const Device& device_, size_t frame_payload_size_);
|
||||
~UpdateDescriptorQueue();
|
||||
|
||||
void TickFrame();
|
||||
@@ -74,11 +75,12 @@ public:
|
||||
|
||||
private:
|
||||
const Device& device;
|
||||
const size_t frame_payload_size;
|
||||
size_t frame_index{0};
|
||||
DescriptorUpdateEntry* payload_cursor = nullptr;
|
||||
DescriptorUpdateEntry* payload_start = nullptr;
|
||||
const DescriptorUpdateEntry* upload_start = nullptr;
|
||||
std::array<DescriptorUpdateEntry, PAYLOAD_SIZE> payload;
|
||||
std::vector<DescriptorUpdateEntry> payload;
|
||||
};
|
||||
|
||||
// TODO: should these be separate classes instead?
|
||||
|
||||
@@ -284,7 +284,24 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
|
||||
Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
|
||||
bool via_header_index, u32 raw) {
|
||||
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
|
||||
ASSERT(handle.first <= tic_limit);
|
||||
if (handle.first > tic_limit) {
|
||||
LOG_CRITICAL(Shader,
|
||||
"TIC index out of range: raw=0x{:08x} tic_index={} tsc_index={} tic_limit={} "
|
||||
"tic_addr=0x{:x} via_header_index={} stage={} program_base=0x{:x} "
|
||||
"start_address=0x{:x}",
|
||||
raw, handle.first, handle.second, tic_limit, tic_addr, via_header_index,
|
||||
static_cast<u32>(stage), program_base, start_address);
|
||||
ASSERT(handle.first <= tic_limit);
|
||||
Tegra::Texture::TICEntry fallback{};
|
||||
fallback.format.Assign(Tegra::Texture::TextureFormat::A8B8G8R8);
|
||||
fallback.r_type.Assign(Tegra::Texture::ComponentType::UNORM);
|
||||
fallback.g_type.Assign(Tegra::Texture::ComponentType::UNORM);
|
||||
fallback.b_type.Assign(Tegra::Texture::ComponentType::UNORM);
|
||||
fallback.a_type.Assign(Tegra::Texture::ComponentType::UNORM);
|
||||
fallback.texture_type.Assign(Tegra::Texture::TextureType::Texture2D);
|
||||
fallback.normalized_coords.Assign(1);
|
||||
return fallback;
|
||||
}
|
||||
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
|
||||
Tegra::Texture::TICEntry entry;
|
||||
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
|
||||
|
||||
@@ -58,23 +58,9 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
|
||||
void(slot_samplers.insert(runtime, sampler_descriptor));
|
||||
|
||||
if constexpr (HAS_DEVICE_MEMORY_INFO) {
|
||||
const s64 device_local_memory = static_cast<s64>(runtime.GetDeviceLocalMemory());
|
||||
const s64 min_spacing_expected = device_local_memory - 1_GiB;
|
||||
const s64 min_spacing_critical = device_local_memory - 512_MiB;
|
||||
const s64 mem_threshold = (std::min)(device_local_memory, TARGET_THRESHOLD);
|
||||
const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
|
||||
const s64 min_vacancy_critical = (2 * mem_threshold) / 10;
|
||||
expected_memory = static_cast<u64>(
|
||||
(std::max)((std::min)(device_local_memory - min_vacancy_expected, min_spacing_expected),
|
||||
DEFAULT_EXPECTED_MEMORY));
|
||||
critical_memory = static_cast<u64>(
|
||||
(std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical),
|
||||
DEFAULT_CRITICAL_MEMORY));
|
||||
minimum_memory = static_cast<u64>((device_local_memory - mem_threshold) / 2);
|
||||
memory_budget = runtime.GetDeviceLocalMemory();
|
||||
} else {
|
||||
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
|
||||
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
|
||||
minimum_memory = 0;
|
||||
memory_budget = FALLBACK_MEMORY_BUDGET;
|
||||
}
|
||||
|
||||
const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue();
|
||||
@@ -114,71 +100,154 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::RunGarbageCollector() {
|
||||
bool high_priority_mode = false;
|
||||
bool aggressive_mode = false;
|
||||
u64 ticks_to_destroy = 0;
|
||||
size_t num_iterations = 0;
|
||||
const auto Configure = [&](bool allow_aggressive) {
|
||||
high_priority_mode = total_used_memory >= expected_memory;
|
||||
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
|
||||
ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
|
||||
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
|
||||
};
|
||||
const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) {
|
||||
if (num_iterations == 0) {
|
||||
return true;
|
||||
}
|
||||
--num_iterations;
|
||||
auto& image = slot_images[image_id];
|
||||
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
||||
return false;
|
||||
}
|
||||
const bool must_download = image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
|
||||
if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) {
|
||||
return false;
|
||||
}
|
||||
if (must_download) {
|
||||
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
|
||||
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
|
||||
image.DownloadMemory(map, copies);
|
||||
runtime.Finish();
|
||||
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, swizzle_data_buffer);
|
||||
}
|
||||
if (True(image.flags & ImageFlagBits::Tracked)) {
|
||||
UntrackImage(image, image_id);
|
||||
}
|
||||
UnregisterImage(image_id);
|
||||
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
|
||||
if (aggressive_mode && total_used_memory < critical_memory) {
|
||||
num_iterations >>= 2;
|
||||
aggressive_mode = false;
|
||||
} else if (high_priority_mode && total_used_memory < expected_memory) {
|
||||
num_iterations >>= 1;
|
||||
high_priority_mode = false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
Configure(false);
|
||||
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
|
||||
if (total_used_memory >= critical_memory) {
|
||||
Configure(true);
|
||||
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
|
||||
void TextureCache<P>::QueueEvictionDownload(Image& image) {
|
||||
auto copies = FullDownloadCopies(image.info);
|
||||
auto staging = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes, true);
|
||||
image.DownloadMemory(staging, FixSmallVectorADL(copies));
|
||||
pending_eviction_downloads.push_back(PendingEvictionDownload{
|
||||
.staging = staging,
|
||||
.gpu_memory = gpu_memory,
|
||||
.copies = std::move(copies),
|
||||
.info = image.info,
|
||||
.gpu_addr = image.gpu_addr,
|
||||
.sync_point = runtime.CurrentSyncPoint(),
|
||||
});
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::TickEvictionDownloads(u64 completed_sync_point) {
|
||||
while (!pending_eviction_downloads.empty() &&
|
||||
pending_eviction_downloads.front().sync_point <= completed_sync_point) {
|
||||
auto& entry = pending_eviction_downloads.front();
|
||||
SwizzleImage(*entry.gpu_memory, entry.gpu_addr, entry.info, FixSmallVectorADL(entry.copies),
|
||||
entry.staging.mapped_span.subspan(entry.staging.offset), swizzle_data_buffer);
|
||||
runtime.FreeDeferredStagingBuffer(entry.staging);
|
||||
pending_eviction_downloads.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::FlushEvictionDownloads() {
|
||||
if (pending_eviction_downloads.empty()) {
|
||||
return;
|
||||
}
|
||||
const u64 last_sync_point = pending_eviction_downloads.back().sync_point;
|
||||
runtime.WaitSyncPoint(last_sync_point);
|
||||
TickEvictionDownloads(last_sync_point);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
u64 TextureCache<P>::ImageSizeBytes(const ImageBase& image) {
|
||||
u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes);
|
||||
if ((IsPixelFormatASTC(image.info.format) &&
|
||||
True(image.flags & ImageFlagBits::AcceleratedUpload)) ||
|
||||
True(image.flags & ImageFlagBits::Converted)) {
|
||||
tentative_size = TranscodedAstcSize(tentative_size, image.info.format);
|
||||
}
|
||||
u64 size = Common::AlignUp(tentative_size, 1024);
|
||||
if (image.HasScaled()) {
|
||||
size += GetScaledImageSizeBytes(image);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
u64 TextureCache<P>::DeviceUsage(bool force_refresh) {
|
||||
if (!runtime.CanReportAllocationUsage()) {
|
||||
return total_used_memory;
|
||||
}
|
||||
if (force_refresh || usage_refresh_countdown == 0) {
|
||||
cached_device_usage = runtime.GetDeviceAllocationUsage();
|
||||
usage_refresh_countdown = USAGE_REFRESH_INTERVAL;
|
||||
} else {
|
||||
--usage_refresh_countdown;
|
||||
}
|
||||
return cached_device_usage;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
u64 TextureCache<P>::ReclaimMemory(u64 target_bytes, bool allow_download) {
|
||||
if (target_bytes == 0 || in_reclaim) {
|
||||
return 0;
|
||||
}
|
||||
in_reclaim = true;
|
||||
const u64 drain_point = runtime.CompletedSyncPoint();
|
||||
TickEvictionDownloads(drain_point);
|
||||
sentenced_images.Reclaim(drain_point);
|
||||
sentenced_image_view.Reclaim(drain_point);
|
||||
sentenced_framebuffers.Reclaim(drain_point);
|
||||
u64 freed = 0;
|
||||
const auto evict = [&](ImageId image_id) {
|
||||
if (freed >= target_bytes) {
|
||||
return true;
|
||||
}
|
||||
auto& image = slot_images[image_id];
|
||||
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
||||
return false;
|
||||
}
|
||||
const bool must_download =
|
||||
image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
|
||||
bool queued_download = false;
|
||||
if (must_download) {
|
||||
if constexpr (HAS_TIMELINE_SYNC_POINTS) {
|
||||
if (!allow_download) {
|
||||
return false;
|
||||
}
|
||||
QueueEvictionDownload(image);
|
||||
queued_download = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const u64 image_bytes = ImageSizeBytes(image);
|
||||
if (True(image.flags & ImageFlagBits::Tracked)) {
|
||||
UntrackImage(image, image_id);
|
||||
}
|
||||
UnregisterImage(image_id);
|
||||
DeleteImage(image_id, !queued_download && image.scale_tick > frame_tick + 5);
|
||||
freed += image_bytes;
|
||||
return false;
|
||||
};
|
||||
const u64 cold_tick =
|
||||
frame_tick > RECLAIM_GUARD_FRAMES ? frame_tick - RECLAIM_GUARD_FRAMES : 0;
|
||||
lru_cache.ForEachItemBelow(cold_tick, evict);
|
||||
if (freed < target_bytes) {
|
||||
lru_cache.ForEachItemBelow(frame_tick > 0 ? frame_tick - 1 : 0, evict);
|
||||
}
|
||||
const u64 exit_point = runtime.CompletedSyncPoint();
|
||||
sentenced_images.Reclaim(exit_point);
|
||||
sentenced_image_view.Reclaim(exit_point);
|
||||
sentenced_framebuffers.Reclaim(exit_point);
|
||||
in_reclaim = false;
|
||||
usage_refresh_countdown = 0;
|
||||
reclaim_stalled = freed == 0;
|
||||
return freed;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::EnsureHeadroom(bool allow_download) {
|
||||
if (reclaim_stalled) {
|
||||
return;
|
||||
}
|
||||
const u64 limit = memory_budget > RECLAIM_HEADROOM ? memory_budget - RECLAIM_HEADROOM : 0;
|
||||
const u64 usage = DeviceUsage(false);
|
||||
if (usage <= limit) {
|
||||
return;
|
||||
}
|
||||
const u64 target = (limit / 100) * RECLAIM_TARGET_PERCENT;
|
||||
ReclaimMemory((std::min)(usage - target, total_used_memory), allow_download);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::TickFrame() {
|
||||
// If we can obtain the memory info, use it instead of the estimate.
|
||||
if (runtime.CanReportMemoryUsage()) {
|
||||
total_used_memory = runtime.GetDeviceMemoryUsage();
|
||||
}
|
||||
if (total_used_memory > minimum_memory) {
|
||||
RunGarbageCollector();
|
||||
}
|
||||
sentenced_images.Tick();
|
||||
sentenced_framebuffers.Tick();
|
||||
sentenced_image_view.Tick();
|
||||
usage_refresh_countdown = 0;
|
||||
reclaim_stalled = false;
|
||||
EnsureHeadroom(true);
|
||||
const u64 completed_sync_point = runtime.CompletedSyncPoint();
|
||||
TickEvictionDownloads(completed_sync_point);
|
||||
sentenced_images.Reclaim(completed_sync_point);
|
||||
sentenced_framebuffers.Reclaim(completed_sync_point);
|
||||
sentenced_image_view.Reclaim(completed_sync_point);
|
||||
TickAsyncDecode();
|
||||
TickAsyncUnswizzle();
|
||||
|
||||
@@ -596,6 +665,7 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
|
||||
FlushEvictionDownloads();
|
||||
boost::container::small_vector<ImageId, 16> images;
|
||||
ForEachImageInRegion(cpu_addr, size, [&images](ImageId image_id, ImageBase& image) {
|
||||
if (!image.IsSafeDownload()) {
|
||||
@@ -894,6 +964,7 @@ void TextureCache<P>::CommitAsyncFlushes() {
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::PopAsyncFlushes() {
|
||||
TickEvictionDownloads(runtime.CompletedSyncPoint());
|
||||
if (committed_downloads.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -1294,8 +1365,9 @@ void TextureCache<P>::InvalidateScale(Image& image) {
|
||||
}
|
||||
RemoveImageViewReferences(image_view_ids);
|
||||
RemoveFramebuffers(image_view_ids);
|
||||
const u64 sync_point = runtime.CurrentSyncPoint();
|
||||
for (const ImageViewId image_view_id : image_view_ids) {
|
||||
sentenced_image_view.Push(std::move(slot_image_views[image_view_id]));
|
||||
sentenced_image_view.Push(std::move(slot_image_views[image_view_id]), sync_point);
|
||||
slot_image_views.erase(image_view_id);
|
||||
}
|
||||
image.image_view_ids.clear();
|
||||
@@ -1331,6 +1403,7 @@ void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
|
||||
LOG_INFO(HW_GPU, "Queuing async texture decode");
|
||||
|
||||
image.flags |= ImageFlagBits::IsDecoding;
|
||||
runtime.TransitionImageLayout(image);
|
||||
auto decode = std::make_unique<AsyncDecodeContext>();
|
||||
auto* decode_ptr = decode.get();
|
||||
decode->image_id = image_id;
|
||||
@@ -1363,6 +1436,7 @@ void TextureCache<P>::QueueAsyncUnswizzle(Image& image, ImageId image_id) {
|
||||
}
|
||||
|
||||
image.flags |= ImageFlagBits::IsDecoding;
|
||||
runtime.TransitionImageLayout(image);
|
||||
|
||||
unswizzle_queue.push_back({
|
||||
.image_id = image_id,
|
||||
@@ -1523,6 +1597,7 @@ ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
|
||||
template <class P>
|
||||
ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DAddr cpu_addr) {
|
||||
EnsureHeadroom(false);
|
||||
ImageInfo new_info = info;
|
||||
const size_t size_bytes = CalculateGuestSizeInBytes(new_info);
|
||||
const bool broken_views = runtime.HasBrokenTextureViewFormats();
|
||||
@@ -1631,7 +1706,28 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
|
||||
for (const ImageId overlap_id : join_ignore_textures) {
|
||||
Image& overlap = slot_images[overlap_id];
|
||||
if (True(overlap.flags & ImageFlagBits::GpuModified)) {
|
||||
UNIMPLEMENTED();
|
||||
if (new_image.TryFindBase(overlap.gpu_addr) &&
|
||||
(!can_rescale || ImageCanRescale(overlap))) {
|
||||
if (can_rescale) {
|
||||
ScaleUp(overlap);
|
||||
} else {
|
||||
ScaleDown(overlap);
|
||||
}
|
||||
join_copies_to_do.emplace_back(JoinCopy{false, overlap_id});
|
||||
continue;
|
||||
}
|
||||
if (overlap.IsSafeDownload() && False(overlap.flags & ImageFlagBits::BadOverlap) &&
|
||||
gpu_memory->GpuToCpuAddress(overlap.gpu_addr).has_value()) {
|
||||
QueueEvictionDownload(overlap);
|
||||
} else {
|
||||
LOG_WARNING(HW_GPU,
|
||||
"Dropping GPU modified overlap, contents are not recoverable: "
|
||||
"gpu_addr=0x{:x} format={} size={}x{}x{} levels={} layers={}",
|
||||
overlap.gpu_addr, static_cast<int>(overlap.info.format),
|
||||
overlap.info.size.width, overlap.info.size.height,
|
||||
overlap.info.size.depth, overlap.info.resources.levels,
|
||||
overlap.info.resources.layers);
|
||||
}
|
||||
}
|
||||
if (True(overlap.flags & ImageFlagBits::Tracked)) {
|
||||
UntrackImage(overlap, overlap_id);
|
||||
@@ -2185,13 +2281,7 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {
|
||||
ASSERT_MSG(False(image.flags & ImageFlagBits::Registered),
|
||||
"Trying to register an already registered image");
|
||||
image.flags |= ImageFlagBits::Registered;
|
||||
u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes);
|
||||
if ((IsPixelFormatASTC(image.info.format) &&
|
||||
True(image.flags & ImageFlagBits::AcceleratedUpload)) ||
|
||||
True(image.flags & ImageFlagBits::Converted)) {
|
||||
tentative_size = TranscodedAstcSize(tentative_size, image.info.format);
|
||||
}
|
||||
total_used_memory += Common::AlignUp(tentative_size, 1024);
|
||||
total_used_memory += ImageSizeBytes(image);
|
||||
image.lru_index = lru_cache.Insert(image_id, frame_tick);
|
||||
|
||||
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
|
||||
@@ -2354,16 +2444,7 @@ void TextureCache<P>::UntrackImage(ImageBase& image, ImageId image_id) {
|
||||
template <class P>
|
||||
void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) {
|
||||
ImageBase& image = slot_images[image_id];
|
||||
if (image.HasScaled()) {
|
||||
total_used_memory -= GetScaledImageSizeBytes(image);
|
||||
}
|
||||
u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes);
|
||||
if ((IsPixelFormatASTC(image.info.format) &&
|
||||
True(image.flags & ImageFlagBits::AcceleratedUpload)) ||
|
||||
True(image.flags & ImageFlagBits::Converted)) {
|
||||
tentative_size = TranscodedAstcSize(tentative_size, image.info.format);
|
||||
}
|
||||
total_used_memory -= Common::AlignUp(tentative_size, 1024);
|
||||
total_used_memory -= std::min<u64>(total_used_memory, ImageSizeBytes(image));
|
||||
const GPUVAddr gpu_addr = image.gpu_addr;
|
||||
const auto alloc_it = image_allocs_table.find(gpu_addr);
|
||||
if (alloc_it == image_allocs_table.end()) {
|
||||
@@ -2417,14 +2498,15 @@ void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) {
|
||||
ASSERT_MSG(num_removed_overlaps == 1, "Invalid number of removed overlapps: {}",
|
||||
num_removed_overlaps);
|
||||
}
|
||||
const u64 sync_point = runtime.CurrentSyncPoint();
|
||||
for (const ImageViewId image_view_id : image_view_ids) {
|
||||
if (!immediate_delete) {
|
||||
sentenced_image_view.Push(std::move(slot_image_views[image_view_id]));
|
||||
sentenced_image_view.Push(std::move(slot_image_views[image_view_id]), sync_point);
|
||||
}
|
||||
slot_image_views.erase(image_view_id);
|
||||
}
|
||||
if (!immediate_delete) {
|
||||
sentenced_images.Push(std::move(slot_images[image_id]));
|
||||
sentenced_images.Push(std::move(slot_images[image_id]), sync_point);
|
||||
}
|
||||
slot_images.erase(image_id);
|
||||
|
||||
@@ -2470,7 +2552,8 @@ void TextureCache<P>::RemoveFramebuffers(std::span<const ImageViewId> removed_vi
|
||||
last_framebuffer_id = {};
|
||||
last_framebuffer_serial = 0;
|
||||
}
|
||||
sentenced_framebuffers.Push(std::move(slot_framebuffers[framebuffer_id]));
|
||||
sentenced_framebuffers.Push(std::move(slot_framebuffers[framebuffer_id]),
|
||||
runtime.CurrentSyncPoint());
|
||||
it = framebuffers.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "common/thread_worker.h"
|
||||
#include "video_core/compatible_formats.h"
|
||||
#include "video_core/control/channel_state_cache.h"
|
||||
#include "video_core/delayed_destruction_ring.h"
|
||||
#include "video_core/deferred_destruction_queue.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache/descriptor_table.h"
|
||||
@@ -108,18 +108,20 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
|
||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO;
|
||||
/// True when the API can do asynchronous texture downloads.
|
||||
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS;
|
||||
static constexpr bool HAS_TIMELINE_SYNC_POINTS = P::HAS_TIMELINE_SYNC_POINTS;
|
||||
|
||||
static constexpr size_t UNSET_CHANNEL{(std::numeric_limits<size_t>::max)()};
|
||||
|
||||
#ifdef YUZU_LEGACY
|
||||
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
|
||||
static constexpr u64 RECLAIM_HEADROOM = 384_MiB;
|
||||
#else
|
||||
static constexpr s64 TARGET_THRESHOLD = 4_GiB;
|
||||
static constexpr u64 RECLAIM_HEADROOM = 512_MiB;
|
||||
#endif
|
||||
|
||||
static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB;
|
||||
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB;
|
||||
static constexpr size_t GC_EMERGENCY_COUNTS = 2;
|
||||
static constexpr u64 FALLBACK_MEMORY_BUDGET = 2_GiB;
|
||||
static constexpr u32 USAGE_REFRESH_INTERVAL = 16;
|
||||
static constexpr u64 RECLAIM_GUARD_FRAMES = 8;
|
||||
static constexpr u64 RECLAIM_TARGET_PERCENT = 88;
|
||||
|
||||
using Runtime = typename P::Runtime;
|
||||
using Image = typename P::Image;
|
||||
@@ -154,6 +156,8 @@ public:
|
||||
/// Notify the cache that a new frame has been queued
|
||||
void TickFrame();
|
||||
|
||||
u64 ReclaimMemory(u64 target_bytes, bool allow_download);
|
||||
|
||||
/// Return a constant reference to the given image view id
|
||||
[[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept;
|
||||
|
||||
@@ -293,8 +297,17 @@ private:
|
||||
|
||||
void OnGPUASRegister(size_t map_id) final override;
|
||||
|
||||
/// Runs the Garbage Collector.
|
||||
void RunGarbageCollector();
|
||||
u64 ImageSizeBytes(const ImageBase& image);
|
||||
|
||||
u64 DeviceUsage(bool force_refresh);
|
||||
|
||||
void EnsureHeadroom(bool allow_download);
|
||||
|
||||
void QueueEvictionDownload(Image& image);
|
||||
|
||||
void TickEvictionDownloads(u64 completed_sync_point);
|
||||
|
||||
void FlushEvictionDownloads();
|
||||
|
||||
/// Find or create an image view in the guest descriptor table
|
||||
ImageViewId VisitImageView(u32 index, bool compute);
|
||||
@@ -451,9 +464,11 @@ private:
|
||||
bool has_deleted_images = false;
|
||||
bool is_rescaling = false;
|
||||
u64 total_used_memory = 0;
|
||||
u64 minimum_memory;
|
||||
u64 expected_memory;
|
||||
u64 critical_memory;
|
||||
u64 memory_budget = 0;
|
||||
u64 cached_device_usage = 0;
|
||||
u32 usage_refresh_countdown = 0;
|
||||
bool in_reclaim = false;
|
||||
bool reclaim_stalled = false;
|
||||
size_t gpu_unswizzle_maxsize = 0;
|
||||
size_t swizzle_chunk_size = 0;
|
||||
u32 swizzle_slices_per_batch = 0;
|
||||
@@ -491,14 +506,19 @@ private:
|
||||
};
|
||||
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
|
||||
|
||||
#ifdef YUZU_LEGACY
|
||||
static constexpr size_t TICKS_TO_DESTROY = 6;
|
||||
#else
|
||||
static constexpr size_t TICKS_TO_DESTROY = 8;
|
||||
#endif
|
||||
DelayedDestructionRing<Image, TICKS_TO_DESTROY> sentenced_images;
|
||||
DelayedDestructionRing<ImageView, TICKS_TO_DESTROY> sentenced_image_view;
|
||||
DelayedDestructionRing<Framebuffer, TICKS_TO_DESTROY> sentenced_framebuffers;
|
||||
DeferredDestructionQueue<Image> sentenced_images;
|
||||
DeferredDestructionQueue<ImageView> sentenced_image_view;
|
||||
DeferredDestructionQueue<Framebuffer> sentenced_framebuffers;
|
||||
|
||||
struct PendingEvictionDownload {
|
||||
AsyncBuffer staging;
|
||||
Tegra::MemoryManager* gpu_memory;
|
||||
boost::container::small_vector<VideoCommon::BufferImageCopy, 16> copies;
|
||||
VideoCommon::ImageInfo info;
|
||||
GPUVAddr gpu_addr;
|
||||
u64 sync_point;
|
||||
};
|
||||
std::deque<PendingEvictionDownload> pending_eviction_downloads;
|
||||
|
||||
ankerl::unordered_dense::map<GPUVAddr, ImageAllocId> image_allocs_table;
|
||||
|
||||
@@ -509,7 +529,8 @@ private:
|
||||
u64 frame_tick = 0;
|
||||
u64 last_sampler_gc_frame = (std::numeric_limits<u64>::max)();
|
||||
|
||||
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder"};
|
||||
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {},
|
||||
Common::ThreadPlacement::Background};
|
||||
std::vector<std::unique_ptr<AsyncDecodeContext>> async_decodes;
|
||||
|
||||
std::deque<PendingUnswizzle> unswizzle_queue;
|
||||
|
||||
@@ -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
|
||||
@@ -747,7 +747,7 @@ boost::container::small_vector<ImageCopy, 16> MakeShrinkImageCopies(const ImageI
|
||||
|
||||
const bool is_dst_3d = dst.type == ImageType::e3D;
|
||||
if (is_dst_3d) {
|
||||
ASSERT(src.type == ImageType::e3D);
|
||||
ASSERT(src.type == ImageType::e3D || src.resources.layers == 1);
|
||||
ASSERT(src.resources.levels == 1);
|
||||
}
|
||||
const bool both_2d{src.type == ImageType::e2D && dst.type == ImageType::e2D};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -10,7 +10,8 @@ namespace Tegra::Texture {
|
||||
|
||||
Common::ThreadWorker& GetThreadWorkers() {
|
||||
static Common::ThreadWorker workers{(std::max)(std::thread::hardware_concurrency(), 2U) / 2,
|
||||
"ImageTranscode"};
|
||||
"ImageTranscode", {},
|
||||
Common::ThreadPlacement::Background};
|
||||
|
||||
return workers;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
@@ -732,7 +733,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
.device = *logical,
|
||||
.preferredLargeHeapBlockSize = is_integrated
|
||||
? (64u * 1024u * 1024u)
|
||||
: (256u * 1024u * 1024u),
|
||||
: (128u * 1024u * 1024u),
|
||||
.pAllocationCallbacks = nullptr,
|
||||
.pDeviceMemoryCallbacks = nullptr,
|
||||
.pHeapSizeLimit = nullptr,
|
||||
@@ -744,12 +745,32 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
|
||||
vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
|
||||
|
||||
{
|
||||
const auto& limits = properties.properties.limits;
|
||||
LOG_INFO(Render_Vulkan, "MSAA sample count support:");
|
||||
LOG_INFO(Render_Vulkan, " framebufferColorSampleCounts: {:#x}",
|
||||
limits.framebufferColorSampleCounts);
|
||||
LOG_INFO(Render_Vulkan, " framebufferDepthSampleCounts: {:#x}",
|
||||
limits.framebufferDepthSampleCounts);
|
||||
LOG_INFO(Render_Vulkan, " framebufferStencilSampleCounts: {:#x}",
|
||||
limits.framebufferStencilSampleCounts);
|
||||
LOG_INFO(Render_Vulkan, " sampledImageColorSampleCounts: {:#x}",
|
||||
limits.sampledImageColorSampleCounts);
|
||||
LOG_INFO(Render_Vulkan, " sampledImageDepthSampleCounts: {:#x}",
|
||||
limits.sampledImageDepthSampleCounts);
|
||||
LOG_INFO(Render_Vulkan, " sampledImageIntegerSampleCounts:{:#x}",
|
||||
limits.sampledImageIntegerSampleCounts);
|
||||
LOG_INFO(Render_Vulkan, " storageImageSampleCounts: {:#x}",
|
||||
limits.storageImageSampleCounts);
|
||||
}
|
||||
|
||||
// Initialize GPU logging if enabled
|
||||
InitializeGPULogging();
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
ShutdownGPULogging();
|
||||
vk::FlushDeletionQueue();
|
||||
vmaDestroyAllocator(allocator);
|
||||
}
|
||||
|
||||
@@ -817,7 +838,8 @@ bool Device::ComputeIsOptimalAstcSupported() const {
|
||||
VK_FORMAT_ASTC_12x10_UNORM_BLOCK, VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
|
||||
VK_FORMAT_ASTC_12x12_UNORM_BLOCK, VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
|
||||
};
|
||||
if (!features.features.textureCompressionASTC_LDR) {
|
||||
if (!features.features.textureCompressionASTC_LDR ||
|
||||
!features.texture_compression_astc_hdr.textureCompressionASTC_HDR) {
|
||||
return false;
|
||||
}
|
||||
const auto format_feature_usage{VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
|
||||
@@ -1209,10 +1231,20 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.depth_bias_control, features.depth_bias_control,
|
||||
VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_depth_clamp_zero_one
|
||||
extensions.depth_clamp_zero_one = features.depth_clamp_zero_one.depthClampZeroOne;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.depth_clamp_zero_one,
|
||||
features.depth_clamp_zero_one,
|
||||
VK_EXT_DEPTH_CLAMP_ZERO_ONE_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_depth_clip_control
|
||||
extensions.depth_clip_control = features.depth_clip_control.depthClipControl;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
|
||||
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
|
||||
// VK_EXT_depth_clip_enable
|
||||
extensions.depth_clip_enable = features.depth_clip_enable.depthClipEnable;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_enable, features.depth_clip_enable,
|
||||
VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_extended_dynamic_state
|
||||
extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState;
|
||||
@@ -1444,6 +1476,23 @@ std::optional<size_t> Device::GetSamplerHeapBudget() const {
|
||||
return sampler_heap_budget;
|
||||
}
|
||||
|
||||
Device::MemoryBudgetInfo Device::GetMemoryBudgetInfo() const {
|
||||
std::array<VmaBudget, VK_MAX_MEMORY_HEAPS> budgets{};
|
||||
vmaGetHeapBudgets(allocator, budgets.data());
|
||||
MemoryBudgetInfo info{};
|
||||
for (const size_t heap : valid_heap_memory) {
|
||||
info.usage += budgets[heap].usage;
|
||||
info.budget += budgets[heap].budget;
|
||||
info.block_bytes += budgets[heap].statistics.blockBytes;
|
||||
info.allocation_bytes += budgets[heap].statistics.allocationBytes;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
void Device::TickAllocatorFrame() const {
|
||||
vmaSetCurrentFrameIndex(allocator, ++allocator_frame_index);
|
||||
}
|
||||
|
||||
u64 Device::GetDeviceMemoryUsage() const {
|
||||
VkPhysicalDeviceMemoryBudgetPropertiesEXT budget;
|
||||
budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT;
|
||||
@@ -1493,9 +1542,12 @@ void Device::CollectPhysicalMemoryInfo() {
|
||||
device_access_memory -= reserve_memory;
|
||||
if (Settings::values.vram_usage_mode.GetValue() != Settings::VramUsageMode::Aggressive) {
|
||||
// Account for resolution scaling in memory limits
|
||||
const size_t normal_memory = 6_GiB;
|
||||
const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
|
||||
device_access_memory = std::min<u64>(device_access_memory, normal_memory + scaler_memory);
|
||||
const u64 normal_memory = 6_GiB;
|
||||
const u64 scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
|
||||
const u64 baseline = normal_memory + scaler_memory;
|
||||
const u64 proportional = (device_access_memory / 4) * 3;
|
||||
device_access_memory =
|
||||
std::min<u64>(device_access_memory, std::max<u64>(baseline, proportional));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,9 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
FEATURE(EXT, ColorWriteEnable, COLOR_WRITE_ENABLE, color_write_enable) \
|
||||
FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \
|
||||
FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \
|
||||
FEATURE(EXT, DepthClampZeroOne, DEPTH_CLAMP_ZERO_ONE, depth_clamp_zero_one) \
|
||||
FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \
|
||||
FEATURE(EXT, DepthClipEnable, DEPTH_CLIP_ENABLE, depth_clip_enable) \
|
||||
FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
|
||||
FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
|
||||
FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
|
||||
@@ -255,6 +257,17 @@ public:
|
||||
return allocator;
|
||||
}
|
||||
|
||||
struct MemoryBudgetInfo {
|
||||
u64 usage;
|
||||
u64 budget;
|
||||
u64 block_bytes;
|
||||
u64 allocation_bytes;
|
||||
};
|
||||
|
||||
MemoryBudgetInfo GetMemoryBudgetInfo() const;
|
||||
|
||||
void TickAllocatorFrame() const;
|
||||
|
||||
/// Returns the logical device.
|
||||
const vk::Device& GetLogical() const {
|
||||
return logical;
|
||||
@@ -369,8 +382,7 @@ FN_MAX_LIMIT_LIST
|
||||
}
|
||||
|
||||
bool IsOptimalAstcSupported() const {
|
||||
return features.features.textureCompressionASTC_LDR &&
|
||||
features.texture_compression_astc_hdr.textureCompressionASTC_HDR;
|
||||
return is_optimal_astc_supported;
|
||||
}
|
||||
|
||||
/// Returns true if BCn is natively supported.
|
||||
@@ -602,6 +614,16 @@ FN_MAX_LIMIT_LIST
|
||||
return extensions.depth_clip_control;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_depth_clamp_zero_one.
|
||||
bool IsExtDepthClampZeroOneSupported() const {
|
||||
return extensions.depth_clamp_zero_one;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_depth_clip_enable.
|
||||
bool IsExtDepthClipEnableSupported() const {
|
||||
return extensions.depth_clip_enable;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_depth_bias_control.
|
||||
bool IsExtDepthBiasControlSupported() const {
|
||||
return extensions.depth_bias_control;
|
||||
@@ -734,6 +756,38 @@ FN_MAX_LIMIT_LIST
|
||||
return features.line_rasterization.stippledRectangularLines != VK_FALSE;
|
||||
}
|
||||
|
||||
VkLineRasterizationModeEXT GetLineRasterizationMode(bool wants_smooth) const {
|
||||
if (wants_smooth && SupportsSmoothLines()) {
|
||||
return VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT;
|
||||
}
|
||||
if (SupportsRectangularLines()) {
|
||||
return VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT;
|
||||
}
|
||||
return VK_LINE_RASTERIZATION_MODE_DEFAULT_EXT;
|
||||
}
|
||||
|
||||
bool SupportsStippleForMode(VkLineRasterizationModeEXT mode) const {
|
||||
switch (mode) {
|
||||
case VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT:
|
||||
return features.line_rasterization.stippledSmoothLines != VK_FALSE;
|
||||
case VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT:
|
||||
return features.line_rasterization.stippledBresenhamLines != VK_FALSE;
|
||||
default:
|
||||
return features.line_rasterization.stippledRectangularLines != VK_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
float ClampLineWidth(float width) const {
|
||||
if (!features.features.wideLines) {
|
||||
return 1.0f;
|
||||
}
|
||||
const auto& range = properties.properties.limits.lineWidthRange;
|
||||
if (!(width >= range[0])) {
|
||||
return range[0];
|
||||
}
|
||||
return width > range[1] ? range[1] : width;
|
||||
}
|
||||
|
||||
bool SupportsAlphaToOne() const {
|
||||
return features.features.alphaToOne != VK_FALSE;
|
||||
}
|
||||
@@ -876,6 +930,10 @@ FN_MAX_LIMIT_LIST
|
||||
|
||||
u64 GetDeviceMemoryUsage() const;
|
||||
|
||||
VkSampleCountFlags GetStorageImageSampleCounts() const {
|
||||
return properties.properties.limits.storageImageSampleCounts;
|
||||
}
|
||||
|
||||
u32 GetSetsPerPool() const {
|
||||
return sets_per_pool;
|
||||
}
|
||||
@@ -1060,6 +1118,7 @@ private:
|
||||
private:
|
||||
VkInstance instance; ///< Vulkan instance.
|
||||
VmaAllocator allocator; ///< VMA allocator.
|
||||
mutable u32 allocator_frame_index{};
|
||||
vk::DeviceDispatch dld; ///< Device function pointers.
|
||||
vk::PhysicalDevice physical; ///< Physical device.
|
||||
vk::Device logical; ///< Logical device.
|
||||
|
||||
@@ -30,26 +30,6 @@ namespace Vulkan {
|
||||
|
||||
// Helpers translating MemoryUsage to flags/usage
|
||||
|
||||
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
|
||||
switch (usage) {
|
||||
case MemoryUsage::DeviceLocal:
|
||||
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
case MemoryUsage::Upload:
|
||||
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
case MemoryUsage::Download:
|
||||
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
|
||||
case MemoryUsage::Stream:
|
||||
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
}
|
||||
ASSERT_MSG(false, "Invalid memory usage={}", usage);
|
||||
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) {
|
||||
if (usage == MemoryUsage::Download) {
|
||||
return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
@@ -86,125 +66,11 @@ namespace Vulkan {
|
||||
}
|
||||
|
||||
|
||||
// This avoids calling vkGetBufferMemoryRequirements* directly.
|
||||
template<typename T>
|
||||
static VkBuffer GetVkHandleFromBuffer(const T &buf) {
|
||||
if constexpr (requires { static_cast<VkBuffer>(buf); }) {
|
||||
return static_cast<VkBuffer>(buf);
|
||||
} else if constexpr (requires {{ buf.GetHandle() } -> std::convertible_to<VkBuffer>; }) {
|
||||
return buf.GetHandle();
|
||||
} else if constexpr (requires {{ buf.Handle() } -> std::convertible_to<VkBuffer>; }) {
|
||||
return buf.Handle();
|
||||
} else if constexpr (requires {{ buf.vk_handle() } -> std::convertible_to<VkBuffer>; }) {
|
||||
return buf.vk_handle();
|
||||
} else {
|
||||
static_assert(sizeof(T) == 0, "Cannot extract VkBuffer handle from vk::Buffer");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
//MemoryCommit is now VMA-backed
|
||||
MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a,
|
||||
const VmaAllocationInfo &info) noexcept
|
||||
: allocator{alloc}, allocation{a}, memory{info.deviceMemory},
|
||||
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {
|
||||
// Log GPU memory allocation
|
||||
if (GPU::Logging::IsActive() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
|
||||
reinterpret_cast<uintptr_t>(memory),
|
||||
static_cast<u64>(size),
|
||||
0 // Memory property flags (not easily available from VMA)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryCommit::~MemoryCommit() { Release(); }
|
||||
|
||||
MemoryCommit::MemoryCommit(MemoryCommit &&rhs) noexcept
|
||||
: allocator{std::exchange(rhs.allocator, nullptr)},
|
||||
allocation{std::exchange(rhs.allocation, nullptr)},
|
||||
memory{std::exchange(rhs.memory, VK_NULL_HANDLE)},
|
||||
offset{std::exchange(rhs.offset, 0)},
|
||||
size{std::exchange(rhs.size, 0)},
|
||||
mapped_ptr{std::exchange(rhs.mapped_ptr, nullptr)} {}
|
||||
|
||||
MemoryCommit &MemoryCommit::operator=(MemoryCommit &&rhs) noexcept {
|
||||
if (this != &rhs) {
|
||||
Release();
|
||||
allocator = std::exchange(rhs.allocator, nullptr);
|
||||
allocation = std::exchange(rhs.allocation, nullptr);
|
||||
memory = std::exchange(rhs.memory, VK_NULL_HANDLE);
|
||||
offset = std::exchange(rhs.offset, 0);
|
||||
size = std::exchange(rhs.size, 0);
|
||||
mapped_ptr = std::exchange(rhs.mapped_ptr, nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::span<u8> MemoryCommit::Map()
|
||||
{
|
||||
if (!allocation) return {};
|
||||
if (!mapped_ptr) {
|
||||
if (vmaMapMemory(allocator, allocation, &mapped_ptr) != VK_SUCCESS) return {};
|
||||
}
|
||||
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
|
||||
(std::numeric_limits<size_t>::max)()));
|
||||
return std::span<u8>{static_cast<u8 *>(mapped_ptr), n};
|
||||
}
|
||||
|
||||
std::span<const u8> MemoryCommit::Map() const
|
||||
{
|
||||
if (!allocation) return {};
|
||||
if (!mapped_ptr) {
|
||||
void *p = nullptr;
|
||||
if (vmaMapMemory(allocator, allocation, &p) != VK_SUCCESS) return {};
|
||||
const_cast<MemoryCommit *>(this)->mapped_ptr = p;
|
||||
}
|
||||
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
|
||||
(std::numeric_limits<size_t>::max)()));
|
||||
return std::span<const u8>{static_cast<const u8 *>(mapped_ptr), n};
|
||||
}
|
||||
|
||||
void MemoryCommit::Unmap()
|
||||
{
|
||||
if (allocation && mapped_ptr) {
|
||||
vmaUnmapMemory(allocator, allocation);
|
||||
mapped_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryCommit::Release() {
|
||||
if (allocation && allocator) {
|
||||
// Log GPU memory deallocation
|
||||
if (GPU::Logging::IsActive() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue() &&
|
||||
memory != VK_NULL_HANDLE) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation(
|
||||
reinterpret_cast<uintptr_t>(memory)
|
||||
);
|
||||
}
|
||||
|
||||
if (mapped_ptr) {
|
||||
vmaUnmapMemory(allocator, allocation);
|
||||
mapped_ptr = nullptr;
|
||||
}
|
||||
vmaFreeMemory(allocator, allocation);
|
||||
}
|
||||
allocation = nullptr;
|
||||
allocator = nullptr;
|
||||
memory = VK_NULL_HANDLE;
|
||||
offset = 0;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
MemoryAllocator::MemoryAllocator(const Device &device_)
|
||||
: device{device_}, allocator{device.GetAllocator()},
|
||||
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
|
||||
buffer_image_granularity{
|
||||
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
|
||||
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties} {
|
||||
|
||||
// Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug
|
||||
if (device.HasDebuggingToolAttached())
|
||||
@@ -224,6 +90,21 @@ namespace Vulkan {
|
||||
|
||||
MemoryAllocator::~MemoryAllocator() = default;
|
||||
|
||||
void MemoryAllocator::SetReclaimCallback(ReclaimCallback callback) {
|
||||
reclaim_callback = std::move(callback);
|
||||
vk::SetAllocatorOwnerThread();
|
||||
}
|
||||
|
||||
bool MemoryAllocator::ReclaimAtLeast(u64 hint_bytes) const {
|
||||
if (!reclaim_callback || in_reclaim) {
|
||||
return false;
|
||||
}
|
||||
in_reclaim = true;
|
||||
const u64 freed = reclaim_callback(hint_bytes);
|
||||
in_reclaim = false;
|
||||
return freed > 0;
|
||||
}
|
||||
|
||||
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const
|
||||
{
|
||||
const VmaAllocationCreateInfo alloc_ci = {
|
||||
@@ -240,7 +121,26 @@ namespace Vulkan {
|
||||
VkImage handle{};
|
||||
VmaAllocation allocation{};
|
||||
VmaAllocationInfo alloc_info{};
|
||||
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
|
||||
DEBUG_ASSERT(vk::OnAllocatorOwnerThread());
|
||||
|
||||
VkResult res = vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info);
|
||||
|
||||
if (res != VK_SUCCESS && ReclaimAtLeast(IMAGE_RECLAIM_HINT)) {
|
||||
res = vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info);
|
||||
}
|
||||
|
||||
if (res != VK_SUCCESS) {
|
||||
auto relaxed_ci = alloc_ci;
|
||||
relaxed_ci.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
|
||||
res = vmaCreateImage(allocator, &ci, &relaxed_ci, &handle, &allocation, &alloc_info);
|
||||
|
||||
if (res != VK_SUCCESS) {
|
||||
relaxed_ci.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
res = vmaCreateImage(allocator, &ci, &relaxed_ci, &handle, &allocation, &alloc_info);
|
||||
}
|
||||
}
|
||||
|
||||
vk::Check(res);
|
||||
|
||||
// Log GPU memory allocation for images
|
||||
if (GPU::Logging::IsActive() &&
|
||||
@@ -277,7 +177,28 @@ namespace Vulkan {
|
||||
VmaAllocation allocation{};
|
||||
VkMemoryPropertyFlags property_flags{};
|
||||
|
||||
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
|
||||
DEBUG_ASSERT(vk::OnAllocatorOwnerThread());
|
||||
|
||||
VkResult res = vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info);
|
||||
|
||||
if (res != VK_SUCCESS && ReclaimAtLeast(ci.size)) {
|
||||
res = vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info);
|
||||
}
|
||||
|
||||
if (res != VK_SUCCESS) {
|
||||
auto relaxed_ci = alloc_ci;
|
||||
relaxed_ci.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
|
||||
res = vmaCreateBuffer(allocator, &ci, &relaxed_ci, &handle, &allocation, &alloc_info);
|
||||
|
||||
if (res != VK_SUCCESS &&
|
||||
(relaxed_ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
|
||||
relaxed_ci.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
res = vmaCreateBuffer(allocator, &ci, &relaxed_ci, &handle, &allocation,
|
||||
&alloc_info);
|
||||
}
|
||||
}
|
||||
|
||||
vk::Check(res);
|
||||
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
|
||||
|
||||
// Log GPU memory allocation for buffers
|
||||
@@ -299,77 +220,4 @@ namespace Vulkan {
|
||||
device.GetDispatchLoader());
|
||||
}
|
||||
|
||||
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage)
|
||||
{
|
||||
const auto vma_usage = MemoryUsageVma(usage);
|
||||
VmaAllocationCreateInfo ci{};
|
||||
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
|
||||
ci.usage = vma_usage;
|
||||
ci.memoryTypeBits = reqs.memoryTypeBits & valid_memory_types;
|
||||
ci.requiredFlags = 0;
|
||||
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
|
||||
|
||||
VmaAllocation a{};
|
||||
VmaAllocationInfo info{};
|
||||
|
||||
VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info);
|
||||
|
||||
if (res != VK_SUCCESS) {
|
||||
// Relax 1: drop budget constraint
|
||||
auto ci2 = ci;
|
||||
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
|
||||
res = vmaAllocateMemory(allocator, &reqs, &ci2, &a, &info);
|
||||
|
||||
// Relax 2: if we preferred DEVICE_LOCAL, drop that preference
|
||||
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
|
||||
auto ci3 = ci2;
|
||||
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
res = vmaAllocateMemory(allocator, &reqs, &ci3, &a, &info);
|
||||
}
|
||||
}
|
||||
|
||||
vk::Check(res);
|
||||
return MemoryCommit(allocator, a, info);
|
||||
}
|
||||
|
||||
MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) {
|
||||
// Allocate memory appropriate for this buffer automatically
|
||||
const auto vma_usage = MemoryUsageVma(usage);
|
||||
|
||||
VmaAllocationCreateInfo ci{};
|
||||
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
|
||||
ci.usage = vma_usage;
|
||||
ci.requiredFlags = 0;
|
||||
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
|
||||
ci.pool = VK_NULL_HANDLE;
|
||||
ci.pUserData = nullptr;
|
||||
ci.priority = 0.0f;
|
||||
|
||||
const VkBuffer raw = *buffer;
|
||||
|
||||
VmaAllocation a{};
|
||||
VmaAllocationInfo info{};
|
||||
|
||||
// Let VMA infer memory requirements from the buffer
|
||||
VkResult res = vmaAllocateMemoryForBuffer(allocator, raw, &ci, &a, &info);
|
||||
|
||||
if (res != VK_SUCCESS) {
|
||||
auto ci2 = ci;
|
||||
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
|
||||
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci2, &a, &info);
|
||||
|
||||
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
|
||||
auto ci3 = ci2;
|
||||
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci3, &a, &info);
|
||||
}
|
||||
}
|
||||
|
||||
vk::Check(res);
|
||||
vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr));
|
||||
return MemoryCommit(allocator, a, info);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -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
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
@@ -39,51 +40,6 @@ namespace Vulkan {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ownership handle of a memory commitment (real VMA allocation).
|
||||
class MemoryCommit {
|
||||
public:
|
||||
MemoryCommit() noexcept = default;
|
||||
|
||||
MemoryCommit(VmaAllocator allocator, VmaAllocation allocation,
|
||||
const VmaAllocationInfo &info) noexcept;
|
||||
|
||||
~MemoryCommit();
|
||||
|
||||
MemoryCommit(const MemoryCommit &) = delete;
|
||||
|
||||
MemoryCommit &operator=(const MemoryCommit &) = delete;
|
||||
|
||||
MemoryCommit(MemoryCommit &&) noexcept;
|
||||
|
||||
MemoryCommit &operator=(MemoryCommit &&) noexcept;
|
||||
|
||||
[[nodiscard]] std::span<u8> Map();
|
||||
|
||||
[[nodiscard]] std::span<const u8> Map() const;
|
||||
|
||||
void Unmap();
|
||||
|
||||
explicit operator bool() const noexcept { return allocation != nullptr; }
|
||||
|
||||
VkDeviceMemory Memory() const noexcept { return memory; }
|
||||
|
||||
VkDeviceSize Offset() const noexcept { return offset; }
|
||||
|
||||
VkDeviceSize Size() const noexcept { return size; }
|
||||
|
||||
VmaAllocation Allocation() const noexcept { return allocation; }
|
||||
|
||||
private:
|
||||
void Release();
|
||||
|
||||
VmaAllocator allocator{}; ///< VMA allocator
|
||||
VmaAllocation allocation{}; ///< VMA allocation handle
|
||||
VkDeviceMemory memory{}; ///< Underlying VkDeviceMemory chosen by VMA
|
||||
VkDeviceSize offset{}; ///< Offset of this allocation inside VkDeviceMemory
|
||||
VkDeviceSize size{}; ///< Size of the allocation
|
||||
void *mapped_ptr{}; ///< Optional persistent mapped pointer
|
||||
};
|
||||
|
||||
/// Memory allocator container.
|
||||
/// Allocates and releases memory allocations on demand.
|
||||
class MemoryAllocator {
|
||||
@@ -107,36 +63,21 @@ namespace Vulkan {
|
||||
|
||||
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const;
|
||||
|
||||
/**
|
||||
* Commits a memory with the specified requirements.
|
||||
*
|
||||
* @param requirements Requirements returned from a Vulkan call.
|
||||
* @param usage Indicates how the memory will be used.
|
||||
*
|
||||
* @returns A memory commit.
|
||||
*/
|
||||
MemoryCommit Commit(const VkMemoryRequirements &requirements, MemoryUsage usage);
|
||||
using ReclaimCallback = std::function<u64(u64)>;
|
||||
|
||||
/// Commits memory required by the buffer and binds it (for buffers created outside VMA).
|
||||
MemoryCommit Commit(const vk::Buffer &buffer, MemoryUsage usage);
|
||||
void SetReclaimCallback(ReclaimCallback callback);
|
||||
|
||||
private:
|
||||
static bool IsAutoUsage(VmaMemoryUsage u) noexcept {
|
||||
switch (u) {
|
||||
case VMA_MEMORY_USAGE_AUTO:
|
||||
case VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE:
|
||||
case VMA_MEMORY_USAGE_AUTO_PREFER_HOST:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ReclaimAtLeast(u64 hint_bytes) const;
|
||||
|
||||
static constexpr u64 IMAGE_RECLAIM_HINT = 64ULL * 1024 * 1024;
|
||||
|
||||
const Device &device; ///< Device handle.
|
||||
VmaAllocator allocator; ///< VMA allocator.
|
||||
const VkPhysicalDeviceMemoryProperties properties; ///< Physical device memory properties.
|
||||
VkDeviceSize buffer_image_granularity; ///< Adjacent buffer/image granularity
|
||||
u32 valid_memory_types{~0u};
|
||||
ReclaimCallback reclaim_callback;
|
||||
mutable bool in_reclaim{false};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging.h"
|
||||
#include "video_core/vulkan_common/vk_enum_string_helper.h"
|
||||
@@ -20,6 +25,60 @@ namespace Vulkan::vk {
|
||||
|
||||
namespace {
|
||||
|
||||
std::thread::id allocator_owner_thread;
|
||||
|
||||
template <typename HandleType>
|
||||
struct PendingRelease {
|
||||
VmaAllocator allocator;
|
||||
HandleType handle;
|
||||
VmaAllocation allocation;
|
||||
u64 timeline;
|
||||
};
|
||||
|
||||
std::mutex deletion_mutex;
|
||||
std::atomic<u64> deletion_timeline{1};
|
||||
std::vector<PendingRelease<VkImage>> pending_images;
|
||||
std::vector<PendingRelease<VkBuffer>> pending_buffers;
|
||||
|
||||
template <typename HandleType>
|
||||
void PushPendingRelease(std::vector<PendingRelease<HandleType>>& pending, VmaAllocator allocator,
|
||||
HandleType handle, VmaAllocation allocation) noexcept {
|
||||
std::scoped_lock lock{deletion_mutex};
|
||||
pending.push_back(PendingRelease<HandleType>{
|
||||
.allocator = allocator,
|
||||
.handle = handle,
|
||||
.allocation = allocation,
|
||||
.timeline = deletion_timeline.load(std::memory_order_acquire),
|
||||
});
|
||||
}
|
||||
|
||||
template <typename HandleType>
|
||||
void ExtractReleased(std::vector<PendingRelease<HandleType>>& pending,
|
||||
std::vector<PendingRelease<HandleType>>& released, u64 completed_value) {
|
||||
const auto split = std::partition(pending.begin(), pending.end(),
|
||||
[completed_value](const PendingRelease<HandleType>& entry) {
|
||||
return entry.timeline > completed_value;
|
||||
});
|
||||
released.assign(split, pending.end());
|
||||
pending.erase(split, pending.end());
|
||||
}
|
||||
|
||||
void DrainDeletionQueue(u64 completed_value) noexcept {
|
||||
std::vector<PendingRelease<VkImage>> images;
|
||||
std::vector<PendingRelease<VkBuffer>> buffers;
|
||||
{
|
||||
std::scoped_lock lock{deletion_mutex};
|
||||
ExtractReleased(pending_images, images, completed_value);
|
||||
ExtractReleased(pending_buffers, buffers, completed_value);
|
||||
}
|
||||
for (const auto& entry : images) {
|
||||
vmaDestroyImage(entry.allocator, entry.handle, entry.allocation);
|
||||
}
|
||||
for (const auto& entry : buffers) {
|
||||
vmaDestroyBuffer(entry.allocator, entry.handle, entry.allocation);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld,
|
||||
Func&& func) {
|
||||
@@ -502,13 +561,35 @@ DebugReportCallback Instance::CreateDebugReportCallback(
|
||||
return DebugReportCallback(object, handle, *dld);
|
||||
}
|
||||
|
||||
void SetAllocatorOwnerThread() {
|
||||
allocator_owner_thread = std::this_thread::get_id();
|
||||
}
|
||||
|
||||
bool OnAllocatorOwnerThread() noexcept {
|
||||
return allocator_owner_thread == std::thread::id{} ||
|
||||
allocator_owner_thread == std::this_thread::get_id();
|
||||
}
|
||||
|
||||
void SetDeletionTimeline(u64 value) noexcept {
|
||||
deletion_timeline.store(value, std::memory_order_release);
|
||||
}
|
||||
|
||||
void TickDeletionQueue(u64 completed_value) noexcept {
|
||||
DEBUG_ASSERT(OnAllocatorOwnerThread());
|
||||
DrainDeletionQueue(completed_value);
|
||||
}
|
||||
|
||||
void FlushDeletionQueue() noexcept {
|
||||
DrainDeletionQueue((std::numeric_limits<u64>::max)());
|
||||
}
|
||||
|
||||
void Image::SetObjectNameEXT(const char* name) const {
|
||||
SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE, name);
|
||||
}
|
||||
|
||||
void Image::Release() const noexcept {
|
||||
if (handle) {
|
||||
vmaDestroyImage(allocator, handle, allocation);
|
||||
PushPendingRelease(pending_images, allocator, handle, allocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +611,7 @@ void Buffer::SetObjectNameEXT(const char* name) const {
|
||||
|
||||
void Buffer::Release() const noexcept {
|
||||
if (handle) {
|
||||
vmaDestroyBuffer(allocator, handle, allocation);
|
||||
PushPendingRelease(pending_buffers, allocator, handle, allocation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,16 @@ private:
|
||||
VkResult result;
|
||||
};
|
||||
|
||||
void SetAllocatorOwnerThread();
|
||||
|
||||
[[nodiscard]] bool OnAllocatorOwnerThread() noexcept;
|
||||
|
||||
void SetDeletionTimeline(u64 value) noexcept;
|
||||
|
||||
void TickDeletionQueue(u64 completed_value) noexcept;
|
||||
|
||||
void FlushDeletionQueue() noexcept;
|
||||
|
||||
/// Throws a Vulkan exception if result is not success.
|
||||
inline void Check(VkResult result) {
|
||||
if (result != VK_SUCCESS) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user