Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36d581ec73 | |||
| 982be246ab | |||
| 3565e32f4d | |||
| d1ba4a2db2 | |||
| 50d0cc2716 | |||
| 4dc2f5a341 | |||
| eed0d1f33b | |||
| f216a9432b | |||
| 5ed87152c3 | |||
| 373bc6512d | |||
| 496327490f | |||
| 1b09d6628b | |||
| 809e5fd523 | |||
| d0b1f2bd05 | |||
| d8d9bb0dfb | |||
| be6844c1ed | |||
| c3fe071723 | |||
| 17fff10e06 | |||
| 8593e6f1e6 | |||
| 20f474b09a | |||
| c1a9fa9db7 | |||
| d6b51e5e21 | |||
| 95b4c78b07 | |||
| 4ec7d79174 | |||
| 36aacf62ad | |||
| 0308a2679e | |||
| 255f8d22d7 | |||
| 791d3d1bea | |||
| f9b940a442 | |||
| 2fa207058b | |||
| 0394893354 | |||
| 76b2313b25 | |||
| cf0b9d1de2 | |||
| 81b1b71993 | |||
| faf57c183f | |||
| 0e2c1a5739 | |||
| 698add8541 | |||
| c9c8537643 | |||
| 1ca9a13e50 | |||
| 15cc561d12 | |||
| 0a39163a90 | |||
| 2a7a65c944 | |||
| ba3af04da1 | |||
| 4009ae1da2 | |||
| cf116a28a6 | |||
| 0485b8e84b | |||
| 2a3d3d3895 | |||
| 83ac715e76 | |||
| 2298508465 | |||
| 569a1962c0 | |||
| 865dd615ca | |||
| ad8aab915b | |||
| 03da34b330 | |||
| 9a06b85b24 | |||
| e704da9192 | |||
| 3870ba670f | |||
| 8a83e3412a | |||
| fd5ef1970c | |||
| bb18a6533d | |||
| 1a5eceeb9c | |||
| 0b172d12c0 | |||
| 719a6dd5a1 | |||
| 36250a4730 | |||
| 3522fc019c | |||
| abb0124b84 | |||
| c5b517aa5f | |||
| ca6f47c686 | |||
| 0dd98842bf | |||
| 954ad2a61e | |||
| d8ad6aa187 | |||
| a11bc4a382 | |||
| 5b1efe522e | |||
| 973bf306ed | |||
| 92942fe01b | |||
| 74790e4f33 | |||
| 0f48292de1 | |||
| 5fc8393125 | |||
| f9bfeaa2bc | |||
| b2955479e5 | |||
| c4ff7ecf51 | |||
| 3d89398b84 | |||
| f611506dca | |||
| df8a2e3ad8 | |||
| c7c99905f4 |
@@ -18,19 +18,20 @@ cd ..
|
||||
mkdir package
|
||||
|
||||
if [ -d "/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/" ]; then
|
||||
QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/'
|
||||
QT_PLUGINS_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins'
|
||||
else
|
||||
#fallback to qt
|
||||
QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins/platforms/'
|
||||
QT_PLUGINS_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins'
|
||||
fi
|
||||
|
||||
find build/ -name "yuzu*.exe" -exec cp {} 'package' \;
|
||||
|
||||
# copy Qt plugins
|
||||
mkdir package/platforms
|
||||
cp "${QT_PLATFORM_DLL_PATH}/qwindows.dll" package/platforms/
|
||||
cp -rv "${QT_PLATFORM_DLL_PATH}/../mediaservice/" package/
|
||||
cp -rv "${QT_PLATFORM_DLL_PATH}/../imageformats/" package/
|
||||
cp -v "${QT_PLUGINS_PATH}/platforms/qwindows.dll" package/platforms/
|
||||
cp -rv "${QT_PLUGINS_PATH}/mediaservice/" package/
|
||||
cp -rv "${QT_PLUGINS_PATH}/imageformats/" package/
|
||||
cp -rv "${QT_PLUGINS_PATH}/styles/" package/
|
||||
rm -f package/mediaservice/*d.dll
|
||||
|
||||
for i in package/*.exe; do
|
||||
|
||||
+4
-1
@@ -18,7 +18,7 @@
|
||||
url = https://github.com/libusb/libusb.git
|
||||
[submodule "discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/discordapp/discord-rpc.git
|
||||
url = https://github.com/discord/discord-rpc.git
|
||||
[submodule "Vulkan-Headers"]
|
||||
path = externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
@@ -43,3 +43,6 @@
|
||||
[submodule "SDL"]
|
||||
path = externals/SDL
|
||||
url = https://github.com/libsdl-org/SDL.git
|
||||
[submodule "externals/cpp-httplib"]
|
||||
path = externals/cpp-httplib
|
||||
url = https://github.com/yhirose/cpp-httplib.git
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ macro(yuzu_find_packages)
|
||||
set(REQUIRED_LIBS
|
||||
# Cmake Pkg Prefix Version Conan Pkg
|
||||
"Catch2 2.13 catch2/2.13.0"
|
||||
"fmt 7.1 fmt/7.1.2"
|
||||
"fmt 8.0 fmt/8.0.0"
|
||||
"lz4 1.8 lz4/1.9.2"
|
||||
"nlohmann_json 3.8 nlohmann_json/3.8.0"
|
||||
"ZLIB 1.2 zlib/1.2.11"
|
||||
|
||||
@@ -1,43 +1,80 @@
|
||||
yuzu emulator
|
||||
=============
|
||||
[](https://travis-ci.com/yuzu-emu/yuzu)
|
||||
[](https://dev.azure.com/yuzu-emu/yuzu/)
|
||||
[](https://discord.com/invite/u77vRWY)
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://yuzu-emu.org/"><img src="https://raw.githubusercontent.com/yuzu-emu/yuzu-assets/master/icons/icon.png" alt="yuzu" width="200"></a>
|
||||
<br>
|
||||
<b>yuzu</b>
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/).
|
||||
<h4 align="center"><b>yuzu</b> is the world's most popular, open-source, Nintendo Switch emulator — started by the creators of <a href="https://citra-emu.org" target="_blank">Citra</a>.
|
||||
<br>
|
||||
It is written in C++ with portability in mind, and we actively maintain builds for Windows and Linux.
|
||||
</h4>
|
||||
|
||||
It is written in C++ with portability in mind, with builds actively maintained for Windows and Linux. The emulator is capable of running several commercial games.
|
||||
<p align="center">
|
||||
<a href="https://dev.azure.com/yuzu-emu/yuzu/">
|
||||
<img src="https://dev.azure.com/yuzu-emu/yuzu/_apis/build/status/yuzu%20mainline?branchName=master"
|
||||
alt="Azure Mainline CI Build Status">
|
||||
</a>
|
||||
<a href="https://discord.com/invite/u77vRWY">
|
||||
<img src="https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
yuzu only emulates a subset of Switch hardware and therefore most commercial games **do not** run at full speed or are not fully functional.
|
||||
<p align="center">
|
||||
<a href="#compatibility">Compatibility</a> |
|
||||
<a href="#development">Development</a> |
|
||||
<a href="#building">Building</a> |
|
||||
<a href="#download">Download</a> |
|
||||
<a href="#support">Support</a> |
|
||||
<a href="#license">License</a>
|
||||
</p>
|
||||
|
||||
Do you want to check which games are compatible and which ones are not? Please visit our [Compatibility page](https://yuzu-emu.org/game/)!
|
||||
## Compatibility
|
||||
|
||||
yuzu is licensed under the GPLv2 (or any later version). Refer to the license.txt file included.
|
||||
The emulator is capable of running most commercial games at full speed, provided you meet the [necessary hardware requirements](https://yuzu-emu.org/help/quickstart/#hardware-requirements).
|
||||
|
||||
Check out our [website](https://yuzu-emu.org/)!
|
||||
For a full list of games yuzu support, please visit our [Compatibility page](https://yuzu-emu.org/game/)
|
||||
|
||||
For development discussion, please join us on [Discord](https://discord.com/invite/u77vRWY).
|
||||
Check out our [website](https://yuzu-emu.org/) for the latest news on exciting features, monthly progress reports, and more!
|
||||
|
||||
### Development
|
||||
## Development
|
||||
|
||||
Most of the development happens on GitHub. It's also where [our central repository](https://github.com/yuzu-emu/yuzu) is hosted.
|
||||
Most of the development happens on GitHub. It's also where [our central repository](https://github.com/yuzu-emu/yuzu) is hosted. For development discussion, please join us on [Discord](https://discord.com/invite/u77vRWY).
|
||||
|
||||
If you want to contribute please take a look at the [Contributor's Guide](https://github.com/yuzu-emu/yuzu/wiki/Contributing) and [Developer Information](https://github.com/yuzu-emu/yuzu/wiki/Developer-Information). You should also contact any of the developers on Discord in order to know about the current state of the emulator.
|
||||
If you want to contribute, please take a look at the [Contributor's Guide](https://github.com/yuzu-emu/yuzu/wiki/Contributing) and [Developer Information](https://github.com/yuzu-emu/yuzu/wiki/Developer-Information).
|
||||
You can also contact any of the developers on Discord in order to know about the current state of the emulator.
|
||||
|
||||
If you want to contribute to the user interface translation, please check out the [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu). We centralize translation work there, and periodically upstream translations.
|
||||
If you want to contribute to the user interface translation project, please check out the [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu). We centralize translation work there, and periodically upstream translations.
|
||||
|
||||
### Building
|
||||
## Building
|
||||
|
||||
* __Windows__: [Windows Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Windows)
|
||||
* __Linux__: [Linux Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Linux)
|
||||
|
||||
## Download
|
||||
|
||||
### Support
|
||||
We happily accept monetary donations, or donated games and hardware. Please see our [donations page](https://yuzu-emu.org/donate/) for more information on how you can contribute to yuzu. Any donations received will go towards things like:
|
||||
You can download the latest releases automatically via the installer on our [downloads](https://yuzu-emu.org/downloads/) page.
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
If you enjoy the project and want to support us financially, check out our Patreon!
|
||||
|
||||
<a href="https://www.patreon.com/yuzuteam">
|
||||
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
|
||||
</a>
|
||||
|
||||
Any donations received will go towards things like:
|
||||
* Switch consoles to explore and reverse-engineer the hardware
|
||||
* Switch games for testing, reverse-engineering, and implementing new features
|
||||
* Web hosting and infrastructure setup
|
||||
* Software licenses (e.g. Visual Studio, IDA Pro, etc.)
|
||||
* Additional hardware (e.g. GPUs as-needed to improve rendering support, other peripherals to add support for, etc.)
|
||||
|
||||
We also more than gladly accept used Switch consoles, preferably ones with firmware 3.0.0 or lower! If you would like to give yours away, don't hesitate to join our [Discord](https://discord.gg/VXqngT3) and talk to bunnei. You may also contact: donations@yuzu-emu.org.
|
||||
If you wish to support us a different way, please join our [Discord](https://discord.gg/u77vRWY) and talk to bunnei. You may also contact: donations@yuzu-emu.org.
|
||||
|
||||
## License
|
||||
|
||||
yuzu is licensed under the GPLv2 (or any later version). Refer to the [license.txt](https://github.com/yuzu-emu/yuzu/blob/master/license.txt) file.
|
||||
|
||||
Vendored
+3
-3
@@ -53,10 +53,10 @@ endif()
|
||||
# SDL2
|
||||
if (NOT SDL2_FOUND AND ENABLE_SDL2)
|
||||
if (NOT WIN32)
|
||||
# Yuzu itself needs: Events Joystick Haptic Sensor Timers
|
||||
# Yuzu itself needs: Events Joystick Haptic Sensor Timers Audio
|
||||
# Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
|
||||
set(SDL_UNUSED_SUBSYSTEMS
|
||||
Atomic Audio Render Power Threads
|
||||
Atomic Render Power Threads
|
||||
File CPUinfo Filesystem Locale)
|
||||
foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
|
||||
string(TOUPPER ${_SUB} _OPT)
|
||||
@@ -115,7 +115,7 @@ if (ENABLE_WEB_SERVICE)
|
||||
|
||||
# httplib
|
||||
add_library(httplib INTERFACE)
|
||||
target_include_directories(httplib INTERFACE ./httplib)
|
||||
target_include_directories(httplib INTERFACE ./cpp-httplib)
|
||||
target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||
if (WIN32)
|
||||
|
||||
+1
Submodule externals/cpp-httplib added at 9648f950f5
Vendored
+1
-1
Submodule externals/discord-rpc updated: e32d001809...963aa9f3e5
Vendored
+1
-1
Submodule externals/dynarmic updated: 0c12614d1a...7946868af4
Vendored
-15
@@ -1,15 +0,0 @@
|
||||
From https://github.com/yhirose/cpp-httplib/tree/ff5677ad197947177c158fe857caff4f0e242045 with https://github.com/yhirose/cpp-httplib/pull/701
|
||||
|
||||
MIT License
|
||||
|
||||
===
|
||||
|
||||
cpp-httplib
|
||||
|
||||
A C++11 header-only HTTP library.
|
||||
|
||||
It's extremely easy to setup. Just include httplib.h file in your code!
|
||||
|
||||
Inspired by Sinatra and express.
|
||||
|
||||
© 2017 Yuji Hirose
|
||||
Vendored
-6714
File diff suppressed because it is too large
Load Diff
@@ -42,6 +42,7 @@ add_library(audio_core STATIC
|
||||
voice_context.h
|
||||
|
||||
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
|
||||
$<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h>
|
||||
)
|
||||
|
||||
create_target_directory_groups(audio_core)
|
||||
@@ -71,3 +72,7 @@ if(ENABLE_CUBEB)
|
||||
target_link_libraries(audio_core PRIVATE cubeb)
|
||||
target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
|
||||
endif()
|
||||
if(ENABLE_SDL2)
|
||||
target_link_libraries(audio_core PRIVATE SDL2)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
||||
endif()
|
||||
|
||||
@@ -15,7 +15,7 @@ constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
|
||||
constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
|
||||
} // namespace Audren
|
||||
|
||||
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
|
||||
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '9');
|
||||
constexpr std::size_t MAX_MIX_BUFFERS = 24;
|
||||
constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
|
||||
constexpr std::size_t MAX_CHANNEL_COUNT = 6;
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include "audio_core/sdl2_sink.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "audio_core/time_stretch.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
//#include "common/settings.h"
|
||||
|
||||
// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
|
||||
#endif
|
||||
#include <SDL.h>
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class SDLSinkStream final : public SinkStream {
|
||||
public:
|
||||
SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device)
|
||||
: num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, num_channels} {
|
||||
|
||||
SDL_AudioSpec spec;
|
||||
spec.freq = sample_rate;
|
||||
spec.channels = static_cast<u8>(num_channels);
|
||||
spec.format = AUDIO_S16SYS;
|
||||
spec.samples = 4096;
|
||||
spec.callback = nullptr;
|
||||
|
||||
SDL_AudioSpec obtained;
|
||||
if (output_device.empty()) {
|
||||
dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0);
|
||||
} else {
|
||||
dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0);
|
||||
}
|
||||
|
||||
if (dev == 0) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_PauseAudioDevice(dev, 0);
|
||||
}
|
||||
|
||||
~SDLSinkStream() override {
|
||||
if (dev == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(dev);
|
||||
}
|
||||
|
||||
void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
|
||||
if (source_num_channels > num_channels) {
|
||||
// Downsample 6 channels to 2
|
||||
ASSERT_MSG(source_num_channels == 6, "Channel count must be 6");
|
||||
|
||||
std::vector<s16> buf;
|
||||
buf.reserve(samples.size() * num_channels / source_num_channels);
|
||||
for (std::size_t i = 0; i < samples.size(); i += source_num_channels) {
|
||||
// Downmixing implementation taken from the ATSC standard
|
||||
const s16 left{samples[i + 0]};
|
||||
const s16 right{samples[i + 1]};
|
||||
const s16 center{samples[i + 2]};
|
||||
const s16 surround_left{samples[i + 4]};
|
||||
const s16 surround_right{samples[i + 5]};
|
||||
// Not used in the ATSC reference implementation
|
||||
[[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
|
||||
|
||||
constexpr s32 clev{707}; // center mixing level coefficient
|
||||
constexpr s32 slev{707}; // surround mixing level coefficient
|
||||
|
||||
buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
|
||||
(slev * surround_left / 1000)));
|
||||
buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
|
||||
(slev * surround_right / 1000)));
|
||||
}
|
||||
int ret = SDL_QueueAudio(dev, static_cast<const void*>(buf.data()),
|
||||
static_cast<u32>(buf.size() * sizeof(s16)));
|
||||
if (ret < 0)
|
||||
LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = SDL_QueueAudio(dev, static_cast<const void*>(samples.data()),
|
||||
static_cast<u32>(samples.size() * sizeof(s16)));
|
||||
if (ret < 0)
|
||||
LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
std::size_t SamplesInQueue(u32 channel_count) const override {
|
||||
if (dev == 0)
|
||||
return 0;
|
||||
|
||||
return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16));
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
should_flush = true;
|
||||
}
|
||||
|
||||
u32 GetNumChannels() const {
|
||||
return num_channels;
|
||||
}
|
||||
|
||||
private:
|
||||
SDL_AudioDeviceID dev = 0;
|
||||
u32 num_channels{};
|
||||
std::atomic<bool> should_flush{};
|
||||
TimeStretcher time_stretch;
|
||||
};
|
||||
|
||||
SDLSink::SDLSink(std::string_view target_device_name) {
|
||||
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
|
||||
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (target_device_name != auto_device_name && !target_device_name.empty()) {
|
||||
output_device = target_device_name;
|
||||
} else {
|
||||
output_device.clear();
|
||||
}
|
||||
}
|
||||
|
||||
SDLSink::~SDLSink() = default;
|
||||
|
||||
SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) {
|
||||
sink_streams.push_back(
|
||||
std::make_unique<SDLSinkStream>(sample_rate, num_channels, output_device));
|
||||
return *sink_streams.back();
|
||||
}
|
||||
|
||||
std::vector<std::string> ListSDLSinkDevices() {
|
||||
std::vector<std::string> device_list;
|
||||
|
||||
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
|
||||
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const int device_count = SDL_GetNumAudioDevices(0);
|
||||
for (int i = 0; i < device_count; ++i) {
|
||||
device_list.emplace_back(SDL_GetAudioDeviceName(i, 0));
|
||||
}
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/sink.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class SDLSink final : public Sink {
|
||||
public:
|
||||
explicit SDLSink(std::string_view device_id);
|
||||
~SDLSink() override;
|
||||
|
||||
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
|
||||
const std::string& name) override;
|
||||
|
||||
private:
|
||||
std::string output_device;
|
||||
std::vector<SinkStreamPtr> sink_streams;
|
||||
};
|
||||
|
||||
std::vector<std::string> ListSDLSinkDevices();
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "audio_core/common.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/cubeb_sink.h"
|
||||
#endif
|
||||
#ifdef HAVE_SDL2
|
||||
#include "audio_core/sdl2_sink.h"
|
||||
#endif
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
@@ -35,6 +38,13 @@ constexpr SinkDetails sink_details[] = {
|
||||
return std::make_unique<CubebSink>(device_id);
|
||||
},
|
||||
&ListCubebSinkDevices},
|
||||
#endif
|
||||
#ifdef HAVE_SDL2
|
||||
SinkDetails{"sdl2",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<SDLSink>(device_id);
|
||||
},
|
||||
&ListSDLSinkDevices},
|
||||
#endif
|
||||
SinkDetails{"null",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
|
||||
@@ -107,9 +107,12 @@ void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
|
||||
active_buffer = queued_buffers.front();
|
||||
queued_buffers.pop();
|
||||
|
||||
VolumeAdjustSamples(active_buffer->GetSamples(), game_volume);
|
||||
auto& samples = active_buffer->GetSamples();
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
|
||||
VolumeAdjustSamples(samples, game_volume);
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), samples);
|
||||
played_samples += samples.size();
|
||||
|
||||
const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer);
|
||||
|
||||
|
||||
@@ -89,6 +89,11 @@ public:
|
||||
return sample_rate;
|
||||
}
|
||||
|
||||
/// Gets the number of samples played so far
|
||||
[[nodiscard]] u64 GetPlayedSampleCount() const {
|
||||
return played_samples;
|
||||
}
|
||||
|
||||
/// Gets the number of channels
|
||||
[[nodiscard]] u32 GetNumChannels() const;
|
||||
|
||||
@@ -106,6 +111,7 @@ private:
|
||||
[[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const;
|
||||
|
||||
u32 sample_rate; ///< Sample rate of the stream
|
||||
u64 played_samples{}; ///< The current played sample count
|
||||
Format format; ///< Format of the stream
|
||||
float game_volume = 1.0f; ///< The volume the game currently has set
|
||||
ReleaseCallback release_callback; ///< Buffer release callback for the stream
|
||||
|
||||
@@ -24,6 +24,7 @@ enum : u64 {
|
||||
Size_128_MB = 128ULL * Size_1_MB,
|
||||
Size_448_MB = 448ULL * Size_1_MB,
|
||||
Size_507_MB = 507ULL * Size_1_MB,
|
||||
Size_512_MB = 512ULL * Size_1_MB,
|
||||
Size_562_MB = 562ULL * Size_1_MB,
|
||||
Size_1554_MB = 1554ULL * Size_1_MB,
|
||||
Size_2048_MB = 2048ULL * Size_1_MB,
|
||||
|
||||
@@ -21,6 +21,8 @@ void DetachedTasks::WaitForAllTasks() {
|
||||
}
|
||||
|
||||
DetachedTasks::~DetachedTasks() {
|
||||
WaitForAllTasks();
|
||||
|
||||
std::unique_lock lock{mutex};
|
||||
ASSERT(count == 0);
|
||||
instance = nullptr;
|
||||
|
||||
@@ -172,7 +172,7 @@ std::string ReadStringFromFile(const std::filesystem::path& path, FileType type)
|
||||
|
||||
size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string) {
|
||||
if (!IsFile(path)) {
|
||||
if (Exists(path) && !IsFile(path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -183,11 +183,7 @@ size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
|
||||
|
||||
size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string) {
|
||||
if (!Exists(path)) {
|
||||
return WriteStringToFile(path, type, string);
|
||||
}
|
||||
|
||||
if (!IsFile(path)) {
|
||||
if (Exists(path) && !IsFile(path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::op
|
||||
|
||||
/**
|
||||
* Reads an entire file at path and returns a string of the contents read from the file.
|
||||
* If the filesystem object at path is not a file, this function returns an empty string.
|
||||
* If the filesystem object at path is not a regular file, this function returns an empty string.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
@@ -71,8 +71,9 @@ template <typename Path>
|
||||
|
||||
/**
|
||||
* Writes a string to a file at path and returns the number of characters successfully written.
|
||||
* If an file already exists at path, its contents will be erased.
|
||||
* If the filesystem object at path is not a file, this function returns 0.
|
||||
* If a file already exists at path, its contents will be erased.
|
||||
* If a file does not exist at path, it creates and opens a new empty file for writing.
|
||||
* If the filesystem object at path exists and is not a regular file, this function returns 0.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
@@ -95,8 +96,8 @@ template <typename Path>
|
||||
|
||||
/**
|
||||
* Appends a string to a file at path and returns the number of characters successfully written.
|
||||
* If a file does not exist at path, WriteStringToFile is called instead.
|
||||
* If the filesystem object at path is not a file, this function returns 0.
|
||||
* If a file does not exist at path, it creates and opens a new empty file for appending.
|
||||
* If the filesystem object at path exists and is not a regular file, this function returns 0.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
@@ -395,11 +396,11 @@ public:
|
||||
[[nodiscard]] size_t WriteString(std::span<const char> string) const;
|
||||
|
||||
/**
|
||||
* Flushes any unwritten buffered data into the file.
|
||||
* Attempts to flush any unwritten buffered data into the file and flush the file into the disk.
|
||||
*
|
||||
* @returns True if the flush was successful, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool Flush() const;
|
||||
bool Flush() const;
|
||||
|
||||
/**
|
||||
* Resizes the file to a given size.
|
||||
|
||||
@@ -135,8 +135,9 @@ std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, File
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!IsFile(path)) {
|
||||
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file",
|
||||
if (Exists(path) && !IsFile(path)) {
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Filesystem object at path={} exists and is not a regular file",
|
||||
PathToUTF8String(path));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
+16
-14
@@ -48,18 +48,18 @@ template <typename Path>
|
||||
*
|
||||
* Failures occur when:
|
||||
* - Input path is not valid
|
||||
* - Filesystem object at path is not a file
|
||||
* - Filesystem object at path is not a regular file
|
||||
* - Filesystem at path is read only
|
||||
*
|
||||
* @param path Filesystem path
|
||||
*
|
||||
* @returns True if file removal succeeds or file does not exist, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool RemoveFile(const std::filesystem::path& path);
|
||||
bool RemoveFile(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool RemoveFile(const Path& path) {
|
||||
bool RemoveFile(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return RemoveFile(ToU8String(path));
|
||||
} else {
|
||||
@@ -74,7 +74,7 @@ template <typename Path>
|
||||
* Failures occur when:
|
||||
* - One or both input path(s) is not valid
|
||||
* - Filesystem object at old_path does not exist
|
||||
* - Filesystem object at old_path is not a file
|
||||
* - Filesystem object at old_path is not a regular file
|
||||
* - Filesystem object at new_path exists
|
||||
* - Filesystem at either path is read only
|
||||
*
|
||||
@@ -110,8 +110,8 @@ template <typename Path1, typename Path2>
|
||||
*
|
||||
* Failures occur when:
|
||||
* - Input path is not valid
|
||||
* - Filesystem object at path is not a file
|
||||
* - The file is not opened
|
||||
* - Filesystem object at path exists and is not a regular file
|
||||
* - The file is not open
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param mode File access mode
|
||||
@@ -251,11 +251,11 @@ template <typename Path>
|
||||
*
|
||||
* @returns True if directory removal succeeds or directory does not exist, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool RemoveDir(const std::filesystem::path& path);
|
||||
bool RemoveDir(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool RemoveDir(const Path& path) {
|
||||
bool RemoveDir(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return RemoveDir(ToU8String(path));
|
||||
} else {
|
||||
@@ -276,11 +276,11 @@ template <typename Path>
|
||||
*
|
||||
* @returns True if the directory and all of its contents are removed successfully, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path);
|
||||
bool RemoveDirRecursively(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool RemoveDirRecursively(const Path& path) {
|
||||
bool RemoveDirRecursively(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return RemoveDirRecursively(ToU8String(path));
|
||||
} else {
|
||||
@@ -301,11 +301,11 @@ template <typename Path>
|
||||
*
|
||||
* @returns True if all of the directory's contents are removed successfully, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path);
|
||||
bool RemoveDirContentsRecursively(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) {
|
||||
bool RemoveDirContentsRecursively(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return RemoveDirContentsRecursively(ToU8String(path));
|
||||
} else {
|
||||
@@ -435,11 +435,13 @@ template <typename Path>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns whether a filesystem object at path is a file.
|
||||
* Returns whether a filesystem object at path is a regular file.
|
||||
* A regular file is a file that stores text or binary data.
|
||||
* It is not a directory, symlink, FIFO, socket, block device, or character device.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
*
|
||||
* @returns True if a filesystem object at path is a file, false otherwise.
|
||||
* @returns True if a filesystem object at path is a regular file, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool IsFile(const std::filesystem::path& path);
|
||||
|
||||
|
||||
@@ -53,8 +53,9 @@ template <typename ContiguousContainer>
|
||||
std::string out;
|
||||
out.reserve(std::size(data) * pad_width);
|
||||
|
||||
const auto format_str = fmt::runtime(upper ? "{:02X}" : "{:02x}");
|
||||
for (const u8 c : data) {
|
||||
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
|
||||
out += fmt::format(format_str, c);
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
@@ -34,7 +34,7 @@ constexpr size_t HugePageSize = 0x200000;
|
||||
|
||||
// Manually imported for MinGW compatibility
|
||||
#ifndef MEM_RESERVE_PLACEHOLDER
|
||||
#define MEM_RESERVE_PLACEHOLDER 0x0004000
|
||||
#define MEM_RESERVE_PLACEHOLDER 0x00040000
|
||||
#endif
|
||||
#ifndef MEM_REPLACE_PLACEHOLDER
|
||||
#define MEM_REPLACE_PLACEHOLDER 0x00004000
|
||||
|
||||
@@ -159,7 +159,7 @@ FileBackend::FileBackend(const std::filesystem::path& filename) {
|
||||
|
||||
// Existence checks are done within the functions themselves.
|
||||
// We don't particularly care if these succeed or not.
|
||||
void(FS::RemoveFile(old_filename));
|
||||
FS::RemoveFile(old_filename);
|
||||
void(FS::RenameFile(filename, old_filename));
|
||||
|
||||
file =
|
||||
@@ -186,7 +186,7 @@ void FileBackend::Write(const Entry& entry) {
|
||||
|
||||
bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
|
||||
if (entry.log_level >= Level::Error) {
|
||||
void(file->Flush());
|
||||
file->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -55,9 +55,11 @@ void LogSettings() {
|
||||
log_setting("Renderer_UseAsynchronousGpuEmulation",
|
||||
values.use_asynchronous_gpu_emulation.GetValue());
|
||||
log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue());
|
||||
log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue());
|
||||
log_setting("Renderer_UseVsync", values.use_vsync.GetValue());
|
||||
log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue());
|
||||
log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
|
||||
log_setting("Renderer_UseGarbageCollection", values.use_caches_gc.GetValue());
|
||||
log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
|
||||
log_setting("Audio_OutputEngine", values.sink_id);
|
||||
log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
|
||||
@@ -121,6 +123,7 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||
values.cpu_accuracy.SetGlobal(true);
|
||||
values.cpuopt_unsafe_unfuse_fma.SetGlobal(true);
|
||||
values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true);
|
||||
values.cpuopt_unsafe_ignore_standard_fpcr.SetGlobal(true);
|
||||
values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true);
|
||||
values.cpuopt_unsafe_fastmem_check.SetGlobal(true);
|
||||
|
||||
@@ -135,10 +138,12 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||
values.gpu_accuracy.SetGlobal(true);
|
||||
values.use_asynchronous_gpu_emulation.SetGlobal(true);
|
||||
values.use_nvdec_emulation.SetGlobal(true);
|
||||
values.accelerate_astc.SetGlobal(true);
|
||||
values.use_vsync.SetGlobal(true);
|
||||
values.use_assembly_shaders.SetGlobal(true);
|
||||
values.use_asynchronous_shaders.SetGlobal(true);
|
||||
values.use_fast_gpu_time.SetGlobal(true);
|
||||
values.use_caches_gc.SetGlobal(true);
|
||||
values.bg_red.SetGlobal(true);
|
||||
values.bg_green.SetGlobal(true);
|
||||
values.bg_blue.SetGlobal(true);
|
||||
|
||||
@@ -129,6 +129,7 @@ struct Values {
|
||||
|
||||
Setting<bool> cpuopt_unsafe_unfuse_fma;
|
||||
Setting<bool> cpuopt_unsafe_reduce_fp_error;
|
||||
Setting<bool> cpuopt_unsafe_ignore_standard_fpcr;
|
||||
Setting<bool> cpuopt_unsafe_inaccurate_nan;
|
||||
Setting<bool> cpuopt_unsafe_fastmem_check;
|
||||
|
||||
@@ -147,10 +148,13 @@ struct Values {
|
||||
Setting<GPUAccuracy> gpu_accuracy;
|
||||
Setting<bool> use_asynchronous_gpu_emulation;
|
||||
Setting<bool> use_nvdec_emulation;
|
||||
Setting<bool> accelerate_astc;
|
||||
Setting<bool> use_vsync;
|
||||
Setting<bool> disable_fps_limit;
|
||||
Setting<bool> use_assembly_shaders;
|
||||
Setting<bool> use_asynchronous_shaders;
|
||||
Setting<bool> use_fast_gpu_time;
|
||||
Setting<bool> use_caches_gc;
|
||||
|
||||
Setting<float> bg_red;
|
||||
Setting<float> bg_green;
|
||||
@@ -218,6 +222,7 @@ struct Values {
|
||||
std::string program_args;
|
||||
bool dump_exefs;
|
||||
bool dump_nso;
|
||||
bool enable_fs_access_log;
|
||||
bool reporting_services;
|
||||
bool quest_flag;
|
||||
bool disable_macro_jit;
|
||||
|
||||
@@ -186,6 +186,9 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||
if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) {
|
||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
|
||||
}
|
||||
if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue()) {
|
||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
|
||||
}
|
||||
if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) {
|
||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
|
||||
}
|
||||
|
||||
+3
-2
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
@@ -377,7 +378,7 @@ struct System::Impl {
|
||||
std::unique_ptr<Core::DeviceMemory> device_memory;
|
||||
Core::Memory::Memory memory;
|
||||
CpuManager cpu_manager;
|
||||
bool is_powered_on = false;
|
||||
std::atomic_bool is_powered_on{};
|
||||
bool exit_lock = false;
|
||||
|
||||
Reporter reporter;
|
||||
@@ -463,7 +464,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||
}
|
||||
|
||||
bool System::IsPoweredOn() const {
|
||||
return impl->is_powered_on;
|
||||
return impl->is_powered_on.load(std::memory_order::relaxed);
|
||||
}
|
||||
|
||||
void System::PrepareReschedule() {
|
||||
|
||||
@@ -835,7 +835,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
"key_area_key_ocean_{:02X}",
|
||||
"key_area_key_system_{:02X}",
|
||||
};
|
||||
WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
|
||||
WriteKeyToFile(category, fmt::format(fmt::runtime(kak_names.at(field2)), field1), key);
|
||||
} else if (id == S128KeyType::Master) {
|
||||
WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Package1) {
|
||||
|
||||
@@ -58,14 +58,17 @@ static bool FollowsNcaIdFormat(std::string_view name) {
|
||||
|
||||
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
|
||||
bool within_two_digit, bool cnmt_suffix) {
|
||||
if (!within_two_digit)
|
||||
return fmt::format(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca",
|
||||
Common::HexToString(nca_id, second_hex_upper));
|
||||
if (!within_two_digit) {
|
||||
const auto format_str = fmt::runtime(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca");
|
||||
return fmt::format(format_str, Common::HexToString(nca_id, second_hex_upper));
|
||||
}
|
||||
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256_ret(nca_id.data(), nca_id.size(), hash.data(), 0);
|
||||
return fmt::format(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca", hash[0],
|
||||
Common::HexToString(nca_id, second_hex_upper));
|
||||
|
||||
const auto format_str =
|
||||
fmt::runtime(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca");
|
||||
return fmt::format(format_str, hash[0], Common::HexToString(nca_id, second_hex_upper));
|
||||
}
|
||||
|
||||
static std::string GetCNMTName(TitleType type, u64 title_id) {
|
||||
|
||||
@@ -24,17 +24,12 @@ constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) {
|
||||
case Mode::Read:
|
||||
return FS::FileAccessMode::Read;
|
||||
case Mode::Write:
|
||||
return FS::FileAccessMode::Write;
|
||||
case Mode::ReadWrite:
|
||||
return FS::FileAccessMode::ReadWrite;
|
||||
case Mode::Append:
|
||||
return FS::FileAccessMode::Append;
|
||||
case Mode::ReadAppend:
|
||||
return FS::FileAccessMode::ReadAppend;
|
||||
case Mode::WriteAppend:
|
||||
return FS::FileAccessMode::Append;
|
||||
case Mode::All:
|
||||
return FS::FileAccessMode::ReadAppend;
|
||||
return FS::FileAccessMode::ReadWrite;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
// This file contains yuzu's HLE API version constants.
|
||||
|
||||
@@ -345,8 +345,12 @@ public:
|
||||
explicit RequestParser(u32* command_buffer) : RequestHelperBase(command_buffer) {}
|
||||
|
||||
explicit RequestParser(Kernel::HLERequestContext& ctx) : RequestHelperBase(ctx) {
|
||||
ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete");
|
||||
Skip(ctx.GetDataPayloadOffset(), false);
|
||||
// TIPC does not have data payload offset
|
||||
if (!ctx.IsTipc()) {
|
||||
ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete");
|
||||
Skip(ctx.GetDataPayloadOffset(), false);
|
||||
}
|
||||
|
||||
// Skip the u64 command id, it's already stored in the context
|
||||
static constexpr u32 CommandIdSize = 2;
|
||||
Skip(CommandIdSize, false);
|
||||
|
||||
@@ -79,6 +79,7 @@ ResultCode KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
|
||||
R_UNLESS(current_values[index] <= value, ResultInvalidState);
|
||||
|
||||
limit_values[index] = value;
|
||||
peak_values[index] = current_values[index];
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ AOC_U::AOC_U(Core::System& system_)
|
||||
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
|
||||
{8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},
|
||||
{9, nullptr, "GetAddOnContentLostErrorCode"},
|
||||
{10, nullptr, "GetAddOnContentListChangedEventWithProcessId"},
|
||||
{10, &AOC_U::GetAddOnContentListChangedEventWithProcessId, "GetAddOnContentListChangedEventWithProcessId"},
|
||||
{100, &AOC_U::CreateEcPurchasedEventManager, "CreateEcPurchasedEventManager"},
|
||||
{101, &AOC_U::CreatePermanentEcPurchasedEventManager, "CreatePermanentEcPurchasedEventManager"},
|
||||
{110, nullptr, "CreateContentsServiceManager"},
|
||||
@@ -257,6 +257,14 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) {
|
||||
rb.PushCopyObjects(aoc_change_event.GetReadableEvent());
|
||||
}
|
||||
|
||||
void AOC_U::GetAddOnContentListChangedEventWithProcessId(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushCopyObjects(aoc_change_event.GetReadableEvent());
|
||||
}
|
||||
|
||||
void AOC_U::CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called");
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ private:
|
||||
void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx);
|
||||
void PrepareAddOnContent(Kernel::HLERequestContext& ctx);
|
||||
void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx);
|
||||
void GetAddOnContentListChangedEventWithProcessId(Kernel::HLERequestContext& ctx);
|
||||
void CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
|
||||
void CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
{7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},
|
||||
{8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
|
||||
{9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
|
||||
{10, nullptr, "GetAudioOutPlayedSampleCount"},
|
||||
{10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"},
|
||||
{11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"},
|
||||
{12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"},
|
||||
{13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"},
|
||||
@@ -186,6 +186,14 @@ private:
|
||||
rb.Push(static_cast<u32>(stream->GetQueueSize()));
|
||||
}
|
||||
|
||||
void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(stream->GetPlayedSampleCount());
|
||||
}
|
||||
|
||||
void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
|
||||
@@ -253,7 +253,11 @@ void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(worker_buffer_sz);
|
||||
}
|
||||
|
||||
void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
void HwOpus::GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx) {
|
||||
GetWorkBufferSize(ctx);
|
||||
}
|
||||
|
||||
void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto sample_rate = rp.Pop<u32>();
|
||||
const auto channel_count = rp.Pop<u32>();
|
||||
@@ -291,14 +295,47 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
}
|
||||
|
||||
void HwOpus::OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto sample_rate = rp.Pop<u32>();
|
||||
const auto channel_count = rp.Pop<u32>();
|
||||
|
||||
LOG_CRITICAL(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count);
|
||||
|
||||
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
|
||||
sample_rate == 12000 || sample_rate == 8000,
|
||||
"Invalid sample rate");
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
const auto mapping_table = CreateMappingTable(channel_count);
|
||||
|
||||
int error = 0;
|
||||
OpusDecoderPtr decoder{
|
||||
opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
|
||||
num_stereo_streams, mapping_table.data(), &error)};
|
||||
if (error != OPUS_OK || decoder == nullptr) {
|
||||
LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultUnknown);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
}
|
||||
|
||||
HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"},
|
||||
{0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"},
|
||||
{1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
|
||||
{2, nullptr, "OpenOpusDecoderForMultiStream"},
|
||||
{3, nullptr, "GetWorkBufferSizeForMultiStream"},
|
||||
{4, nullptr, "OpenHardwareOpusDecoderEx"},
|
||||
{5, nullptr, "GetWorkBufferSizeEx"},
|
||||
{4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
|
||||
{5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
|
||||
{6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},
|
||||
{7, nullptr, "GetWorkBufferSizeForMultiStreamEx"},
|
||||
};
|
||||
|
||||
@@ -18,8 +18,10 @@ public:
|
||||
~HwOpus() override;
|
||||
|
||||
private:
|
||||
void OpenOpusDecoder(Kernel::HLERequestContext& ctx);
|
||||
void OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx);
|
||||
void OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx);
|
||||
void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
|
||||
void GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -313,7 +313,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
|
||||
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
|
||||
|
||||
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
|
||||
void(Common::FS::RemoveFile(zip_path));
|
||||
Common::FS::RemoveFile(zip_path);
|
||||
}
|
||||
|
||||
HandleDownloadDisplayResult(applet_manager, res);
|
||||
@@ -445,7 +445,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
|
||||
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
|
||||
|
||||
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
|
||||
void(Common::FS::RemoveFile(bin_file_path));
|
||||
Common::FS::RemoveFile(bin_file_path);
|
||||
}
|
||||
|
||||
HandleDownloadDisplayResult(applet_manager, res);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
@@ -785,6 +786,10 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
|
||||
if (Settings::values.enable_fs_access_log) {
|
||||
access_log_mode = AccessLogMode::SdCard;
|
||||
}
|
||||
}
|
||||
|
||||
FSP_SRV::~FSP_SRV() = default;
|
||||
@@ -1041,9 +1046,9 @@ void FSP_SRV::DisableAutoSaveDataCreation(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
log_mode = rp.PopEnum<LogMode>();
|
||||
access_log_mode = rp.PopEnum<AccessLogMode>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, log_mode={:08X}", log_mode);
|
||||
LOG_DEBUG(Service_FS, "called, access_log_mode={}", access_log_mode);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
@@ -1054,7 +1059,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(log_mode);
|
||||
rb.PushEnum(access_log_mode);
|
||||
}
|
||||
|
||||
void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) {
|
||||
@@ -1062,9 +1067,9 @@ void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) {
|
||||
auto log = Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(raw.data()), raw.size());
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, log='{}'", log);
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
reporter.SaveFilesystemAccessReport(log_mode, std::move(log));
|
||||
reporter.SaveFSAccessLog(log);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
@@ -24,11 +24,10 @@ enum class AccessLogVersion : u32 {
|
||||
Latest = V7_0_0,
|
||||
};
|
||||
|
||||
enum class LogMode : u32 {
|
||||
Off,
|
||||
enum class AccessLogMode : u32 {
|
||||
None,
|
||||
Log,
|
||||
RedirectToSdCard,
|
||||
LogToSdCard = Log | RedirectToSdCard,
|
||||
SdCard,
|
||||
};
|
||||
|
||||
class FSP_SRV final : public ServiceFramework<FSP_SRV> {
|
||||
@@ -59,13 +58,12 @@ private:
|
||||
|
||||
FileSystemController& fsc;
|
||||
const FileSys::ContentProvider& content_provider;
|
||||
const Core::Reporter& reporter;
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
u64 current_process_id = 0;
|
||||
u32 access_log_program_index = 0;
|
||||
LogMode log_mode = LogMode::LogToSdCard;
|
||||
|
||||
const Core::Reporter& reporter;
|
||||
AccessLogMode access_log_mode = AccessLogMode::None;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -314,6 +314,8 @@ void Controller_NPad::OnInit() {
|
||||
|
||||
void Controller_NPad::OnLoadInputDevices() {
|
||||
const auto& players = Settings::values.players.GetValue();
|
||||
|
||||
std::lock_guard lock{mutex};
|
||||
for (std::size_t i = 0; i < players.size(); ++i) {
|
||||
std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
|
||||
players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
|
||||
@@ -348,6 +350,8 @@ void Controller_NPad::OnRelease() {
|
||||
}
|
||||
|
||||
void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
const auto controller_idx = NPadIdToIndex(npad_id);
|
||||
const auto controller_type = connected_controllers[controller_idx].type;
|
||||
if (!connected_controllers[controller_idx].is_connected) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/quaternion.h"
|
||||
@@ -563,6 +565,8 @@ private:
|
||||
using MotionArray = std::array<
|
||||
std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,
|
||||
10>;
|
||||
|
||||
std::mutex mutex;
|
||||
ButtonArray buttons;
|
||||
StickArray sticks;
|
||||
VibrationArray vibrations;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/uuid.h"
|
||||
|
||||
@@ -307,6 +307,9 @@ void NVFlinger::Compose() {
|
||||
}
|
||||
|
||||
s64 NVFlinger::GetNextTicks() const {
|
||||
if (Settings::values.disable_fps_limit.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
constexpr s64 max_hertz = 120LL;
|
||||
return (1000000000 * (1LL << swap_interval)) / max_hertz;
|
||||
}
|
||||
|
||||
@@ -149,10 +149,10 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
|
||||
std::string function_name = info == nullptr ? fmt::format("{}", ctx.GetCommand()) : info->name;
|
||||
|
||||
fmt::memory_buffer buf;
|
||||
fmt::format_to(buf, "function '{}': port='{}' cmd_buf={{[0]=0x{:X}", function_name,
|
||||
service_name, cmd_buf[0]);
|
||||
fmt::format_to(std::back_inserter(buf), "function '{}': port='{}' cmd_buf={{[0]=0x{:X}",
|
||||
function_name, service_name, cmd_buf[0]);
|
||||
for (int i = 1; i <= 8; ++i) {
|
||||
fmt::format_to(buf, ", [{}]=0x{:X}", i, cmd_buf[i]);
|
||||
fmt::format_to(std::back_inserter(buf), ", [{}]=0x{:X}", i, cmd_buf[i]);
|
||||
}
|
||||
buf.push_back('}');
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::SPL {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
|
||||
@@ -125,7 +125,7 @@ ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& locati
|
||||
return ERROR_TIME_NOT_FOUND;
|
||||
}
|
||||
|
||||
vfs_file = zoneinfo_dir->GetFile(location_name);
|
||||
vfs_file = zoneinfo_dir->GetFileRelative(location_name);
|
||||
if (!vfs_file) {
|
||||
LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.",
|
||||
time_zone_binary_titleid, location_name);
|
||||
|
||||
+20
-16
@@ -195,7 +195,9 @@ json GetHLERequestContextData(Kernel::HLERequestContext& ctx, Core::Memory::Memo
|
||||
|
||||
namespace Core {
|
||||
|
||||
Reporter::Reporter(System& system_) : system(system_) {}
|
||||
Reporter::Reporter(System& system_) : system(system_) {
|
||||
ClearFSAccessLog();
|
||||
}
|
||||
|
||||
Reporter::~Reporter() = default;
|
||||
|
||||
@@ -362,22 +364,12 @@ void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
|
||||
SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));
|
||||
}
|
||||
|
||||
void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode,
|
||||
std::string log_message) const {
|
||||
if (!IsReportingEnabled())
|
||||
return;
|
||||
void Reporter::SaveFSAccessLog(std::string_view log_message) const {
|
||||
const auto access_log_path =
|
||||
Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "FsAccessLog.txt";
|
||||
|
||||
const auto timestamp = GetTimestamp();
|
||||
const auto title_id = system.CurrentProcess()->GetTitleID();
|
||||
json out;
|
||||
|
||||
out["yuzu_version"] = GetYuzuVersionData();
|
||||
out["report_common"] = GetReportCommonData(title_id, ResultSuccess, timestamp);
|
||||
|
||||
out["log_mode"] = fmt::format("{:08X}", static_cast<u32>(log_mode));
|
||||
out["log_message"] = std::move(log_message);
|
||||
|
||||
SaveToFile(std::move(out), GetPath("filesystem_access_report", title_id, timestamp));
|
||||
void(Common::FS::AppendStringToFile(access_log_path, Common::FS::FileType::TextFile,
|
||||
log_message));
|
||||
}
|
||||
|
||||
void Reporter::SaveUserReport() const {
|
||||
@@ -392,6 +384,18 @@ void Reporter::SaveUserReport() const {
|
||||
GetPath("user_report", title_id, timestamp));
|
||||
}
|
||||
|
||||
void Reporter::ClearFSAccessLog() const {
|
||||
const auto access_log_path =
|
||||
Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "FsAccessLog.txt";
|
||||
|
||||
Common::FS::IOFile access_log_file{access_log_path, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::TextFile};
|
||||
|
||||
if (!access_log_file.IsOpen()) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to clear the filesystem access log.");
|
||||
}
|
||||
}
|
||||
|
||||
bool Reporter::IsReportingEnabled() const {
|
||||
return Settings::values.reporting_services;
|
||||
}
|
||||
|
||||
+3
-6
@@ -16,10 +16,6 @@ namespace Kernel {
|
||||
class HLERequestContext;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Service::FileSystem {
|
||||
enum class LogMode : u32;
|
||||
}
|
||||
|
||||
namespace Service::LM {
|
||||
struct LogMessage;
|
||||
} // namespace Service::LM
|
||||
@@ -69,14 +65,15 @@ public:
|
||||
std::optional<std::string> custom_text_main = {},
|
||||
std::optional<std::string> custom_text_detail = {}) const;
|
||||
|
||||
void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode,
|
||||
std::string log_message) const;
|
||||
void SaveFSAccessLog(std::string_view log_message) const;
|
||||
|
||||
// Can be used anywhere to generate a backtrace and general info report at any point during
|
||||
// execution. Not intended to be used for anything other than debugging or testing.
|
||||
void SaveUserReport() const;
|
||||
|
||||
private:
|
||||
void ClearFSAccessLog() const;
|
||||
|
||||
bool IsReportingEnabled() const;
|
||||
|
||||
System& system;
|
||||
|
||||
@@ -230,6 +230,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
|
||||
Settings::values.use_asynchronous_gpu_emulation.GetValue());
|
||||
AddField(field_type, "Renderer_UseNvdecEmulation",
|
||||
Settings::values.use_nvdec_emulation.GetValue());
|
||||
AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue());
|
||||
AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue());
|
||||
AddField(field_type, "Renderer_UseAssemblyShaders",
|
||||
Settings::values.use_assembly_shaders.GetValue());
|
||||
|
||||
@@ -2,25 +2,23 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "input_common/mouse/mouse_input.h"
|
||||
|
||||
namespace MouseInput {
|
||||
|
||||
Mouse::Mouse() {
|
||||
update_thread = std::thread(&Mouse::UpdateThread, this);
|
||||
update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
|
||||
}
|
||||
|
||||
Mouse::~Mouse() {
|
||||
update_thread_running = false;
|
||||
if (update_thread.joinable()) {
|
||||
update_thread.join();
|
||||
}
|
||||
}
|
||||
Mouse::~Mouse() = default;
|
||||
|
||||
void Mouse::UpdateThread() {
|
||||
void Mouse::UpdateThread(std::stop_token stop_token) {
|
||||
constexpr int update_time = 10;
|
||||
while (update_thread_running) {
|
||||
while (!stop_token.stop_requested()) {
|
||||
for (MouseInfo& info : mouse_info) {
|
||||
const Common::Vec3f angular_direction{
|
||||
-info.tilt_direction.y,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
#include "common/common_types.h"
|
||||
@@ -85,7 +86,7 @@ public:
|
||||
[[nodiscard]] const MouseData& GetMouseState(std::size_t button) const;
|
||||
|
||||
private:
|
||||
void UpdateThread();
|
||||
void UpdateThread(std::stop_token stop_token);
|
||||
void UpdateYuzuSettings();
|
||||
void StopPanning();
|
||||
|
||||
@@ -105,12 +106,11 @@ private:
|
||||
u16 buttons{};
|
||||
u16 toggle_buttons{};
|
||||
u16 lock_buttons{};
|
||||
std::thread update_thread;
|
||||
std::jthread update_thread;
|
||||
MouseButton last_button{MouseButton::Undefined};
|
||||
std::array<MouseInfo, 7> mouse_info;
|
||||
Common::SPSCQueue<MouseStatus> mouse_queue;
|
||||
bool configuring{false};
|
||||
bool update_thread_running{true};
|
||||
int mouse_panning_timout{};
|
||||
};
|
||||
} // namespace MouseInput
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/settings.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "input_common/touch_from_button.h"
|
||||
|
||||
@@ -237,6 +237,7 @@ add_library(video_core STATIC
|
||||
texture_cache/util.cpp
|
||||
texture_cache/util.h
|
||||
textures/astc.h
|
||||
textures/astc.cpp
|
||||
textures/decoders.cpp
|
||||
textures/decoders.h
|
||||
textures/texture.cpp
|
||||
|
||||
@@ -256,6 +256,16 @@ public:
|
||||
stream_score += score;
|
||||
}
|
||||
|
||||
/// Sets the new frame tick
|
||||
void SetFrameTick(u64 new_frame_tick) noexcept {
|
||||
frame_tick = new_frame_tick;
|
||||
}
|
||||
|
||||
/// Returns the new frame tick
|
||||
[[nodiscard]] u64 FrameTick() const noexcept {
|
||||
return frame_tick;
|
||||
}
|
||||
|
||||
/// Returns the likeliness of this being a stream buffer
|
||||
[[nodiscard]] int StreamScore() const noexcept {
|
||||
return stream_score;
|
||||
@@ -586,6 +596,7 @@ private:
|
||||
RasterizerInterface* rasterizer = nullptr;
|
||||
VAddr cpu_addr = 0;
|
||||
Words words;
|
||||
u64 frame_tick = 0;
|
||||
BufferFlagBits flags{};
|
||||
int stream_score = 0;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/common_sizes.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/div_ceil.h"
|
||||
#include "common/microprofile.h"
|
||||
@@ -65,6 +66,9 @@ class BufferCache {
|
||||
|
||||
static constexpr BufferId NULL_BUFFER_ID{0};
|
||||
|
||||
static constexpr u64 EXPECTED_MEMORY = Common::Size_512_MB;
|
||||
static constexpr u64 CRITICAL_MEMORY = Common::Size_1_GB;
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
using Runtime = typename P::Runtime;
|
||||
@@ -102,6 +106,8 @@ public:
|
||||
|
||||
void TickFrame();
|
||||
|
||||
void RunGarbageCollector();
|
||||
|
||||
void WriteMemory(VAddr cpu_addr, u64 size);
|
||||
|
||||
void CachedWriteMemory(VAddr cpu_addr, u64 size);
|
||||
@@ -243,6 +249,8 @@ private:
|
||||
template <bool insert>
|
||||
void ChangeRegister(BufferId buffer_id);
|
||||
|
||||
void TouchBuffer(Buffer& buffer) const noexcept;
|
||||
|
||||
bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);
|
||||
|
||||
bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size);
|
||||
@@ -255,6 +263,10 @@ private:
|
||||
|
||||
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
|
||||
|
||||
void DownloadBufferMemory(Buffer& buffer_id);
|
||||
|
||||
void DownloadBufferMemory(Buffer& buffer_id, VAddr cpu_addr, u64 size);
|
||||
|
||||
void DeleteBuffer(BufferId buffer_id);
|
||||
|
||||
void ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id);
|
||||
@@ -319,6 +331,10 @@ private:
|
||||
size_t immediate_buffer_capacity = 0;
|
||||
std::unique_ptr<u8[]> immediate_buffer_alloc;
|
||||
|
||||
typename SlotVector<Buffer>::Iterator deletion_iterator;
|
||||
u64 frame_tick = 0;
|
||||
u64 total_used_memory = 0;
|
||||
|
||||
std::array<BufferId, ((1ULL << 39) >> PAGE_BITS)> page_table;
|
||||
};
|
||||
|
||||
@@ -332,6 +348,28 @@ BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
|
||||
gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_}, runtime{runtime_} {
|
||||
// Ensure the first slot is used for the null buffer
|
||||
void(slot_buffers.insert(runtime, NullBufferParams{}));
|
||||
deletion_iterator = slot_buffers.end();
|
||||
}
|
||||
|
||||
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;
|
||||
for (; num_iterations > 0; --num_iterations) {
|
||||
if (deletion_iterator == slot_buffers.end()) {
|
||||
deletion_iterator = slot_buffers.begin();
|
||||
}
|
||||
++deletion_iterator;
|
||||
if (deletion_iterator == slot_buffers.end()) {
|
||||
break;
|
||||
}
|
||||
const auto [buffer_id, buffer] = *deletion_iterator;
|
||||
if (buffer->FrameTick() + ticks_to_destroy < frame_tick) {
|
||||
DownloadBufferMemory(*buffer);
|
||||
DeleteBuffer(buffer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -349,6 +387,10 @@ void BufferCache<P>::TickFrame() {
|
||||
const bool skip_preferred = hits * 256 < shots * 251;
|
||||
uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0;
|
||||
|
||||
if (Settings::values.use_caches_gc.GetValue() && total_used_memory >= EXPECTED_MEMORY) {
|
||||
RunGarbageCollector();
|
||||
}
|
||||
++frame_tick;
|
||||
delayed_destruction_ring.Tick();
|
||||
}
|
||||
|
||||
@@ -371,50 +413,8 @@ void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) {
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::DownloadMemory(VAddr cpu_addr, u64 size) {
|
||||
ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) {
|
||||
boost::container::small_vector<BufferCopy, 1> copies;
|
||||
u64 total_size_bytes = 0;
|
||||
u64 largest_copy = 0;
|
||||
buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) {
|
||||
copies.push_back(BufferCopy{
|
||||
.src_offset = range_offset,
|
||||
.dst_offset = total_size_bytes,
|
||||
.size = range_size,
|
||||
});
|
||||
total_size_bytes += range_size;
|
||||
largest_copy = std::max(largest_copy, range_size);
|
||||
});
|
||||
if (total_size_bytes == 0) {
|
||||
return;
|
||||
}
|
||||
MICROPROFILE_SCOPE(GPU_DownloadMemory);
|
||||
|
||||
if constexpr (USE_MEMORY_MAPS) {
|
||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
|
||||
const u8* const mapped_memory = download_staging.mapped_span.data();
|
||||
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
|
||||
for (BufferCopy& copy : copies) {
|
||||
// Modify copies to have the staging offset in mind
|
||||
copy.dst_offset += download_staging.offset;
|
||||
}
|
||||
runtime.CopyBuffer(download_staging.buffer, buffer, copies_span);
|
||||
runtime.Finish();
|
||||
for (const BufferCopy& copy : copies) {
|
||||
const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
|
||||
// Undo the modified offset
|
||||
const u64 dst_offset = copy.dst_offset - download_staging.offset;
|
||||
const u8* copy_mapped_memory = mapped_memory + dst_offset;
|
||||
cpu_memory.WriteBlockUnsafe(copy_cpu_addr, copy_mapped_memory, copy.size);
|
||||
}
|
||||
} else {
|
||||
const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy);
|
||||
for (const BufferCopy& copy : copies) {
|
||||
buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size));
|
||||
const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
|
||||
cpu_memory.WriteBlockUnsafe(copy_cpu_addr, immediate_buffer.data(), copy.size);
|
||||
}
|
||||
}
|
||||
});
|
||||
ForEachBufferInRange(cpu_addr, size,
|
||||
[&](BufferId, Buffer& buffer) { DownloadBufferMemory(buffer); });
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -640,6 +640,7 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
|
||||
template <class P>
|
||||
void BufferCache<P>::BindHostIndexBuffer() {
|
||||
Buffer& buffer = slot_buffers[index_buffer.buffer_id];
|
||||
TouchBuffer(buffer);
|
||||
const u32 offset = buffer.Offset(index_buffer.cpu_addr);
|
||||
const u32 size = index_buffer.size;
|
||||
SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
|
||||
@@ -658,6 +659,7 @@ void BufferCache<P>::BindHostVertexBuffers() {
|
||||
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
||||
const Binding& binding = vertex_buffers[index];
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer);
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
|
||||
if (!flags[Dirty::VertexBuffer0 + index]) {
|
||||
continue;
|
||||
@@ -693,6 +695,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
||||
const VAddr cpu_addr = binding.cpu_addr;
|
||||
const u32 size = binding.size;
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer);
|
||||
const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
|
||||
size <= uniform_buffer_skip_cache_size &&
|
||||
!buffer.IsRegionGpuModified(cpu_addr, size);
|
||||
@@ -744,6 +747,7 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
|
||||
ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) {
|
||||
const Binding& binding = storage_buffers[stage][index];
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer);
|
||||
const u32 size = binding.size;
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||
|
||||
@@ -766,6 +770,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
||||
const Binding& binding = transform_feedback_buffers[index];
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer);
|
||||
const u32 size = binding.size;
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||
|
||||
@@ -784,6 +789,7 @@ void BufferCache<P>::BindHostComputeUniformBuffers() {
|
||||
ForEachEnabledBit(enabled_compute_uniform_buffers, [&](u32 index) {
|
||||
const Binding& binding = compute_uniform_buffers[index];
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer);
|
||||
const u32 size = binding.size;
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||
|
||||
@@ -803,6 +809,7 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
|
||||
ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) {
|
||||
const Binding& binding = compute_storage_buffers[index];
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer);
|
||||
const u32 size = binding.size;
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||
|
||||
@@ -1101,6 +1108,7 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
|
||||
const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);
|
||||
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
|
||||
const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
|
||||
TouchBuffer(slot_buffers[new_buffer_id]);
|
||||
for (const BufferId overlap_id : overlap.ids) {
|
||||
JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
|
||||
}
|
||||
@@ -1122,8 +1130,14 @@ template <class P>
|
||||
template <bool insert>
|
||||
void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
|
||||
const Buffer& buffer = slot_buffers[buffer_id];
|
||||
const auto size = buffer.SizeBytes();
|
||||
if (insert) {
|
||||
total_used_memory += Common::AlignUp(size, 1024);
|
||||
} else {
|
||||
total_used_memory -= Common::AlignUp(size, 1024);
|
||||
}
|
||||
const VAddr cpu_addr_begin = buffer.CpuAddr();
|
||||
const VAddr cpu_addr_end = cpu_addr_begin + buffer.SizeBytes();
|
||||
const VAddr cpu_addr_end = cpu_addr_begin + size;
|
||||
const u64 page_begin = cpu_addr_begin / PAGE_SIZE;
|
||||
const u64 page_end = Common::DivCeil(cpu_addr_end, PAGE_SIZE);
|
||||
for (u64 page = page_begin; page != page_end; ++page) {
|
||||
@@ -1135,6 +1149,11 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::TouchBuffer(Buffer& buffer) const noexcept {
|
||||
buffer.SetFrameTick(frame_tick);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) {
|
||||
if (buffer.CpuAddr() == 0) {
|
||||
@@ -1211,6 +1230,57 @@ void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes,
|
||||
runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::DownloadBufferMemory(Buffer& buffer) {
|
||||
DownloadBufferMemory(buffer, buffer.CpuAddr(), buffer.SizeBytes());
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 size) {
|
||||
boost::container::small_vector<BufferCopy, 1> copies;
|
||||
u64 total_size_bytes = 0;
|
||||
u64 largest_copy = 0;
|
||||
buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) {
|
||||
copies.push_back(BufferCopy{
|
||||
.src_offset = range_offset,
|
||||
.dst_offset = total_size_bytes,
|
||||
.size = range_size,
|
||||
});
|
||||
total_size_bytes += range_size;
|
||||
largest_copy = std::max(largest_copy, range_size);
|
||||
});
|
||||
if (total_size_bytes == 0) {
|
||||
return;
|
||||
}
|
||||
MICROPROFILE_SCOPE(GPU_DownloadMemory);
|
||||
|
||||
if constexpr (USE_MEMORY_MAPS) {
|
||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
|
||||
const u8* const mapped_memory = download_staging.mapped_span.data();
|
||||
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
|
||||
for (BufferCopy& copy : copies) {
|
||||
// Modify copies to have the staging offset in mind
|
||||
copy.dst_offset += download_staging.offset;
|
||||
}
|
||||
runtime.CopyBuffer(download_staging.buffer, buffer, copies_span);
|
||||
runtime.Finish();
|
||||
for (const BufferCopy& copy : copies) {
|
||||
const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
|
||||
// Undo the modified offset
|
||||
const u64 dst_offset = copy.dst_offset - download_staging.offset;
|
||||
const u8* copy_mapped_memory = mapped_memory + dst_offset;
|
||||
cpu_memory.WriteBlockUnsafe(copy_cpu_addr, copy_mapped_memory, copy.size);
|
||||
}
|
||||
} else {
|
||||
const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy);
|
||||
for (const BufferCopy& copy : copies) {
|
||||
buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size));
|
||||
const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
|
||||
cpu_memory.WriteBlockUnsafe(copy_cpu_addr, immediate_buffer.data(), copy.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::DeleteBuffer(BufferId buffer_id) {
|
||||
const auto scalar_replace = [buffer_id](Binding& binding) {
|
||||
@@ -1236,6 +1306,7 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id) {
|
||||
|
||||
Unregister(buffer_id);
|
||||
delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
|
||||
slot_buffers.erase(buffer_id);
|
||||
|
||||
NotifyBufferDeletion();
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ public:
|
||||
return 4;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -763,7 +763,7 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) {
|
||||
case 1: {
|
||||
READ_UINT_VALUES(2)
|
||||
uint L0 = (v[0] >> 2) | (v[1] & 0xC0);
|
||||
uint L1 = max(L0 + (v[1] & 0x3F), 0xFFU);
|
||||
uint L1 = min(L0 + (v[1] & 0x3F), 0xFFU);
|
||||
ep1 = uvec4(0xFF, L0, L0, L0);
|
||||
ep2 = uvec4(0xFF, L1, L1, L1);
|
||||
break;
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <stop_token>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/gpu.h"
|
||||
@@ -123,7 +123,7 @@ public:
|
||||
virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
|
||||
|
||||
/// Initialize disk cached resources for the game being emulated
|
||||
virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
|
||||
const DiskResourceLoadCallback& callback) {}
|
||||
|
||||
/// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
|
||||
|
||||
@@ -454,7 +454,7 @@ private:
|
||||
|
||||
template <typename... Args>
|
||||
void AddExpression(std::string_view text, Args&&... args) {
|
||||
shader_source += fmt::format(text, std::forward<Args>(args)...);
|
||||
shader_source += fmt::format(fmt::runtime(text), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
|
||||
@@ -351,7 +351,7 @@ void RasterizerOpenGL::SetupShaders(bool is_indexed) {
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
void RasterizerOpenGL::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) {
|
||||
shader_cache.LoadDiskCache(title_id, stop_loading, callback);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) override;
|
||||
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
|
||||
u32 pixel_stride) override;
|
||||
void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) override;
|
||||
|
||||
/// Returns true when there are commands queued to the OpenGL server.
|
||||
|
||||
@@ -331,7 +331,7 @@ ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_,
|
||||
|
||||
ShaderCacheOpenGL::~ShaderCacheOpenGL() = default;
|
||||
|
||||
void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, std::stop_token stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) {
|
||||
disk_cache.BindTitleID(title_id);
|
||||
const std::optional transferable = disk_cache.LoadTransferable();
|
||||
@@ -372,7 +372,7 @@ void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop
|
||||
const auto scope = context->Acquire();
|
||||
|
||||
for (std::size_t i = begin; i < end; ++i) {
|
||||
if (stop_loading) {
|
||||
if (stop_loading.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
const auto& entry = (*transferable)[i];
|
||||
@@ -435,7 +435,7 @@ void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop
|
||||
precompiled_cache_altered = true;
|
||||
return;
|
||||
}
|
||||
if (stop_loading) {
|
||||
if (stop_loading.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
~ShaderCacheOpenGL() override;
|
||||
|
||||
/// Loads disk cache for the current game
|
||||
void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
void LoadDiskCache(u64 title_id, std::stop_token stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback);
|
||||
|
||||
/// Gets the current specified shader stage program
|
||||
|
||||
@@ -96,7 +96,7 @@ public:
|
||||
// etc).
|
||||
template <typename... Args>
|
||||
void AddLine(std::string_view text, Args&&... args) {
|
||||
AddExpression(fmt::format(text, std::forward<Args>(args)...));
|
||||
AddExpression(fmt::format(fmt::runtime(text), std::forward<Args>(args)...));
|
||||
AddNewLine();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/settings.h"
|
||||
|
||||
#include "video_core/renderer_opengl/gl_device.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state_tracker.h"
|
||||
@@ -307,7 +309,9 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
|
||||
|
||||
[[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime,
|
||||
const VideoCommon::ImageInfo& info) {
|
||||
return !runtime.HasNativeASTC() && IsPixelFormatASTC(info.format);
|
||||
if (IsPixelFormatASTC(info.format)) {
|
||||
return !runtime.HasNativeASTC() && Settings::values.accelerate_astc.GetValue();
|
||||
}
|
||||
// Disable other accelerated uploads for now as they don't implement swizzled uploads
|
||||
return false;
|
||||
switch (info.type) {
|
||||
@@ -733,6 +737,8 @@ Image::Image(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info_,
|
||||
}
|
||||
}
|
||||
|
||||
Image::~Image() = default;
|
||||
|
||||
void Image::UploadMemory(const ImageBufferMap& map,
|
||||
std::span<const VideoCommon::BufferImageCopy> copies) {
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, map.buffer);
|
||||
|
||||
@@ -143,6 +143,14 @@ public:
|
||||
explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,
|
||||
VAddr cpu_addr);
|
||||
|
||||
~Image();
|
||||
|
||||
Image(const Image&) = delete;
|
||||
Image& operator=(const Image&) = delete;
|
||||
|
||||
Image(Image&&) = default;
|
||||
Image& operator=(Image&&) = default;
|
||||
|
||||
void UploadMemory(const ImageBufferMap& map,
|
||||
std::span<const VideoCommon::BufferImageCopy> copies);
|
||||
|
||||
@@ -235,6 +243,7 @@ struct TextureCacheParams {
|
||||
static constexpr bool ENABLE_VALIDATION = true;
|
||||
static constexpr bool FRAMEBUFFER_BLITS = true;
|
||||
static constexpr bool HAS_EMULATED_COPIES = true;
|
||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = false;
|
||||
|
||||
using Runtime = OpenGL::TextureCacheRuntime;
|
||||
using Image = OpenGL::Image;
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
@@ -12,8 +11,6 @@
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
MasterSemaphore::MasterSemaphore(const Device& device) {
|
||||
static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR,
|
||||
@@ -34,9 +31,9 @@ MasterSemaphore::MasterSemaphore(const Device& device) {
|
||||
// Validation layers have a bug where they fail to track resource usage when using timeline
|
||||
// semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have
|
||||
// a separate thread waiting for each timeline semaphore value.
|
||||
debug_thread = std::thread([this] {
|
||||
debug_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
u64 counter = 0;
|
||||
while (!shutdown) {
|
||||
while (!stop_token.stop_requested()) {
|
||||
if (semaphore.Wait(counter, 10'000'000)) {
|
||||
++counter;
|
||||
}
|
||||
@@ -44,13 +41,6 @@ MasterSemaphore::MasterSemaphore(const Device& device) {
|
||||
});
|
||||
}
|
||||
|
||||
MasterSemaphore::~MasterSemaphore() {
|
||||
shutdown = true;
|
||||
|
||||
// This thread might not be started
|
||||
if (debug_thread.joinable()) {
|
||||
debug_thread.join();
|
||||
}
|
||||
}
|
||||
MasterSemaphore::~MasterSemaphore() = default;
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -65,11 +65,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
vk::Semaphore semaphore; ///< Timeline semaphore.
|
||||
std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick.
|
||||
std::atomic<u64> current_tick{1}; ///< Current logical tick.
|
||||
std::atomic<bool> shutdown{false}; ///< True when the object is being destroyed.
|
||||
std::thread debug_thread; ///< Debug thread to workaround validation layer bugs.
|
||||
vk::Semaphore semaphore; ///< Timeline semaphore.
|
||||
std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick.
|
||||
std::atomic<u64> current_tick{1}; ///< Current logical tick.
|
||||
std::jthread debug_thread; ///< Debug thread to workaround validation layer bugs.
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/bit_cast.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/renderer_vulkan/blit_image.h"
|
||||
@@ -817,6 +818,10 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
|
||||
});
|
||||
}
|
||||
|
||||
u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
|
||||
return device.GetDeviceLocalMemory();
|
||||
}
|
||||
|
||||
Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_addr_,
|
||||
VAddr cpu_addr_)
|
||||
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime.scheduler},
|
||||
@@ -828,7 +833,11 @@ Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_
|
||||
commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
|
||||
}
|
||||
if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) {
|
||||
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
|
||||
if (Settings::values.accelerate_astc.GetValue()) {
|
||||
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
|
||||
} else {
|
||||
flags |= VideoCommon::ImageFlagBits::Converted;
|
||||
}
|
||||
}
|
||||
if (runtime.device.HasDebuggingToolAttached()) {
|
||||
if (image) {
|
||||
@@ -871,6 +880,8 @@ Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_
|
||||
}
|
||||
}
|
||||
|
||||
Image::~Image() = default;
|
||||
|
||||
void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
|
||||
// TODO: Move this to another API
|
||||
scheduler->RequestOutsideRenderPassOperationContext();
|
||||
|
||||
@@ -97,6 +97,8 @@ struct TextureCacheRuntime {
|
||||
// All known Vulkan drivers can natively handle BGR textures
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 GetDeviceLocalMemory() const;
|
||||
};
|
||||
|
||||
class Image : public VideoCommon::ImageBase {
|
||||
@@ -104,6 +106,14 @@ public:
|
||||
explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,
|
||||
VAddr cpu_addr);
|
||||
|
||||
~Image();
|
||||
|
||||
Image(const Image&) = delete;
|
||||
Image& operator=(const Image&) = delete;
|
||||
|
||||
Image(Image&&) = default;
|
||||
Image& operator=(Image&&) = default;
|
||||
|
||||
void UploadMemory(const StagingBufferRef& map,
|
||||
std::span<const VideoCommon::BufferImageCopy> copies);
|
||||
|
||||
@@ -257,6 +267,7 @@ struct TextureCacheParams {
|
||||
static constexpr bool ENABLE_VALIDATION = true;
|
||||
static constexpr bool FRAMEBUFFER_BLITS = false;
|
||||
static constexpr bool HAS_EMULATED_COPIES = false;
|
||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
||||
|
||||
using Runtime = Vulkan::TextureCacheRuntime;
|
||||
using Image = Vulkan::Image;
|
||||
|
||||
@@ -283,4 +283,11 @@ std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
|
||||
return {DefaultBlockWidth(format), DefaultBlockHeight(format)};
|
||||
}
|
||||
|
||||
u64 EstimatedDecompressedSize(u64 base_size, PixelFormat format) {
|
||||
constexpr u64 RGBA8_PIXEL_SIZE = 4;
|
||||
const u64 base_block_size = static_cast<u64>(DefaultBlockWidth(format)) *
|
||||
static_cast<u64>(DefaultBlockHeight(format)) * RGBA8_PIXEL_SIZE;
|
||||
return (base_size * base_block_size) / BytesPerBlock(format);
|
||||
}
|
||||
|
||||
} // namespace VideoCore::Surface
|
||||
|
||||
@@ -462,4 +462,6 @@ bool IsPixelFormatSRGB(PixelFormat format);
|
||||
|
||||
std::pair<u32, u32> GetASTCBlockSize(PixelFormat format);
|
||||
|
||||
u64 EstimatedDecompressedSize(u64 base_size, PixelFormat format);
|
||||
|
||||
} // namespace VideoCore::Surface
|
||||
|
||||
@@ -113,6 +113,43 @@ void ImageBase::InsertView(const ImageViewInfo& view_info, ImageViewId image_vie
|
||||
image_view_ids.push_back(image_view_id);
|
||||
}
|
||||
|
||||
bool ImageBase::IsSafeDownload() const noexcept {
|
||||
// Skip images that were not modified from the GPU
|
||||
if (False(flags & ImageFlagBits::GpuModified)) {
|
||||
return false;
|
||||
}
|
||||
// Skip images that .are. modified from the CPU
|
||||
// We don't want to write sensitive data from the guest
|
||||
if (True(flags & ImageFlagBits::CpuModified)) {
|
||||
return false;
|
||||
}
|
||||
if (info.num_samples > 1) {
|
||||
LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImageBase::CheckBadOverlapState() {
|
||||
if (False(flags & ImageFlagBits::BadOverlap)) {
|
||||
return;
|
||||
}
|
||||
if (!overlapping_images.empty()) {
|
||||
return;
|
||||
}
|
||||
flags &= ~ImageFlagBits::BadOverlap;
|
||||
}
|
||||
|
||||
void ImageBase::CheckAliasState() {
|
||||
if (False(flags & ImageFlagBits::Alias)) {
|
||||
return;
|
||||
}
|
||||
if (!aliased_images.empty()) {
|
||||
return;
|
||||
}
|
||||
flags &= ~ImageFlagBits::Alias;
|
||||
}
|
||||
|
||||
void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_id) {
|
||||
static constexpr auto OPTIONS = RelaxedOptions::Size | RelaxedOptions::Format;
|
||||
ASSERT(lhs.info.type == rhs.info.type);
|
||||
|
||||
@@ -25,6 +25,12 @@ enum class ImageFlagBits : u32 {
|
||||
Strong = 1 << 5, ///< Exists in the image table, the dimensions are can be trusted
|
||||
Registered = 1 << 6, ///< True when the image is registered
|
||||
Picked = 1 << 7, ///< Temporary flag to mark the image as picked
|
||||
|
||||
// Garbage Collection Flags
|
||||
BadOverlap = 1 << 8, ///< This image overlaps other but doesn't fit, has higher
|
||||
///< garbage collection priority
|
||||
Alias = 1 << 9, ///< This image has aliases and has priority on garbage
|
||||
///< collection
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
|
||||
|
||||
@@ -44,11 +50,16 @@ struct ImageBase {
|
||||
|
||||
void InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id);
|
||||
|
||||
[[nodiscard]] bool IsSafeDownload() const noexcept;
|
||||
|
||||
[[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept {
|
||||
const VAddr overlap_end = overlap_cpu_addr + overlap_size;
|
||||
return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end;
|
||||
}
|
||||
|
||||
void CheckBadOverlapState();
|
||||
void CheckAliasState();
|
||||
|
||||
ImageInfo info;
|
||||
|
||||
u32 guest_size_bytes = 0;
|
||||
@@ -72,6 +83,7 @@ struct ImageBase {
|
||||
std::vector<SubresourceBase> slice_subresources;
|
||||
|
||||
std::vector<AliasedImage> aliased_images;
|
||||
std::vector<ImageId> overlapping_images;
|
||||
};
|
||||
|
||||
struct ImageAllocBase {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <concepts>
|
||||
#include <numeric>
|
||||
#include <type_traits>
|
||||
@@ -32,6 +33,60 @@ template <class T>
|
||||
requires std::is_nothrow_move_assignable_v<T>&&
|
||||
std::is_nothrow_move_constructible_v<T> class SlotVector {
|
||||
public:
|
||||
class Iterator {
|
||||
friend SlotVector<T>;
|
||||
|
||||
public:
|
||||
constexpr Iterator() = default;
|
||||
|
||||
Iterator& operator++() noexcept {
|
||||
const u64* const bitset = slot_vector->stored_bitset.data();
|
||||
const u32 size = static_cast<u32>(slot_vector->stored_bitset.size()) * 64;
|
||||
if (id.index < size) {
|
||||
do {
|
||||
++id.index;
|
||||
} while (id.index < size && !IsValid(bitset));
|
||||
if (id.index == size) {
|
||||
id.index = SlotId::INVALID_INDEX;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) noexcept {
|
||||
const Iterator copy{*this};
|
||||
++*this;
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator& other) const noexcept {
|
||||
return id.index == other.id.index;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator& other) const noexcept {
|
||||
return id.index != other.id.index;
|
||||
}
|
||||
|
||||
std::pair<SlotId, T*> operator*() const noexcept {
|
||||
return {id, std::addressof((*slot_vector)[id])};
|
||||
}
|
||||
|
||||
T* operator->() const noexcept {
|
||||
return std::addressof((*slot_vector)[id]);
|
||||
}
|
||||
|
||||
private:
|
||||
Iterator(SlotVector<T>* slot_vector_, SlotId id_) noexcept
|
||||
: slot_vector{slot_vector_}, id{id_} {}
|
||||
|
||||
bool IsValid(const u64* bitset) const noexcept {
|
||||
return ((bitset[id.index / 64] >> (id.index % 64)) & 1) != 0;
|
||||
}
|
||||
|
||||
SlotVector<T>* slot_vector;
|
||||
SlotId id;
|
||||
};
|
||||
|
||||
~SlotVector() noexcept {
|
||||
size_t index = 0;
|
||||
for (u64 bits : stored_bitset) {
|
||||
@@ -70,6 +125,20 @@ public:
|
||||
ResetStorageBit(id.index);
|
||||
}
|
||||
|
||||
[[nodiscard]] Iterator begin() noexcept {
|
||||
const auto it = std::ranges::find_if(stored_bitset, [](u64 value) { return value != 0; });
|
||||
if (it == stored_bitset.end()) {
|
||||
return end();
|
||||
}
|
||||
const u32 word_index = static_cast<u32>(std::distance(it, stored_bitset.begin()));
|
||||
const SlotId first_id{word_index * 64 + static_cast<u32>(std::countr_zero(*it))};
|
||||
return Iterator(this, first_id);
|
||||
}
|
||||
|
||||
[[nodiscard]] Iterator end() noexcept {
|
||||
return Iterator(this, SlotId{SlotId::INVALID_INDEX});
|
||||
}
|
||||
|
||||
private:
|
||||
struct NonTrivialDummy {
|
||||
NonTrivialDummy() noexcept {}
|
||||
@@ -140,7 +209,6 @@ private:
|
||||
|
||||
Entry* values = nullptr;
|
||||
size_t values_capacity = 0;
|
||||
size_t values_size = 0;
|
||||
|
||||
std::vector<u64> stored_bitset;
|
||||
std::vector<u32> free_list;
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_sizes.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/compatible_formats.h"
|
||||
#include "video_core/delayed_destruction_ring.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
@@ -69,12 +71,17 @@ class TextureCache {
|
||||
static constexpr bool FRAMEBUFFER_BLITS = P::FRAMEBUFFER_BLITS;
|
||||
/// True when some copies have to be emulated
|
||||
static constexpr bool HAS_EMULATED_COPIES = P::HAS_EMULATED_COPIES;
|
||||
/// True when the API can provide info about the memory of the device.
|
||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO;
|
||||
|
||||
/// Image view ID for null descriptors
|
||||
static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0};
|
||||
/// Sampler ID for bugged sampler ids
|
||||
static constexpr SamplerId NULL_SAMPLER_ID{0};
|
||||
|
||||
static constexpr u64 DEFAULT_EXPECTED_MEMORY = Common::Size_1_GB;
|
||||
static constexpr u64 DEFAULT_CRITICAL_MEMORY = Common::Size_2_GB;
|
||||
|
||||
using Runtime = typename P::Runtime;
|
||||
using Image = typename P::Image;
|
||||
using ImageAlloc = typename P::ImageAlloc;
|
||||
@@ -103,6 +110,9 @@ public:
|
||||
/// Notify the cache that a new frame has been queued
|
||||
void TickFrame();
|
||||
|
||||
/// Runs the Garbage Collector.
|
||||
void RunGarbageCollector();
|
||||
|
||||
/// Return a constant reference to the given image view id
|
||||
[[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept;
|
||||
|
||||
@@ -333,6 +343,10 @@ private:
|
||||
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> page_table;
|
||||
|
||||
bool has_deleted_images = false;
|
||||
u64 total_used_memory = 0;
|
||||
u64 minimum_memory;
|
||||
u64 expected_memory;
|
||||
u64 critical_memory;
|
||||
|
||||
SlotVector<Image> slot_images;
|
||||
SlotVector<ImageView> slot_image_views;
|
||||
@@ -353,6 +367,7 @@ private:
|
||||
|
||||
u64 modification_tick = 0;
|
||||
u64 frame_tick = 0;
|
||||
typename SlotVector<Image>::Iterator deletion_iterator;
|
||||
};
|
||||
|
||||
template <class P>
|
||||
@@ -373,11 +388,94 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface&
|
||||
// This way the null resource becomes a compile time constant
|
||||
void(slot_image_views.insert(runtime, NullImageParams{}));
|
||||
void(slot_samplers.insert(runtime, sampler_descriptor));
|
||||
|
||||
deletion_iterator = slot_images.begin();
|
||||
|
||||
if constexpr (HAS_DEVICE_MEMORY_INFO) {
|
||||
const auto device_memory = runtime.GetDeviceLocalMemory();
|
||||
const u64 possible_expected_memory = (device_memory * 3) / 10;
|
||||
const u64 possible_critical_memory = (device_memory * 6) / 10;
|
||||
expected_memory = std::max(possible_expected_memory, DEFAULT_EXPECTED_MEMORY);
|
||||
critical_memory = std::max(possible_critical_memory, DEFAULT_CRITICAL_MEMORY);
|
||||
minimum_memory = 0;
|
||||
} else {
|
||||
// on OGL we can be more conservatives as the driver takes care.
|
||||
expected_memory = DEFAULT_EXPECTED_MEMORY + Common::Size_512_MB;
|
||||
critical_memory = DEFAULT_CRITICAL_MEMORY + Common::Size_1_GB;
|
||||
minimum_memory = expected_memory;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::RunGarbageCollector() {
|
||||
const bool high_priority_mode = total_used_memory >= expected_memory;
|
||||
const bool aggressive_mode = total_used_memory >= critical_memory;
|
||||
const u64 ticks_to_destroy = high_priority_mode ? 60 : 100;
|
||||
int num_iterations = aggressive_mode ? 256 : (high_priority_mode ? 128 : 64);
|
||||
for (; num_iterations > 0; --num_iterations) {
|
||||
if (deletion_iterator == slot_images.end()) {
|
||||
deletion_iterator = slot_images.begin();
|
||||
if (deletion_iterator == slot_images.end()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto [image_id, image_tmp] = *deletion_iterator;
|
||||
Image* image = image_tmp; // fix clang error.
|
||||
const bool is_alias = True(image->flags & ImageFlagBits::Alias);
|
||||
const bool is_bad_overlap = True(image->flags & ImageFlagBits::BadOverlap);
|
||||
const bool must_download = image->IsSafeDownload();
|
||||
bool should_care = is_bad_overlap || is_alias || (high_priority_mode && !must_download);
|
||||
const u64 ticks_needed =
|
||||
is_bad_overlap
|
||||
? ticks_to_destroy >> 4
|
||||
: ((should_care && aggressive_mode) ? ticks_to_destroy >> 1 : ticks_to_destroy);
|
||||
should_care |= aggressive_mode;
|
||||
if (should_care && image->frame_tick + ticks_needed < frame_tick) {
|
||||
if (is_bad_overlap) {
|
||||
const bool overlap_check = std::ranges::all_of(
|
||||
image->overlapping_images, [&, image](const ImageId& overlap_id) {
|
||||
auto& overlap = slot_images[overlap_id];
|
||||
return overlap.frame_tick >= image->frame_tick;
|
||||
});
|
||||
if (!overlap_check) {
|
||||
++deletion_iterator;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!is_bad_overlap && must_download) {
|
||||
const bool alias_check = std::ranges::none_of(
|
||||
image->aliased_images, [&, image](const AliasedImage& alias) {
|
||||
auto& alias_image = slot_images[alias.id];
|
||||
return (alias_image.frame_tick < image->frame_tick) ||
|
||||
(alias_image.modification_tick < image->modification_tick);
|
||||
});
|
||||
|
||||
if (alias_check) {
|
||||
auto map = runtime.DownloadStagingBuffer(image->unswizzled_size_bytes);
|
||||
const auto copies = FullDownloadCopies(image->info);
|
||||
image->DownloadMemory(map, copies);
|
||||
runtime.Finish();
|
||||
SwizzleImage(gpu_memory, image->gpu_addr, image->info, copies, map.mapped_span);
|
||||
}
|
||||
}
|
||||
if (True(image->flags & ImageFlagBits::Tracked)) {
|
||||
UntrackImage(*image);
|
||||
}
|
||||
UnregisterImage(image_id);
|
||||
DeleteImage(image_id);
|
||||
if (is_bad_overlap) {
|
||||
++num_iterations;
|
||||
}
|
||||
}
|
||||
++deletion_iterator;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::TickFrame() {
|
||||
// Tick sentenced resources in this order to ensure they are destroyed in the right order
|
||||
if (Settings::values.use_caches_gc.GetValue() && total_used_memory > minimum_memory) {
|
||||
RunGarbageCollector();
|
||||
}
|
||||
sentenced_images.Tick();
|
||||
sentenced_framebuffers.Tick();
|
||||
sentenced_image_view.Tick();
|
||||
@@ -568,17 +666,7 @@ template <class P>
|
||||
void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) {
|
||||
std::vector<ImageId> images;
|
||||
ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) {
|
||||
// Skip images that were not modified from the GPU
|
||||
if (False(image.flags & ImageFlagBits::GpuModified)) {
|
||||
return;
|
||||
}
|
||||
// Skip images that .are. modified from the CPU
|
||||
// We don't want to write sensitive data from the guest
|
||||
if (True(image.flags & ImageFlagBits::CpuModified)) {
|
||||
return;
|
||||
}
|
||||
if (image.info.num_samples > 1) {
|
||||
LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
|
||||
if (!image.IsSafeDownload()) {
|
||||
return;
|
||||
}
|
||||
image.flags &= ~ImageFlagBits::GpuModified;
|
||||
@@ -967,6 +1055,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
|
||||
std::vector<ImageId> overlap_ids;
|
||||
std::vector<ImageId> left_aliased_ids;
|
||||
std::vector<ImageId> right_aliased_ids;
|
||||
std::vector<ImageId> bad_overlap_ids;
|
||||
ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) {
|
||||
if (info.type != overlap.info.type) {
|
||||
return;
|
||||
@@ -992,9 +1081,14 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
|
||||
const ImageBase new_image_base(new_info, gpu_addr, cpu_addr);
|
||||
if (IsSubresource(new_info, overlap, gpu_addr, options, broken_views, native_bgr)) {
|
||||
left_aliased_ids.push_back(overlap_id);
|
||||
overlap.flags |= ImageFlagBits::Alias;
|
||||
} else if (IsSubresource(overlap.info, new_image_base, overlap.gpu_addr, options,
|
||||
broken_views, native_bgr)) {
|
||||
right_aliased_ids.push_back(overlap_id);
|
||||
overlap.flags |= ImageFlagBits::Alias;
|
||||
} else {
|
||||
bad_overlap_ids.push_back(overlap_id);
|
||||
overlap.flags |= ImageFlagBits::BadOverlap;
|
||||
}
|
||||
});
|
||||
const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr);
|
||||
@@ -1022,10 +1116,18 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
|
||||
for (const ImageId aliased_id : right_aliased_ids) {
|
||||
ImageBase& aliased = slot_images[aliased_id];
|
||||
AddImageAlias(new_image_base, aliased, new_image_id, aliased_id);
|
||||
new_image.flags |= ImageFlagBits::Alias;
|
||||
}
|
||||
for (const ImageId aliased_id : left_aliased_ids) {
|
||||
ImageBase& aliased = slot_images[aliased_id];
|
||||
AddImageAlias(aliased, new_image_base, aliased_id, new_image_id);
|
||||
new_image.flags |= ImageFlagBits::Alias;
|
||||
}
|
||||
for (const ImageId aliased_id : bad_overlap_ids) {
|
||||
ImageBase& aliased = slot_images[aliased_id];
|
||||
aliased.overlapping_images.push_back(new_image_id);
|
||||
new_image.overlapping_images.push_back(aliased_id);
|
||||
new_image.flags |= ImageFlagBits::BadOverlap;
|
||||
}
|
||||
RegisterImage(new_image_id);
|
||||
return new_image_id;
|
||||
@@ -1195,6 +1297,13 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {
|
||||
image.flags |= ImageFlagBits::Registered;
|
||||
ForEachPage(image.cpu_addr, image.guest_size_bytes,
|
||||
[this, image_id](u64 page) { page_table[page].push_back(image_id); });
|
||||
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 = EstimatedDecompressedSize(tentative_size, image.info.format);
|
||||
}
|
||||
total_used_memory += Common::AlignUp(tentative_size, 1024);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -1203,6 +1312,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {
|
||||
ASSERT_MSG(True(image.flags & ImageFlagBits::Registered),
|
||||
"Trying to unregister an already registered image");
|
||||
image.flags &= ~ImageFlagBits::Registered;
|
||||
image.flags &= ~ImageFlagBits::BadOverlap;
|
||||
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 = EstimatedDecompressedSize(tentative_size, image.info.format);
|
||||
}
|
||||
total_used_memory -= Common::AlignUp(tentative_size, 1024);
|
||||
ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
|
||||
const auto page_it = page_table.find(page);
|
||||
if (page_it == page_table.end()) {
|
||||
@@ -1276,9 +1393,19 @@ void TextureCache<P>::DeleteImage(ImageId image_id) {
|
||||
std::erase_if(other_image.aliased_images, [image_id](const AliasedImage& other_alias) {
|
||||
return other_alias.id == image_id;
|
||||
});
|
||||
other_image.CheckAliasState();
|
||||
ASSERT_MSG(num_removed_aliases == 1, "Invalid number of removed aliases: {}",
|
||||
num_removed_aliases);
|
||||
}
|
||||
for (const ImageId overlap_id : image.overlapping_images) {
|
||||
ImageBase& other_image = slot_images[overlap_id];
|
||||
[[maybe_unused]] const size_t num_removed_overlaps = std::erase_if(
|
||||
other_image.overlapping_images,
|
||||
[image_id](const ImageId other_overlap_id) { return other_overlap_id == image_id; });
|
||||
other_image.CheckBadOverlapState();
|
||||
ASSERT_MSG(num_removed_overlaps == 1, "Invalid number of removed overlapps: {}",
|
||||
num_removed_overlaps);
|
||||
}
|
||||
for (const ImageViewId image_view_id : image_view_ids) {
|
||||
sentenced_image_view.Push(std::move(slot_image_views[image_view_id]));
|
||||
slot_image_views.erase(image_view_id);
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "video_core/texture_cache/formatter.h"
|
||||
#include "video_core/texture_cache/samples_helper.h"
|
||||
#include "video_core/texture_cache/util.h"
|
||||
#include "video_core/textures/astc.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
@@ -580,6 +581,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
|
||||
|
||||
for (s32 layer = 0; layer < info.resources.layers; ++layer) {
|
||||
const std::span<const u8> src = input.subspan(host_offset);
|
||||
gpu_memory.ReadBlockUnsafe(gpu_addr + guest_offset, dst.data(), dst.size_bytes());
|
||||
|
||||
SwizzleTexture(dst, src, bytes_per_block, num_tiles.width, num_tiles.height,
|
||||
num_tiles.depth, block.height, block.depth);
|
||||
|
||||
@@ -884,8 +887,16 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8
|
||||
ASSERT(copy.image_extent == mip_size);
|
||||
ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width));
|
||||
ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height));
|
||||
DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent,
|
||||
output.subspan(output_offset));
|
||||
if (IsPixelFormatASTC(info.format)) {
|
||||
ASSERT(copy.image_extent.depth == 1);
|
||||
Tegra::Texture::ASTC::Decompress(input.subspan(copy.buffer_offset),
|
||||
copy.image_extent.width, copy.image_extent.height,
|
||||
copy.image_subresource.num_layers, tile_size.width,
|
||||
tile_size.height, output.subspan(output_offset));
|
||||
} else {
|
||||
DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent,
|
||||
output.subspan(output_offset));
|
||||
}
|
||||
copy.buffer_offset = output_offset;
|
||||
copy.buffer_row_length = mip_size.width;
|
||||
copy.buffer_image_height = mip_size.height;
|
||||
@@ -1087,7 +1098,15 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
|
||||
return std::nullopt;
|
||||
}
|
||||
const ImageInfo& existing = image.info;
|
||||
if (False(options & RelaxedOptions::Format)) {
|
||||
if (True(options & RelaxedOptions::Format)) {
|
||||
// Format checking is relaxed, but we still have to check for matching bytes per block.
|
||||
// This avoids creating a view for blits on UE4 titles where formats with different bytes
|
||||
// per block are aliased.
|
||||
if (BytesPerBlock(existing.format) != BytesPerBlock(candidate.format)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
// Format comaptibility is not relaxed, ensure we are creating a view on a compatible format
|
||||
if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -129,4 +129,7 @@ struct AstcBufferData {
|
||||
decltype(REPLICATE_BYTE_TO_16_TABLE) replicate_byte_to_16 = REPLICATE_BYTE_TO_16_TABLE;
|
||||
} constexpr ASTC_BUFFER_DATA;
|
||||
|
||||
void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t block_width, uint32_t block_height, std::span<uint8_t> output);
|
||||
|
||||
} // namespace Tegra::Texture::ASTC
|
||||
|
||||
@@ -50,7 +50,7 @@ NsightAftermathTracker::NsightAftermathTracker() {
|
||||
}
|
||||
dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / "gpucrash";
|
||||
|
||||
void(Common::FS::RemoveDirRecursively(dump_dir));
|
||||
Common::FS::RemoveDirRecursively(dump_dir);
|
||||
if (!Common::FS::CreateDir(dump_dir)) {
|
||||
LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
|
||||
return;
|
||||
|
||||
@@ -12,6 +12,14 @@ VkBool32 Callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* data,
|
||||
[[maybe_unused]] void* user_data) {
|
||||
// Skip logging known false-positive validation errors
|
||||
switch (static_cast<u32>(data->messageIdNumber)) {
|
||||
case 0x682a878au: // VUID-vkCmdBindVertexBuffers2EXT-pBuffers-parameter
|
||||
case 0x99fb7dfdu: // UNASSIGNED-RequiredParameter (vkCmdBindVertexBuffers2EXT pBuffers[0])
|
||||
return VK_FALSE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const std::string_view message{data->pMessage};
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
LOG_CRITICAL(Render_Vulkan, "{}", message);
|
||||
|
||||
@@ -408,6 +408,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
}
|
||||
logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld);
|
||||
|
||||
CollectPhysicalMemoryInfo();
|
||||
CollectTelemetryParameters();
|
||||
CollectToolingInfo();
|
||||
|
||||
@@ -818,6 +819,17 @@ void Device::CollectTelemetryParameters() {
|
||||
}
|
||||
}
|
||||
|
||||
void Device::CollectPhysicalMemoryInfo() {
|
||||
const auto mem_properties = physical.GetMemoryProperties();
|
||||
const std::size_t num_properties = mem_properties.memoryHeapCount;
|
||||
device_access_memory = 0;
|
||||
for (std::size_t element = 0; element < num_properties; element++) {
|
||||
if ((mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) {
|
||||
device_access_memory += mem_properties.memoryHeaps[element].size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Device::CollectToolingInfo() {
|
||||
if (!ext_tooling_info) {
|
||||
return;
|
||||
|
||||
@@ -225,6 +225,10 @@ public:
|
||||
return use_asynchronous_shaders;
|
||||
}
|
||||
|
||||
u64 GetDeviceLocalMemory() const {
|
||||
return device_access_memory;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Checks if the physical device is suitable.
|
||||
void CheckSuitability(bool requires_swapchain) const;
|
||||
@@ -244,6 +248,9 @@ private:
|
||||
/// Collects information about attached tools.
|
||||
void CollectToolingInfo();
|
||||
|
||||
/// Collects information about the device's local memory.
|
||||
void CollectPhysicalMemoryInfo();
|
||||
|
||||
/// Returns a list of queue initialization descriptors.
|
||||
std::vector<VkDeviceQueueCreateInfo> GetDeviceQueueCreateInfos() const;
|
||||
|
||||
@@ -302,6 +309,8 @@ private:
|
||||
|
||||
/// Nsight Aftermath GPU crash tracker
|
||||
std::unique_ptr<NsightAftermathTracker> nsight_aftermath_tracker;
|
||||
|
||||
u64 device_access_memory;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -69,10 +69,10 @@ constexpr VkExportMemoryAllocateInfo EXPORT_ALLOCATE_INFO{
|
||||
|
||||
class MemoryAllocation {
|
||||
public:
|
||||
explicit MemoryAllocation(vk::DeviceMemory memory_, VkMemoryPropertyFlags properties,
|
||||
u64 allocation_size_, u32 type)
|
||||
: memory{std::move(memory_)}, allocation_size{allocation_size_}, property_flags{properties},
|
||||
shifted_memory_type{1U << type} {}
|
||||
explicit MemoryAllocation(MemoryAllocator* const allocator_, vk::DeviceMemory memory_,
|
||||
VkMemoryPropertyFlags properties, u64 allocation_size_, u32 type)
|
||||
: allocator{allocator_}, memory{std::move(memory_)}, allocation_size{allocation_size_},
|
||||
property_flags{properties}, shifted_memory_type{1U << type} {}
|
||||
|
||||
#if defined(_WIN32) || defined(__unix__)
|
||||
~MemoryAllocation() {
|
||||
@@ -106,6 +106,10 @@ public:
|
||||
const auto it = std::ranges::find(commits, begin, &Range::begin);
|
||||
ASSERT_MSG(it != commits.end(), "Invalid commit");
|
||||
commits.erase(it);
|
||||
if (commits.empty()) {
|
||||
// Do not call any code involving 'this' after this call, the object will be destroyed
|
||||
allocator->ReleaseMemory(this);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<u8> Map() {
|
||||
@@ -171,6 +175,7 @@ private:
|
||||
return candidate;
|
||||
}
|
||||
|
||||
MemoryAllocator* const allocator; ///< Parent memory allocation.
|
||||
const vk::DeviceMemory memory; ///< Vulkan memory allocation handler.
|
||||
const u64 allocation_size; ///< Size of this allocation.
|
||||
const VkMemoryPropertyFlags property_flags; ///< Vulkan memory property flags.
|
||||
@@ -275,10 +280,17 @@ bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
allocations.push_back(std::make_unique<MemoryAllocation>(std::move(memory), flags, size, type));
|
||||
allocations.push_back(
|
||||
std::make_unique<MemoryAllocation>(this, std::move(memory), flags, size, type));
|
||||
return true;
|
||||
}
|
||||
|
||||
void MemoryAllocator::ReleaseMemory(MemoryAllocation* alloc) {
|
||||
const auto it = std::ranges::find(allocations, alloc, &std::unique_ptr<MemoryAllocation>::get);
|
||||
ASSERT(it != allocations.end());
|
||||
allocations.erase(it);
|
||||
}
|
||||
|
||||
std::optional<MemoryCommit> MemoryAllocator::TryCommit(const VkMemoryRequirements& requirements,
|
||||
VkMemoryPropertyFlags flags) {
|
||||
for (auto& allocation : allocations) {
|
||||
|
||||
@@ -69,6 +69,8 @@ private:
|
||||
/// Memory allocator container.
|
||||
/// Allocates and releases memory allocations on demand.
|
||||
class MemoryAllocator {
|
||||
friend MemoryAllocation;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Construct memory allocator
|
||||
@@ -104,6 +106,9 @@ private:
|
||||
/// Tries to allocate a chunk of memory.
|
||||
bool TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size);
|
||||
|
||||
/// Releases a chunk of memory.
|
||||
void ReleaseMemory(MemoryAllocation* alloc);
|
||||
|
||||
/// Tries to allocate a memory commit.
|
||||
std::optional<MemoryCommit> TryCommit(const VkMemoryRequirements& requirements,
|
||||
VkMemoryPropertyFlags flags);
|
||||
|
||||
@@ -100,8 +100,9 @@ struct Client::Impl {
|
||||
request.body = data;
|
||||
|
||||
httplib::Response response;
|
||||
httplib::Error error;
|
||||
|
||||
if (!cli->send(request, response)) {
|
||||
if (!cli->send(request, response, error)) {
|
||||
LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
|
||||
return WebResult{WebResult::Code::LibError, "Null response", ""};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia
|
||||
const auto build_id = std::string(Common::g_build_id);
|
||||
|
||||
const auto yuzu_build = fmt::format("yuzu Development Build | {}-{}", branch_name, description);
|
||||
const auto override_build = fmt::format(std::string(Common::g_title_bar_format_idle), build_id);
|
||||
const auto override_build =
|
||||
fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);
|
||||
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
@@ -51,11 +51,11 @@ void EmuThread::run() {
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& gpu = system.GPU();
|
||||
auto stop_token = stop_source.get_token();
|
||||
|
||||
system.RegisterHostThread();
|
||||
|
||||
auto& gpu = system.GPU();
|
||||
|
||||
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
|
||||
// execution.
|
||||
gpu.Start();
|
||||
@@ -65,7 +65,7 @@ void EmuThread::run() {
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
|
||||
system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
system.CurrentProcess()->GetTitleID(), stop_run,
|
||||
system.CurrentProcess()->GetTitleID(), stop_token,
|
||||
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
|
||||
emit LoadProgress(stage, value, total);
|
||||
});
|
||||
@@ -78,7 +78,7 @@ void EmuThread::run() {
|
||||
// so that the DebugModeLeft signal can be emitted before the
|
||||
// next execution step
|
||||
bool was_active = false;
|
||||
while (!stop_run) {
|
||||
while (!stop_token.stop_requested()) {
|
||||
if (running) {
|
||||
if (was_active) {
|
||||
emit DebugModeLeft();
|
||||
@@ -100,7 +100,7 @@ void EmuThread::run() {
|
||||
}
|
||||
running_guard = false;
|
||||
|
||||
if (!stop_run) {
|
||||
if (!stop_token.stop_requested()) {
|
||||
was_active = true;
|
||||
emit DebugModeEntered();
|
||||
}
|
||||
@@ -108,7 +108,7 @@ void EmuThread::run() {
|
||||
UNIMPLEMENTED();
|
||||
} else {
|
||||
std::unique_lock lock{running_mutex};
|
||||
running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
|
||||
running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,16 +89,16 @@ public:
|
||||
* Requests for the emulation thread to stop running
|
||||
*/
|
||||
void RequestStop() {
|
||||
stop_run = true;
|
||||
stop_source.request_stop();
|
||||
SetRunning(false);
|
||||
}
|
||||
|
||||
private:
|
||||
bool exec_step = false;
|
||||
bool running = false;
|
||||
std::atomic_bool stop_run{false};
|
||||
std::stop_source stop_source;
|
||||
std::mutex running_mutex;
|
||||
std::condition_variable running_cv;
|
||||
std::condition_variable_any running_cv;
|
||||
Common::Event running_wait{};
|
||||
std::atomic_bool running_guard{false};
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
|
||||
// This must be in alphabetical order according to action name as it must have the same order as
|
||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||
// clang-format off
|
||||
const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{
|
||||
const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{
|
||||
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
|
||||
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
|
||||
@@ -236,6 +236,7 @@ const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{
|
||||
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
|
||||
@@ -647,6 +648,8 @@ void Config::ReadDebuggingValues() {
|
||||
ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString();
|
||||
Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();
|
||||
Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool();
|
||||
Settings::values.enable_fs_access_log =
|
||||
ReadSetting(QStringLiteral("enable_fs_access_log"), false).toBool();
|
||||
Settings::values.reporting_services =
|
||||
ReadSetting(QStringLiteral("reporting_services"), false).toBool();
|
||||
Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool();
|
||||
@@ -754,6 +757,8 @@ void Config::ReadCpuValues() {
|
||||
QStringLiteral("cpuopt_unsafe_unfuse_fma"), true);
|
||||
ReadSettingGlobal(Settings::values.cpuopt_unsafe_reduce_fp_error,
|
||||
QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true);
|
||||
ReadSettingGlobal(Settings::values.cpuopt_unsafe_ignore_standard_fpcr,
|
||||
QStringLiteral("cpuopt_unsafe_ignore_standard_fpcr"), true);
|
||||
ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan,
|
||||
QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true);
|
||||
ReadSettingGlobal(Settings::values.cpuopt_unsafe_fastmem_check,
|
||||
@@ -807,13 +812,17 @@ void Config::ReadRendererValues() {
|
||||
QStringLiteral("use_asynchronous_gpu_emulation"), true);
|
||||
ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"),
|
||||
true);
|
||||
ReadSettingGlobal(Settings::values.accelerate_astc, QStringLiteral("accelerate_astc"), true);
|
||||
ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true);
|
||||
ReadSettingGlobal(Settings::values.disable_fps_limit, QStringLiteral("disable_fps_limit"),
|
||||
false);
|
||||
ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"),
|
||||
false);
|
||||
ReadSettingGlobal(Settings::values.use_asynchronous_shaders,
|
||||
QStringLiteral("use_asynchronous_shaders"), false);
|
||||
ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"),
|
||||
true);
|
||||
ReadSettingGlobal(Settings::values.use_caches_gc, QStringLiteral("use_caches_gc"), false);
|
||||
ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0);
|
||||
ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0);
|
||||
ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0);
|
||||
@@ -1258,6 +1267,8 @@ void Config::SaveDebuggingValues() {
|
||||
QString::fromStdString(Settings::values.program_args), QString{});
|
||||
WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);
|
||||
WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false);
|
||||
WriteSetting(QStringLiteral("enable_fs_access_log"), Settings::values.enable_fs_access_log,
|
||||
false);
|
||||
WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false);
|
||||
WriteSetting(QStringLiteral("use_debug_asserts"), Settings::values.use_debug_asserts, false);
|
||||
WriteSetting(QStringLiteral("disable_macro_jit"), Settings::values.disable_macro_jit, false);
|
||||
@@ -1334,6 +1345,8 @@ void Config::SaveCpuValues() {
|
||||
Settings::values.cpuopt_unsafe_unfuse_fma, true);
|
||||
WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_reduce_fp_error"),
|
||||
Settings::values.cpuopt_unsafe_reduce_fp_error, true);
|
||||
WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_ignore_standard_fpcr"),
|
||||
Settings::values.cpuopt_unsafe_ignore_standard_fpcr, true);
|
||||
WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"),
|
||||
Settings::values.cpuopt_unsafe_inaccurate_nan, true);
|
||||
WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_fastmem_check"),
|
||||
@@ -1388,13 +1401,17 @@ void Config::SaveRendererValues() {
|
||||
Settings::values.use_asynchronous_gpu_emulation, true);
|
||||
WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation,
|
||||
true);
|
||||
WriteSettingGlobal(QStringLiteral("accelerate_astc"), Settings::values.accelerate_astc, true);
|
||||
WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true);
|
||||
WriteSettingGlobal(QStringLiteral("disable_fps_limit"), Settings::values.disable_fps_limit,
|
||||
false);
|
||||
WriteSettingGlobal(QStringLiteral("use_assembly_shaders"),
|
||||
Settings::values.use_assembly_shaders, false);
|
||||
WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"),
|
||||
Settings::values.use_asynchronous_shaders, false);
|
||||
WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time,
|
||||
true);
|
||||
WriteSettingGlobal(QStringLiteral("use_caches_gc"), Settings::values.use_caches_gc, false);
|
||||
// Cast to double because Qt's written float values are not human-readable
|
||||
WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0);
|
||||
WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user