Compare commits
74 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81ed54d13e | ||
|
|
001675dced | ||
|
|
71d8d84b59 | ||
|
|
0509fe3377 | ||
|
|
2a2f0bfe9e | ||
|
|
043904bae1 | ||
|
|
756d76d971 | ||
|
|
5be2d6fd28 | ||
|
|
e6b80c2cf8 | ||
|
|
fe2e710003 | ||
|
|
6a082df427 | ||
|
|
9fbe188c01 | ||
|
|
a779cede7c | ||
|
|
7df790f1ae | ||
|
|
3e3bd425c1 | ||
|
|
2da91ec75b | ||
|
|
b9eee1c539 | ||
|
|
23ca1eb82e | ||
|
|
fa22695705 | ||
|
|
c4eafcc861 | ||
|
|
859deda3bb | ||
|
|
b023413c98 | ||
|
|
00358e2098 | ||
|
|
8e0cc3e59a | ||
|
|
48b6d41f1b | ||
|
|
63325cafbe | ||
|
|
acce512ae8 | ||
|
|
bd0e1d3a25 | ||
|
|
bb29dcb7f2 | ||
|
|
456adb95ff | ||
|
|
bd1a764827 | ||
|
|
928b64d2ce | ||
|
|
268b5764c7 | ||
|
|
5f97f74a9a | ||
|
|
70cc4c0f46 | ||
|
|
e80323b8b0 | ||
|
|
f183668a87 | ||
|
|
156ea746a3 | ||
|
|
e3688f0627 | ||
|
|
a3f80a97a3 | ||
|
|
cc8ac112fc | ||
|
|
42d8e08f78 | ||
|
|
d20c5ac720 | ||
|
|
e611f522c2 | ||
|
|
8ba551e1cd | ||
|
|
02e98f6c93 | ||
|
|
5566f3dbc0 | ||
|
|
4edfa6ad8f | ||
|
|
e1a92db519 | ||
|
|
6df9611059 | ||
|
|
7e846be376 | ||
|
|
346149dcf9 | ||
|
|
f9563c8f24 | ||
|
|
a1cb453470 | ||
|
|
9a7d2e3659 | ||
|
|
f10dc35dd0 | ||
|
|
7b39215c8a | ||
|
|
edb9c72e26 | ||
|
|
be16d92060 | ||
|
|
d5bf597436 | ||
|
|
1fb158ce90 | ||
|
|
705f111058 | ||
|
|
2868d4ba84 | ||
|
|
3e26141483 | ||
|
|
c439fc9be9 | ||
|
|
5ab8053511 | ||
|
|
b2862e4772 | ||
|
|
a75d70fa90 | ||
|
|
5665d05547 | ||
|
|
15c0c213b1 | ||
|
|
3862511a9a | ||
|
|
e9cf08c241 | ||
|
|
7737bdfd1a | ||
|
|
a1f19b61f8 |
@@ -376,7 +376,7 @@ if (ENABLE_SDL2)
|
||||
if (YUZU_USE_BUNDLED_SDL2)
|
||||
# Detect toolchain and platform
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
|
||||
set(SDL2_VER "SDL2-2.0.15-prerelease")
|
||||
set(SDL2_VER "SDL2-2.0.16")
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
|
||||
endif()
|
||||
@@ -396,7 +396,7 @@ if (ENABLE_SDL2)
|
||||
elseif (YUZU_USE_EXTERNAL_SDL2)
|
||||
message(STATUS "Using SDL2 from externals.")
|
||||
else()
|
||||
find_package(SDL2 2.0.15 REQUIRED)
|
||||
find_package(SDL2 2.0.16 REQUIRED)
|
||||
|
||||
# Some installations don't set SDL2_LIBRARIES
|
||||
if("${SDL2_LIBRARIES}" STREQUAL "")
|
||||
@@ -583,8 +583,32 @@ if (YUZU_USE_BUNDLED_FFMPEG)
|
||||
"${FFmpeg_PREFIX};${FFmpeg_BUILD_DIR}"
|
||||
CACHE PATH "Path to FFmpeg headers" FORCE)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
Include(FindPkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBVA libva)
|
||||
endif()
|
||||
if(LIBVA_FOUND)
|
||||
pkg_check_modules(LIBDRM libdrm REQUIRED)
|
||||
find_package(X11 REQUIRED)
|
||||
pkg_check_modules(LIBVA-DRM libva-drm REQUIRED)
|
||||
pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED)
|
||||
set(FFmpeg_LIBVA_LIBRARIES
|
||||
${LIBDRM_LIBRARIES}
|
||||
${X11_LIBRARIES}
|
||||
${LIBVA-DRM_LIBRARIES}
|
||||
${LIBVA-X11_LIBRARIES}
|
||||
${LIBVA_LIBRARIES})
|
||||
set(FFmpeg_HWACCEL_FLAGS
|
||||
--enable-hwaccel=h264_vaapi
|
||||
--enable-hwaccel=vp9_vaapi
|
||||
--enable-libdrm)
|
||||
message(STATUS "VA-API found")
|
||||
else()
|
||||
set(FFmpeg_HWACCEL_FLAGS --disable-vaapi)
|
||||
endif()
|
||||
|
||||
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
|
||||
# `--disable-{vaapi,vdpau}` is needed to avoid linking issues
|
||||
# `--disable-vdpau` is needed to avoid linking issues
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${FFmpeg_MAKEFILE}
|
||||
@@ -600,15 +624,16 @@ if (YUZU_USE_BUNDLED_FFMPEG)
|
||||
--disable-network
|
||||
--disable-postproc
|
||||
--disable-swresample
|
||||
--disable-vaapi
|
||||
--disable-vdpau
|
||||
--enable-decoder=h264
|
||||
--enable-decoder=vp9
|
||||
--cc="${CMAKE_C_COMPILER}"
|
||||
--cxx="${CMAKE_CXX_COMPILER}"
|
||||
${FFmpeg_HWACCEL_FLAGS}
|
||||
WORKING_DIRECTORY
|
||||
${FFmpeg_BUILD_DIR}
|
||||
)
|
||||
unset(FFmpeg_HWACCEL_FLAGS)
|
||||
|
||||
# Workaround for Ubuntu 18.04's older version of make not being able to call make as a child
|
||||
# with context of the jobserver. Also helps ninja users.
|
||||
@@ -618,9 +643,10 @@ if (YUZU_USE_BUNDLED_FFMPEG)
|
||||
OUTPUT_VARIABLE
|
||||
SYSTEM_THREADS)
|
||||
|
||||
set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${FFmpeg_LIBRARIES}
|
||||
${FFmpeg_BUILD_LIBRARIES}
|
||||
COMMAND
|
||||
make -j${SYSTEM_THREADS}
|
||||
WORKING_DIRECTORY
|
||||
@@ -630,7 +656,12 @@ if (YUZU_USE_BUNDLED_FFMPEG)
|
||||
# ALL makes this custom target build every time
|
||||
# but it won't actually build if the DEPENDS parameter is up to date
|
||||
add_custom_target(ffmpeg-configure ALL DEPENDS ${FFmpeg_MAKEFILE})
|
||||
add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_LIBRARIES} ffmpeg-configure)
|
||||
add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_BUILD_LIBRARIES} ffmpeg-configure)
|
||||
link_libraries(${FFmpeg_LIBVA_LIBRARIES})
|
||||
set(FFmpeg_LIBRARIES ${FFmpeg_LIBVA_LIBRARIES} ${FFmpeg_BUILD_LIBRARIES}
|
||||
CACHE PATH "Paths to FFmpeg libraries" FORCE)
|
||||
unset(FFmpeg_BUILD_LIBRARIES)
|
||||
unset(FFmpeg_LIBVA_LIBRARIES)
|
||||
|
||||
if (FFmpeg_FOUND)
|
||||
message(STATUS "Found FFmpeg version ${FFmpeg_VERSION}")
|
||||
|
||||
4
dist/qt_themes/default/style.qss
vendored
4
dist/qt_themes/default/style.qss
vendored
@@ -51,11 +51,11 @@ QPushButton#GPUStatusBarButton:hover {
|
||||
}
|
||||
|
||||
QPushButton#GPUStatusBarButton:checked {
|
||||
color: #ff8040;
|
||||
color: #b06020;
|
||||
}
|
||||
|
||||
QPushButton#GPUStatusBarButton:!checked {
|
||||
color: #40dd40;
|
||||
color: #109010;
|
||||
}
|
||||
|
||||
QPushButton#buttonRefreshDevices {
|
||||
|
||||
2
externals/SDL
vendored
2
externals/SDL
vendored
Submodule externals/SDL updated: 2f248a2a31...25f9ed87ff
@@ -176,3 +176,6 @@ if (MSVC)
|
||||
else()
|
||||
target_link_libraries(common PRIVATE zstd)
|
||||
endif()
|
||||
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID STREQUAL GNU)
|
||||
target_link_libraries(common PRIVATE backtrace)
|
||||
endif()
|
||||
|
||||
@@ -52,8 +52,12 @@ assert_noinline_call(const Fn& fn) {
|
||||
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
|
||||
#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
|
||||
#else // not debug
|
||||
#define DEBUG_ASSERT(_a_)
|
||||
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...)
|
||||
#define DEBUG_ASSERT(_a_) \
|
||||
do { \
|
||||
} while (0)
|
||||
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!")
|
||||
|
||||
@@ -61,7 +61,7 @@ template <typename ContiguousContainer>
|
||||
return out;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[17]) {
|
||||
[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[33]) {
|
||||
return HexStringToArray<16>(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,9 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
@@ -16,28 +12,189 @@
|
||||
#include <windows.h> // For OutputDebugStringW
|
||||
#endif
|
||||
|
||||
#include "common/assert.h"
|
||||
#if defined(__linux__) && defined(__GNUG__) && !defined(__clang__)
|
||||
#define BOOST_STACKTRACE_USE_BACKTRACE
|
||||
#include <boost/stacktrace.hpp>
|
||||
#undef BOOST_STACKTRACE_USE_BACKTRACE
|
||||
#include <signal.h>
|
||||
#define YUZU_LINUX_GCC_BACKTRACE
|
||||
#endif
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_paths.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/literals.h"
|
||||
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
#include "common/settings.h"
|
||||
#ifdef _WIN32
|
||||
#include "common/string_util.h"
|
||||
#endif
|
||||
#include "common/threadsafe_queue.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Interface for logging backends.
|
||||
*/
|
||||
class Backend {
|
||||
public:
|
||||
virtual ~Backend() = default;
|
||||
|
||||
virtual void Write(const Entry& entry) = 0;
|
||||
|
||||
virtual void EnableForStacktrace() = 0;
|
||||
|
||||
virtual void Flush() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to stderr and with color
|
||||
*/
|
||||
class ColorConsoleBackend final : public Backend {
|
||||
public:
|
||||
explicit ColorConsoleBackend() = default;
|
||||
|
||||
~ColorConsoleBackend() override = default;
|
||||
|
||||
void Write(const Entry& entry) override {
|
||||
if (enabled.load(std::memory_order_relaxed)) {
|
||||
PrintColoredMessage(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
// stderr shouldn't be buffered
|
||||
}
|
||||
|
||||
void EnableForStacktrace() override {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void SetEnabled(bool enabled_) {
|
||||
enabled = enabled_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic_bool enabled{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to a file passed into the constructor
|
||||
*/
|
||||
class FileBackend final : public Backend {
|
||||
public:
|
||||
explicit FileBackend(const std::filesystem::path& filename) {
|
||||
auto old_filename = filename;
|
||||
old_filename += ".old.txt";
|
||||
|
||||
// Existence checks are done within the functions themselves.
|
||||
// We don't particularly care if these succeed or not.
|
||||
static_cast<void>(FS::RemoveFile(old_filename));
|
||||
static_cast<void>(FS::RenameFile(filename, old_filename));
|
||||
|
||||
file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write,
|
||||
FS::FileType::TextFile);
|
||||
}
|
||||
|
||||
~FileBackend() override = default;
|
||||
|
||||
void Write(const Entry& entry) override {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
|
||||
|
||||
using namespace Common::Literals;
|
||||
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
|
||||
const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB;
|
||||
const bool write_limit_exceeded = bytes_written > write_limit;
|
||||
if (entry.log_level >= Level::Error || write_limit_exceeded) {
|
||||
if (write_limit_exceeded) {
|
||||
// Stop writing after the write limit is exceeded.
|
||||
// Don't close the file so we can print a stacktrace if necessary
|
||||
enabled = false;
|
||||
}
|
||||
file->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
file->Flush();
|
||||
}
|
||||
|
||||
void EnableForStacktrace() override {
|
||||
enabled = true;
|
||||
bytes_written = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FS::IOFile> file;
|
||||
bool enabled = true;
|
||||
std::size_t bytes_written = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to Visual Studio's output window
|
||||
*/
|
||||
class DebuggerBackend final : public Backend {
|
||||
public:
|
||||
explicit DebuggerBackend() = default;
|
||||
|
||||
~DebuggerBackend() override = default;
|
||||
|
||||
void Write(const Entry& entry) override {
|
||||
#ifdef _WIN32
|
||||
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void Flush() override {}
|
||||
|
||||
void EnableForStacktrace() override {}
|
||||
};
|
||||
|
||||
bool initialization_in_progress_suppress_logging = false;
|
||||
|
||||
#ifdef YUZU_LINUX_GCC_BACKTRACE
|
||||
[[noreturn]] void SleepForever() {
|
||||
while (true) {
|
||||
pause();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Static state as a singleton.
|
||||
*/
|
||||
class Impl {
|
||||
public:
|
||||
static Impl& Instance() {
|
||||
static Impl backend;
|
||||
return backend;
|
||||
if (!instance) {
|
||||
abort();
|
||||
}
|
||||
return *instance;
|
||||
}
|
||||
|
||||
static void Initialize() {
|
||||
if (instance) {
|
||||
abort();
|
||||
}
|
||||
using namespace Common::FS;
|
||||
initialization_in_progress_suppress_logging = true;
|
||||
const auto& log_dir = GetYuzuPath(YuzuPath::LogDir);
|
||||
void(CreateDir(log_dir));
|
||||
Filter filter;
|
||||
filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
||||
instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
|
||||
Deleter);
|
||||
initialization_in_progress_suppress_logging = false;
|
||||
}
|
||||
|
||||
Impl(const Impl&) = delete;
|
||||
@@ -46,74 +203,111 @@ public:
|
||||
Impl(Impl&&) = delete;
|
||||
Impl& operator=(Impl&&) = delete;
|
||||
|
||||
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, std::string message) {
|
||||
message_queue.Push(
|
||||
CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
|
||||
}
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
||||
std::lock_guard lock{writing_mutex};
|
||||
backends.push_back(std::move(backend));
|
||||
}
|
||||
|
||||
void RemoveBackend(std::string_view backend_name) {
|
||||
std::lock_guard lock{writing_mutex};
|
||||
|
||||
std::erase_if(backends, [&backend_name](const auto& backend) {
|
||||
return backend_name == backend->GetName();
|
||||
});
|
||||
}
|
||||
|
||||
const Filter& GetGlobalFilter() const {
|
||||
return filter;
|
||||
}
|
||||
|
||||
void SetGlobalFilter(const Filter& f) {
|
||||
filter = f;
|
||||
}
|
||||
|
||||
Backend* GetBackend(std::string_view backend_name) {
|
||||
const auto it =
|
||||
std::find_if(backends.begin(), backends.end(),
|
||||
[&backend_name](const auto& i) { return backend_name == i->GetName(); });
|
||||
if (it == backends.end())
|
||||
return nullptr;
|
||||
return it->get();
|
||||
void SetColorConsoleBackendEnabled(bool enabled) {
|
||||
color_console_backend.SetEnabled(enabled);
|
||||
}
|
||||
|
||||
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, std::string message) {
|
||||
if (!filter.CheckMessage(log_class, log_level))
|
||||
return;
|
||||
const Entry& entry =
|
||||
CreateEntry(log_class, log_level, filename, line_num, function, std::move(message));
|
||||
message_queue.Push(entry);
|
||||
}
|
||||
|
||||
private:
|
||||
Impl() {
|
||||
backend_thread = std::thread([&] {
|
||||
Entry entry;
|
||||
auto write_logs = [&](Entry& e) {
|
||||
std::lock_guard lock{writing_mutex};
|
||||
for (const auto& backend : backends) {
|
||||
backend->Write(e);
|
||||
}
|
||||
};
|
||||
while (true) {
|
||||
entry = message_queue.PopWait();
|
||||
if (entry.final_entry) {
|
||||
break;
|
||||
}
|
||||
write_logs(entry);
|
||||
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
|
||||
: filter{filter_}, file_backend{file_backend_filename}, backend_thread{std::thread([this] {
|
||||
Common::SetCurrentThreadName("yuzu:Log");
|
||||
Entry entry;
|
||||
const auto write_logs = [this, &entry]() {
|
||||
ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
|
||||
};
|
||||
while (true) {
|
||||
entry = message_queue.PopWait();
|
||||
if (entry.final_entry) {
|
||||
break;
|
||||
}
|
||||
write_logs();
|
||||
}
|
||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
|
||||
// case where a system is repeatedly spamming logs even on close.
|
||||
int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
|
||||
while (max_logs_to_write-- && message_queue.Pop(entry)) {
|
||||
write_logs();
|
||||
}
|
||||
})} {
|
||||
#ifdef YUZU_LINUX_GCC_BACKTRACE
|
||||
int waker_pipefd[2];
|
||||
int done_printing_pipefd[2];
|
||||
if (pipe2(waker_pipefd, O_CLOEXEC) || pipe2(done_printing_pipefd, O_CLOEXEC)) {
|
||||
abort();
|
||||
}
|
||||
backtrace_thread_waker_fd = waker_pipefd[1];
|
||||
backtrace_done_printing_fd = done_printing_pipefd[0];
|
||||
std::thread([this, wait_fd = waker_pipefd[0], done_fd = done_printing_pipefd[1]] {
|
||||
Common::SetCurrentThreadName("yuzu:Crash");
|
||||
for (u8 ignore = 0; read(wait_fd, &ignore, 1) != 1;)
|
||||
;
|
||||
const int sig = received_signal;
|
||||
if (sig <= 0) {
|
||||
abort();
|
||||
}
|
||||
|
||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
|
||||
// case where a system is repeatedly spamming logs even on close.
|
||||
const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
|
||||
int logs_written = 0;
|
||||
while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
|
||||
write_logs(entry);
|
||||
StopBackendThread();
|
||||
const auto signal_entry =
|
||||
CreateEntry(Class::Log, Level::Critical, "?", 0, "?",
|
||||
fmt::vformat("Received signal {}", fmt::make_format_args(sig)));
|
||||
ForEachBackend([&signal_entry](Backend& backend) {
|
||||
backend.EnableForStacktrace();
|
||||
backend.Write(signal_entry);
|
||||
});
|
||||
const auto backtrace =
|
||||
boost::stacktrace::stacktrace::from_dump(backtrace_storage.data(), 4096);
|
||||
for (const auto& frame : backtrace.as_vector()) {
|
||||
auto line = boost::stacktrace::detail::to_string(&frame, 1);
|
||||
if (line.empty()) {
|
||||
abort();
|
||||
}
|
||||
line.pop_back(); // Remove newline
|
||||
const auto frame_entry =
|
||||
CreateEntry(Class::Log, Level::Critical, "?", 0, "?", line);
|
||||
ForEachBackend([&frame_entry](Backend& backend) { backend.Write(frame_entry); });
|
||||
}
|
||||
});
|
||||
using namespace std::literals;
|
||||
const auto rip_entry = CreateEntry(Class::Log, Level::Critical, "?", 0, "?", "RIP"s);
|
||||
ForEachBackend([&rip_entry](Backend& backend) {
|
||||
backend.Write(rip_entry);
|
||||
backend.Flush();
|
||||
});
|
||||
for (const u8 anything = 0; write(done_fd, &anything, 1) != 1;)
|
||||
;
|
||||
// Abort on original thread to help debugging
|
||||
SleepForever();
|
||||
}).detach();
|
||||
signal(SIGSEGV, &HandleSignal);
|
||||
signal(SIGABRT, &HandleSignal);
|
||||
#endif
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
Entry entry;
|
||||
entry.final_entry = true;
|
||||
message_queue.Push(entry);
|
||||
#ifdef YUZU_LINUX_GCC_BACKTRACE
|
||||
if (int zero_or_ignore = 0;
|
||||
!received_signal.compare_exchange_strong(zero_or_ignore, SIGKILL)) {
|
||||
SleepForever();
|
||||
}
|
||||
#endif
|
||||
StopBackendThread();
|
||||
}
|
||||
|
||||
void StopBackendThread() {
|
||||
Entry stop_entry{};
|
||||
stop_entry.final_entry = true;
|
||||
message_queue.Push(stop_entry);
|
||||
backend_thread.join();
|
||||
}
|
||||
|
||||
@@ -135,100 +329,88 @@ private:
|
||||
};
|
||||
}
|
||||
|
||||
std::mutex writing_mutex;
|
||||
std::thread backend_thread;
|
||||
std::vector<std::unique_ptr<Backend>> backends;
|
||||
MPSCQueue<Entry> message_queue;
|
||||
Filter filter;
|
||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||
};
|
||||
|
||||
ConsoleBackend::~ConsoleBackend() = default;
|
||||
|
||||
void ConsoleBackend::Write(const Entry& entry) {
|
||||
PrintMessage(entry);
|
||||
}
|
||||
|
||||
ColorConsoleBackend::~ColorConsoleBackend() = default;
|
||||
|
||||
void ColorConsoleBackend::Write(const Entry& entry) {
|
||||
PrintColoredMessage(entry);
|
||||
}
|
||||
|
||||
FileBackend::FileBackend(const std::filesystem::path& filename) {
|
||||
auto old_filename = filename;
|
||||
old_filename += ".old.txt";
|
||||
|
||||
// Existence checks are done within the functions themselves.
|
||||
// We don't particularly care if these succeed or not.
|
||||
FS::RemoveFile(old_filename);
|
||||
void(FS::RenameFile(filename, old_filename));
|
||||
|
||||
file =
|
||||
std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
|
||||
}
|
||||
|
||||
FileBackend::~FileBackend() = default;
|
||||
|
||||
void FileBackend::Write(const Entry& entry) {
|
||||
if (!file->IsOpen()) {
|
||||
return;
|
||||
void ForEachBackend(auto lambda) {
|
||||
lambda(static_cast<Backend&>(debugger_backend));
|
||||
lambda(static_cast<Backend&>(color_console_backend));
|
||||
lambda(static_cast<Backend&>(file_backend));
|
||||
}
|
||||
|
||||
using namespace Common::Literals;
|
||||
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
|
||||
constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB;
|
||||
constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB;
|
||||
|
||||
const bool write_limit_exceeded =
|
||||
bytes_written > MAX_BYTES_WRITTEN_EXTENDED ||
|
||||
(bytes_written > MAX_BYTES_WRITTEN && !Settings::values.extended_logging);
|
||||
|
||||
// Close the file after the write limit is exceeded.
|
||||
if (write_limit_exceeded) {
|
||||
file->Close();
|
||||
return;
|
||||
static void Deleter(Impl* ptr) {
|
||||
delete ptr;
|
||||
}
|
||||
|
||||
bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
|
||||
if (entry.log_level >= Level::Error) {
|
||||
file->Flush();
|
||||
#ifdef YUZU_LINUX_GCC_BACKTRACE
|
||||
[[noreturn]] static void HandleSignal(int sig) {
|
||||
signal(SIGABRT, SIG_DFL);
|
||||
signal(SIGSEGV, SIG_DFL);
|
||||
if (sig <= 0) {
|
||||
abort();
|
||||
}
|
||||
instance->InstanceHandleSignal(sig);
|
||||
}
|
||||
}
|
||||
|
||||
DebuggerBackend::~DebuggerBackend() = default;
|
||||
|
||||
void DebuggerBackend::Write(const Entry& entry) {
|
||||
#ifdef _WIN32
|
||||
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||
[[noreturn]] void InstanceHandleSignal(int sig) {
|
||||
if (int zero_or_ignore = 0; !received_signal.compare_exchange_strong(zero_or_ignore, sig)) {
|
||||
if (received_signal == SIGKILL) {
|
||||
abort();
|
||||
}
|
||||
SleepForever();
|
||||
}
|
||||
// Don't restart like boost suggests. We want to append to the log file and not lose dynamic
|
||||
// symbols. This may segfault if it unwinds outside C/C++ code but we'll just have to fall
|
||||
// back to core dumps.
|
||||
boost::stacktrace::safe_dump_to(backtrace_storage.data(), 4096);
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
for (const int anything = 0; write(backtrace_thread_waker_fd, &anything, 1) != 1;)
|
||||
;
|
||||
for (u8 ignore = 0; read(backtrace_done_printing_fd, &ignore, 1) != 1;)
|
||||
;
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
|
||||
|
||||
Filter filter;
|
||||
DebuggerBackend debugger_backend{};
|
||||
ColorConsoleBackend color_console_backend{};
|
||||
FileBackend file_backend;
|
||||
|
||||
std::thread backend_thread;
|
||||
MPSCQueue<Entry> message_queue{};
|
||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||
|
||||
#ifdef YUZU_LINUX_GCC_BACKTRACE
|
||||
std::atomic_int received_signal{0};
|
||||
std::array<u8, 4096> backtrace_storage{};
|
||||
int backtrace_thread_waker_fd;
|
||||
int backtrace_done_printing_fd;
|
||||
#endif
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void Initialize() {
|
||||
Impl::Initialize();
|
||||
}
|
||||
|
||||
void DisableLoggingInTests() {
|
||||
initialization_in_progress_suppress_logging = true;
|
||||
}
|
||||
|
||||
void SetGlobalFilter(const Filter& filter) {
|
||||
Impl::Instance().SetGlobalFilter(filter);
|
||||
}
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
||||
Impl::Instance().AddBackend(std::move(backend));
|
||||
}
|
||||
|
||||
void RemoveBackend(std::string_view backend_name) {
|
||||
Impl::Instance().RemoveBackend(backend_name);
|
||||
}
|
||||
|
||||
Backend* GetBackend(std::string_view backend_name) {
|
||||
return Impl::Instance().GetBackend(backend_name);
|
||||
void SetColorConsoleBackendEnabled(bool enabled) {
|
||||
Impl::Instance().SetColorConsoleBackendEnabled(enabled);
|
||||
}
|
||||
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, const char* format,
|
||||
const fmt::format_args& args) {
|
||||
auto& instance = Impl::Instance();
|
||||
const auto& filter = instance.GetGlobalFilter();
|
||||
if (!filter.CheckMessage(log_class, log_level))
|
||||
return;
|
||||
|
||||
instance.PushEntry(log_class, log_level, filename, line_num, function,
|
||||
fmt::vformat(format, args));
|
||||
if (!initialization_in_progress_suppress_logging) {
|
||||
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
|
||||
fmt::vformat(format, args));
|
||||
}
|
||||
}
|
||||
} // namespace Common::Log
|
||||
|
||||
@@ -5,120 +5,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Common::FS {
|
||||
class IOFile;
|
||||
}
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
class Filter;
|
||||
|
||||
/**
|
||||
* Interface for logging backends. As loggers can be created and removed at runtime, this can be
|
||||
* used by a frontend for adding a custom logging backend as needed
|
||||
*/
|
||||
class Backend {
|
||||
public:
|
||||
virtual ~Backend() = default;
|
||||
/// Initializes the logging system. This should be the first thing called in main.
|
||||
void Initialize();
|
||||
|
||||
virtual void SetFilter(const Filter& new_filter) {
|
||||
filter = new_filter;
|
||||
}
|
||||
virtual const char* GetName() const = 0;
|
||||
virtual void Write(const Entry& entry) = 0;
|
||||
|
||||
private:
|
||||
Filter filter;
|
||||
};
|
||||
void DisableLoggingInTests();
|
||||
|
||||
/**
|
||||
* Backend that writes to stderr without any color commands
|
||||
*/
|
||||
class ConsoleBackend : public Backend {
|
||||
public:
|
||||
~ConsoleBackend() override;
|
||||
|
||||
static const char* Name() {
|
||||
return "console";
|
||||
}
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
void Write(const Entry& entry) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to stderr and with color
|
||||
*/
|
||||
class ColorConsoleBackend : public Backend {
|
||||
public:
|
||||
~ColorConsoleBackend() override;
|
||||
|
||||
static const char* Name() {
|
||||
return "color_console";
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
void Write(const Entry& entry) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to a file passed into the constructor
|
||||
*/
|
||||
class FileBackend : public Backend {
|
||||
public:
|
||||
explicit FileBackend(const std::filesystem::path& filename);
|
||||
~FileBackend() override;
|
||||
|
||||
static const char* Name() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
|
||||
void Write(const Entry& entry) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<FS::IOFile> file;
|
||||
std::size_t bytes_written = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to Visual Studio's output window
|
||||
*/
|
||||
class DebuggerBackend : public Backend {
|
||||
public:
|
||||
~DebuggerBackend() override;
|
||||
|
||||
static const char* Name() {
|
||||
return "debugger";
|
||||
}
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
void Write(const Entry& entry) override;
|
||||
};
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend);
|
||||
|
||||
void RemoveBackend(std::string_view backend_name);
|
||||
|
||||
Backend* GetBackend(std::string_view backend_name);
|
||||
|
||||
/**
|
||||
* The global filter will prevent any messages from even being processed if they are filtered. Each
|
||||
* backend can have a filter, but if the level is lower than the global filter, the backend will
|
||||
* never get the message
|
||||
* The global filter will prevent any messages from even being processed if they are filtered.
|
||||
*/
|
||||
void SetGlobalFilter(const Filter& filter);
|
||||
} // namespace Common::Log
|
||||
|
||||
void SetColorConsoleBackendEnabled(bool enabled);
|
||||
} // namespace Common::Log
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -74,14 +75,14 @@ public:
|
||||
*/
|
||||
explicit BasicSetting(const Type& default_val, const std::string& name)
|
||||
: default_value{default_val}, global{default_val}, label{name} {}
|
||||
~BasicSetting() = default;
|
||||
virtual ~BasicSetting() = default;
|
||||
|
||||
/**
|
||||
* Returns a reference to the setting's value.
|
||||
*
|
||||
* @returns A reference to the setting
|
||||
*/
|
||||
[[nodiscard]] const Type& GetValue() const {
|
||||
[[nodiscard]] virtual const Type& GetValue() const {
|
||||
return global;
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ public:
|
||||
*
|
||||
* @param value The desired value
|
||||
*/
|
||||
void SetValue(const Type& value) {
|
||||
virtual void SetValue(const Type& value) {
|
||||
Type temp{value};
|
||||
std::swap(global, temp);
|
||||
}
|
||||
@@ -120,7 +121,7 @@ public:
|
||||
*
|
||||
* @returns A reference to the setting
|
||||
*/
|
||||
const Type& operator=(const Type& value) {
|
||||
virtual const Type& operator=(const Type& value) {
|
||||
Type temp{value};
|
||||
std::swap(global, temp);
|
||||
return global;
|
||||
@@ -131,7 +132,7 @@ public:
|
||||
*
|
||||
* @returns A reference to the setting
|
||||
*/
|
||||
explicit operator const Type&() const {
|
||||
explicit virtual operator const Type&() const {
|
||||
return global;
|
||||
}
|
||||
|
||||
@@ -141,6 +142,51 @@ protected:
|
||||
const std::string label{}; ///< The setting's label
|
||||
};
|
||||
|
||||
/**
|
||||
* BasicRangedSetting class is intended for use with quantifiable settings that need a more
|
||||
* restrictive range than implicitly defined by its type. Implements a minimum and maximum that is
|
||||
* simply used to sanitize SetValue and the assignment overload.
|
||||
*/
|
||||
template <typename Type>
|
||||
class BasicRangedSetting : virtual public BasicSetting<Type> {
|
||||
public:
|
||||
/**
|
||||
* Sets a default value, minimum value, maximum value, and label.
|
||||
*
|
||||
* @param default_val Intial value of the setting, and default value of the setting
|
||||
* @param min_val Sets the minimum allowed value of the setting
|
||||
* @param max_val Sets the maximum allowed value of the setting
|
||||
* @param name Label for the setting
|
||||
*/
|
||||
explicit BasicRangedSetting(const Type& default_val, const Type& min_val, const Type& max_val,
|
||||
const std::string& name)
|
||||
: BasicSetting<Type>{default_val, name}, minimum{min_val}, maximum{max_val} {}
|
||||
virtual ~BasicRangedSetting() = default;
|
||||
|
||||
/**
|
||||
* Like BasicSetting's SetValue, except value is clamped to the range of the setting.
|
||||
*
|
||||
* @param value The desired value
|
||||
*/
|
||||
void SetValue(const Type& value) override {
|
||||
this->global = std::clamp(value, minimum, maximum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like BasicSetting's assignment overload, except value is clamped to the range of the setting.
|
||||
*
|
||||
* @param value The desired value
|
||||
* @returns A reference to the setting's value
|
||||
*/
|
||||
const Type& operator=(const Type& value) override {
|
||||
this->global = std::clamp(value, minimum, maximum);
|
||||
return this->global;
|
||||
}
|
||||
|
||||
const Type minimum; ///< Minimum allowed value of the setting
|
||||
const Type maximum; ///< Maximum allowed value of the setting
|
||||
};
|
||||
|
||||
/**
|
||||
* The Setting class is a slightly more complex version of the BasicSetting class. This adds a
|
||||
* custom setting to switch to when a guest application specifically requires it. The effect is that
|
||||
@@ -152,7 +198,7 @@ protected:
|
||||
* Like the BasicSetting, this requires setting a default value and label to use.
|
||||
*/
|
||||
template <typename Type>
|
||||
class Setting final : public BasicSetting<Type> {
|
||||
class Setting : virtual public BasicSetting<Type> {
|
||||
public:
|
||||
/**
|
||||
* Sets a default value, label, and setting value.
|
||||
@@ -162,7 +208,7 @@ public:
|
||||
*/
|
||||
explicit Setting(const Type& default_val, const std::string& name)
|
||||
: BasicSetting<Type>(default_val, name) {}
|
||||
~Setting() = default;
|
||||
virtual ~Setting() = default;
|
||||
|
||||
/**
|
||||
* Tells this setting to represent either the global or custom setting when other member
|
||||
@@ -191,7 +237,13 @@ public:
|
||||
*
|
||||
* @returns The required value of the setting
|
||||
*/
|
||||
[[nodiscard]] const Type& GetValue(bool need_global = false) const {
|
||||
[[nodiscard]] virtual const Type& GetValue() const override {
|
||||
if (use_global) {
|
||||
return this->global;
|
||||
}
|
||||
return custom;
|
||||
}
|
||||
[[nodiscard]] virtual const Type& GetValue(bool need_global) const {
|
||||
if (use_global || need_global) {
|
||||
return this->global;
|
||||
}
|
||||
@@ -203,7 +255,7 @@ public:
|
||||
*
|
||||
* @param value The new value
|
||||
*/
|
||||
void SetValue(const Type& value) {
|
||||
void SetValue(const Type& value) override {
|
||||
Type temp{value};
|
||||
if (use_global) {
|
||||
std::swap(this->global, temp);
|
||||
@@ -219,7 +271,7 @@ public:
|
||||
*
|
||||
* @returns A reference to the current setting value
|
||||
*/
|
||||
const Type& operator=(const Type& value) {
|
||||
const Type& operator=(const Type& value) override {
|
||||
Type temp{value};
|
||||
if (use_global) {
|
||||
std::swap(this->global, temp);
|
||||
@@ -234,18 +286,87 @@ public:
|
||||
*
|
||||
* @returns A reference to the current setting value
|
||||
*/
|
||||
explicit operator const Type&() const {
|
||||
virtual explicit operator const Type&() const override {
|
||||
if (use_global) {
|
||||
return this->global;
|
||||
}
|
||||
return custom;
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
bool use_global{true}; ///< The setting's global state
|
||||
Type custom{}; ///< The custom value of the setting
|
||||
};
|
||||
|
||||
/**
|
||||
* RangedSetting is a Setting that implements a maximum and minimum value for its setting. Intended
|
||||
* for use with quantifiable settings.
|
||||
*/
|
||||
template <typename Type>
|
||||
class RangedSetting final : public BasicRangedSetting<Type>, public Setting<Type> {
|
||||
public:
|
||||
/**
|
||||
* Sets a default value, minimum value, maximum value, and label.
|
||||
*
|
||||
* @param default_val Intial value of the setting, and default value of the setting
|
||||
* @param min_val Sets the minimum allowed value of the setting
|
||||
* @param max_val Sets the maximum allowed value of the setting
|
||||
* @param name Label for the setting
|
||||
*/
|
||||
explicit RangedSetting(const Type& default_val, const Type& min_val, const Type& max_val,
|
||||
const std::string& name)
|
||||
: BasicSetting<Type>{default_val, name},
|
||||
BasicRangedSetting<Type>{default_val, min_val, max_val, name}, Setting<Type>{default_val,
|
||||
name} {}
|
||||
virtual ~RangedSetting() = default;
|
||||
|
||||
// The following are needed to avoid a MSVC bug
|
||||
// (source: https://stackoverflow.com/questions/469508)
|
||||
[[nodiscard]] const Type& GetValue() const override {
|
||||
return Setting<Type>::GetValue();
|
||||
}
|
||||
[[nodiscard]] const Type& GetValue(bool need_global) const override {
|
||||
return Setting<Type>::GetValue(need_global);
|
||||
}
|
||||
explicit operator const Type&() const override {
|
||||
if (this->use_global) {
|
||||
return this->global;
|
||||
}
|
||||
return this->custom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like BasicSetting's SetValue, except value is clamped to the range of the setting. Sets the
|
||||
* appropriate value depending on the global state.
|
||||
*
|
||||
* @param value The desired value
|
||||
*/
|
||||
void SetValue(const Type& value) override {
|
||||
const Type temp = std::clamp(value, this->minimum, this->maximum);
|
||||
if (this->use_global) {
|
||||
this->global = temp;
|
||||
}
|
||||
this->custom = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like BasicSetting's assignment overload, except value is clamped to the range of the setting.
|
||||
* Uses the appropriate value depending on the global state.
|
||||
*
|
||||
* @param value The desired value
|
||||
* @returns A reference to the setting's value
|
||||
*/
|
||||
const Type& operator=(const Type& value) override {
|
||||
const Type temp = std::clamp(value, this->minimum, this->maximum);
|
||||
if (this->use_global) {
|
||||
this->global = temp;
|
||||
return this->global;
|
||||
}
|
||||
this->custom = temp;
|
||||
return this->custom;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The InputSetting class allows for getting a reference to either the global or custom members.
|
||||
* This is required as we cannot easily modify the values of user-defined types within containers
|
||||
@@ -289,13 +410,14 @@ struct Values {
|
||||
BasicSetting<std::string> sink_id{"auto", "output_engine"};
|
||||
BasicSetting<bool> audio_muted{false, "audio_muted"};
|
||||
Setting<bool> enable_audio_stretching{true, "enable_audio_stretching"};
|
||||
Setting<u8> volume{100, "volume"};
|
||||
RangedSetting<u8> volume{100, 0, 100, "volume"};
|
||||
|
||||
// Core
|
||||
Setting<bool> use_multi_core{true, "use_multi_core"};
|
||||
|
||||
// Cpu
|
||||
Setting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, "cpu_accuracy"};
|
||||
RangedSetting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
|
||||
CPUAccuracy::Unsafe, "cpu_accuracy"};
|
||||
// TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021
|
||||
BasicSetting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"};
|
||||
BasicSetting<bool> cpu_debug_mode{false, "cpu_debug_mode"};
|
||||
@@ -317,7 +439,8 @@ struct Values {
|
||||
Setting<bool> cpuopt_unsafe_fastmem_check{true, "cpuopt_unsafe_fastmem_check"};
|
||||
|
||||
// Renderer
|
||||
Setting<RendererBackend> renderer_backend{RendererBackend::OpenGL, "backend"};
|
||||
RangedSetting<RendererBackend> renderer_backend{
|
||||
RendererBackend::OpenGL, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"};
|
||||
BasicSetting<bool> renderer_debug{false, "debug"};
|
||||
BasicSetting<bool> renderer_shader_feedback{false, "shader_feedback"};
|
||||
BasicSetting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
|
||||
@@ -328,26 +451,28 @@ struct Values {
|
||||
Setting<u16> resolution_factor{1, "resolution_factor"};
|
||||
// *nix platforms may have issues with the borderless windowed fullscreen mode.
|
||||
// Default to exclusive fullscreen on these platforms for now.
|
||||
Setting<FullscreenMode> fullscreen_mode{
|
||||
RangedSetting<FullscreenMode> fullscreen_mode{
|
||||
#ifdef _WIN32
|
||||
FullscreenMode::Borderless,
|
||||
#else
|
||||
FullscreenMode::Exclusive,
|
||||
#endif
|
||||
"fullscreen_mode"};
|
||||
Setting<int> aspect_ratio{0, "aspect_ratio"};
|
||||
Setting<int> max_anisotropy{0, "max_anisotropy"};
|
||||
FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"};
|
||||
RangedSetting<int> aspect_ratio{0, 0, 3, "aspect_ratio"};
|
||||
RangedSetting<int> max_anisotropy{0, 0, 4, "max_anisotropy"};
|
||||
Setting<bool> use_speed_limit{true, "use_speed_limit"};
|
||||
Setting<u16> speed_limit{100, "speed_limit"};
|
||||
RangedSetting<u16> speed_limit{100, 0, 9999, "speed_limit"};
|
||||
Setting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
|
||||
Setting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, "gpu_accuracy"};
|
||||
RangedSetting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal,
|
||||
GPUAccuracy::Extreme, "gpu_accuracy"};
|
||||
Setting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"};
|
||||
Setting<bool> use_nvdec_emulation{true, "use_nvdec_emulation"};
|
||||
Setting<bool> accelerate_astc{true, "accelerate_astc"};
|
||||
Setting<bool> use_vsync{true, "use_vsync"};
|
||||
BasicSetting<u16> fps_cap{1000, "fps_cap"};
|
||||
BasicRangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
|
||||
BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"};
|
||||
Setting<ShaderBackend> shader_backend{ShaderBackend::GLASM, "shader_backend"};
|
||||
RangedSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL,
|
||||
ShaderBackend::SPIRV, "shader_backend"};
|
||||
Setting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
|
||||
Setting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
|
||||
Setting<bool> use_caches_gc{false, "use_caches_gc"};
|
||||
@@ -364,10 +489,10 @@ struct Values {
|
||||
std::chrono::seconds custom_rtc_differential;
|
||||
|
||||
BasicSetting<s32> current_user{0, "current_user"};
|
||||
Setting<s32> language_index{1, "language_index"};
|
||||
Setting<s32> region_index{1, "region_index"};
|
||||
Setting<s32> time_zone_index{0, "time_zone_index"};
|
||||
Setting<s32> sound_index{1, "sound_index"};
|
||||
RangedSetting<s32> language_index{1, 0, 16, "language_index"};
|
||||
RangedSetting<s32> region_index{1, 0, 6, "region_index"};
|
||||
RangedSetting<s32> time_zone_index{0, 0, 45, "time_zone_index"};
|
||||
RangedSetting<s32> sound_index{1, 0, 2, "sound_index"};
|
||||
|
||||
// Controls
|
||||
InputSetting<std::array<PlayerInput, 10>> players;
|
||||
@@ -384,7 +509,7 @@ struct Values {
|
||||
"udp_input_servers"};
|
||||
|
||||
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
|
||||
BasicSetting<u8> mouse_panning_sensitivity{10, "mouse_panning_sensitivity"};
|
||||
BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
|
||||
BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
std::string mouse_device;
|
||||
MouseButtonsRaw mouse_buttons;
|
||||
|
||||
@@ -6,10 +6,64 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/uuid.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsHexDigit(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
u8 HexCharToByte(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return static_cast<u8>(c - '0');
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return static_cast<u8>(c - 'a' + 10);
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return static_cast<u8>(c - 'A' + 10);
|
||||
}
|
||||
ASSERT_MSG(false, "{} is not a hexadecimal digit!", c);
|
||||
return u8{0};
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
u128 HexStringToU128(std::string_view hex_string) {
|
||||
const size_t length = hex_string.length();
|
||||
|
||||
// Detect "0x" prefix.
|
||||
const bool has_0x_prefix = length > 2 && hex_string[0] == '0' && hex_string[1] == 'x';
|
||||
const size_t offset = has_0x_prefix ? 2 : 0;
|
||||
|
||||
// Check length.
|
||||
if (length > 32 + offset) {
|
||||
ASSERT_MSG(false, "hex_string has more than 32 hexadecimal characters!");
|
||||
return INVALID_UUID;
|
||||
}
|
||||
|
||||
u64 lo = 0;
|
||||
u64 hi = 0;
|
||||
for (size_t i = 0; i < length - offset; ++i) {
|
||||
const char c = hex_string[length - 1 - i];
|
||||
if (!IsHexDigit(c)) {
|
||||
ASSERT_MSG(false, "{} is not a hexadecimal digit!", c);
|
||||
return INVALID_UUID;
|
||||
}
|
||||
if (i < 16) {
|
||||
lo |= u64{HexCharToByte(c)} << (i * 4);
|
||||
}
|
||||
if (i >= 16) {
|
||||
hi |= u64{HexCharToByte(c)} << ((i - 16) * 4);
|
||||
}
|
||||
}
|
||||
return u128{lo, hi};
|
||||
}
|
||||
|
||||
UUID UUID::Generate() {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -12,12 +13,30 @@ namespace Common {
|
||||
|
||||
constexpr u128 INVALID_UUID{{0, 0}};
|
||||
|
||||
/**
|
||||
* Converts a hex string to a 128-bit unsigned integer.
|
||||
*
|
||||
* The hex string can be formatted in lowercase or uppercase, with or without the "0x" prefix.
|
||||
*
|
||||
* This function will assert and return INVALID_UUID under the following conditions:
|
||||
* - If the hex string is more than 32 characters long
|
||||
* - If the hex string contains non-hexadecimal characters
|
||||
*
|
||||
* @param hex_string Hexadecimal string
|
||||
*
|
||||
* @returns A 128-bit unsigned integer if successfully converted, INVALID_UUID otherwise.
|
||||
*/
|
||||
[[nodiscard]] u128 HexStringToU128(std::string_view hex_string);
|
||||
|
||||
struct UUID {
|
||||
// UUIDs which are 0 are considered invalid!
|
||||
u128 uuid;
|
||||
UUID() = default;
|
||||
constexpr explicit UUID(const u128& id) : uuid{id} {}
|
||||
constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
|
||||
explicit UUID(std::string_view hex_string) {
|
||||
uuid = HexStringToU128(hex_string);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr explicit operator bool() const {
|
||||
return uuid != INVALID_UUID;
|
||||
@@ -50,3 +69,14 @@ struct UUID {
|
||||
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
|
||||
|
||||
} // namespace Common
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<Common::UUID> {
|
||||
size_t operator()(const Common::UUID& uuid) const noexcept {
|
||||
return uuid.uuid[1] ^ uuid.uuid[0];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
@@ -84,8 +84,6 @@ FileSys::StorageId GetStorageIdForFrontendSlot(
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
/*static*/ System System::s_instance;
|
||||
|
||||
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
const std::string& path) {
|
||||
// To account for split 00+01+etc files.
|
||||
@@ -425,6 +423,13 @@ struct System::Impl {
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
System::~System() = default;
|
||||
|
||||
void System::InitializeGlobalInstance() {
|
||||
if (s_instance) {
|
||||
abort();
|
||||
}
|
||||
s_instance = std::unique_ptr<System>(new System);
|
||||
}
|
||||
|
||||
CpuManager& System::GetCpuManager() {
|
||||
return impl->cpu_manager;
|
||||
}
|
||||
|
||||
@@ -121,9 +121,14 @@ public:
|
||||
* @returns Reference to the instance of the System singleton class.
|
||||
*/
|
||||
[[deprecated("Use of the global system instance is deprecated")]] static System& GetInstance() {
|
||||
return s_instance;
|
||||
if (!s_instance) {
|
||||
abort();
|
||||
}
|
||||
return *s_instance;
|
||||
}
|
||||
|
||||
static void InitializeGlobalInstance();
|
||||
|
||||
/// Enumeration representing the return values of the System Initialize and Load process.
|
||||
enum class ResultStatus : u32 {
|
||||
Success, ///< Succeeded
|
||||
@@ -396,7 +401,7 @@ private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
|
||||
static System s_instance;
|
||||
inline static std::unique_ptr<System> s_instance{};
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -377,7 +377,8 @@ void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) {
|
||||
|
||||
if (swkbd_config_common.use_utf8) {
|
||||
std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text);
|
||||
const u64 buffer_size = utf8_submitted_text.size();
|
||||
// Include the null terminator in the buffer size.
|
||||
const u64 buffer_size = utf8_submitted_text.size() + 1;
|
||||
|
||||
LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size,
|
||||
utf8_submitted_text);
|
||||
@@ -386,7 +387,8 @@ void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) {
|
||||
std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(),
|
||||
utf8_submitted_text.size());
|
||||
} else {
|
||||
const u64 buffer_size = current_text.size() * sizeof(char16_t);
|
||||
// Include the null terminator in the buffer size.
|
||||
const u64 buffer_size = (current_text.size() + 1) * sizeof(char16_t);
|
||||
|
||||
LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size,
|
||||
Common::UTF16ToUTF8(current_text));
|
||||
|
||||
@@ -166,8 +166,6 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
|
||||
LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size);
|
||||
} else {
|
||||
cmd_buffer.map_address = object->dma_map_addr;
|
||||
AddBufferMap(object->dma_map_addr, object->size, object->addr,
|
||||
object->status == nvmap::Object::Status::Allocated);
|
||||
}
|
||||
}
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer));
|
||||
@@ -178,30 +176,11 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
|
||||
}
|
||||
|
||||
NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBuffer params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer));
|
||||
std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
|
||||
SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
|
||||
|
||||
auto& gpu = system.GPU();
|
||||
|
||||
for (auto& cmd_buffer : cmd_buffer_handles) {
|
||||
const auto object{nvmap_dev->GetObject(cmd_buffer.map_handle)};
|
||||
if (!object) {
|
||||
LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmd_buffer.map_handle);
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return NvResult::InvalidState;
|
||||
}
|
||||
if (const auto size{RemoveBufferMap(object->dma_map_addr)}; size) {
|
||||
gpu.MemoryManager().Unmap(object->dma_map_addr, *size);
|
||||
} else {
|
||||
// This occurs quite frequently, however does not seem to impact functionality
|
||||
LOG_DEBUG(Service_NVDRV, "invalid offset=0x{:X} dma=0x{:X}", object->addr,
|
||||
object->dma_map_addr);
|
||||
}
|
||||
object->dma_map_addr = 0;
|
||||
}
|
||||
// This is intntionally stubbed.
|
||||
// Skip unmapping buffers here, as to not break the continuity of the VP9 reference frame
|
||||
// addresses, and risk invalidating data before the async GPU thread is done with it
|
||||
std::memset(output.data(), 0, output.size());
|
||||
LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
@@ -212,33 +191,4 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input,
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
std::optional<nvhost_nvdec_common::BufferMap> nvhost_nvdec_common::FindBufferMap(
|
||||
GPUVAddr gpu_addr) const {
|
||||
const auto it = std::find_if(
|
||||
buffer_mappings.begin(), buffer_mappings.upper_bound(gpu_addr), [&](const auto& entry) {
|
||||
return (gpu_addr >= entry.second.StartAddr() && gpu_addr < entry.second.EndAddr());
|
||||
});
|
||||
|
||||
ASSERT(it != buffer_mappings.end());
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void nvhost_nvdec_common::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
|
||||
bool is_allocated) {
|
||||
buffer_mappings.insert_or_assign(gpu_addr, BufferMap{gpu_addr, size, cpu_addr, is_allocated});
|
||||
}
|
||||
|
||||
std::optional<std::size_t> nvhost_nvdec_common::RemoveBufferMap(GPUVAddr gpu_addr) {
|
||||
const auto iter{buffer_mappings.find(gpu_addr)};
|
||||
if (iter == buffer_mappings.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::size_t size = 0;
|
||||
if (iter->second.IsAllocated()) {
|
||||
size = iter->second.Size();
|
||||
}
|
||||
buffer_mappings.erase(iter);
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -23,45 +23,6 @@ public:
|
||||
~nvhost_nvdec_common() override;
|
||||
|
||||
protected:
|
||||
class BufferMap final {
|
||||
public:
|
||||
constexpr BufferMap() = default;
|
||||
|
||||
constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_)
|
||||
: start_addr{start_addr_}, end_addr{start_addr_ + size_} {}
|
||||
|
||||
constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_,
|
||||
bool is_allocated_)
|
||||
: start_addr{start_addr_}, end_addr{start_addr_ + size_}, cpu_addr{cpu_addr_},
|
||||
is_allocated{is_allocated_} {}
|
||||
|
||||
constexpr VAddr StartAddr() const {
|
||||
return start_addr;
|
||||
}
|
||||
|
||||
constexpr VAddr EndAddr() const {
|
||||
return end_addr;
|
||||
}
|
||||
|
||||
constexpr std::size_t Size() const {
|
||||
return end_addr - start_addr;
|
||||
}
|
||||
|
||||
constexpr VAddr CpuAddr() const {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
constexpr bool IsAllocated() const {
|
||||
return is_allocated;
|
||||
}
|
||||
|
||||
private:
|
||||
GPUVAddr start_addr{};
|
||||
GPUVAddr end_addr{};
|
||||
VAddr cpu_addr{};
|
||||
bool is_allocated{};
|
||||
};
|
||||
|
||||
struct IoctlSetNvmapFD {
|
||||
s32_le nvmap_fd{};
|
||||
};
|
||||
@@ -154,17 +115,11 @@ protected:
|
||||
NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
|
||||
std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
|
||||
void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
|
||||
std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
|
||||
|
||||
s32_le nvmap_fd{};
|
||||
u32_le submit_timeout{};
|
||||
std::shared_ptr<nvmap> nvmap_dev;
|
||||
SyncpointManager& syncpoint_manager;
|
||||
std::array<u32, MaxSyncPoints> device_syncpoints{};
|
||||
// This is expected to be ordered, therefore we must use a map, not unordered_map
|
||||
std::map<GPUVAddr, BufferMap> buffer_mappings;
|
||||
};
|
||||
}; // namespace Devices
|
||||
} // namespace Service::Nvidia
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/atomic_ops.h"
|
||||
@@ -14,12 +12,10 @@
|
||||
#include "common/page_table.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/device_memory.h"
|
||||
#include "core/hle/kernel/k_page_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/physical_memory.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
@@ -62,17 +58,7 @@ struct Memory::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValidVirtualAddress(const Kernel::KProcess& process, const VAddr vaddr) const {
|
||||
const auto& page_table = process.PageTable().PageTableImpl();
|
||||
const auto [pointer, type] = page_table.pointers[vaddr >> PAGE_BITS].PointerType();
|
||||
return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
|
||||
}
|
||||
|
||||
bool IsValidVirtualAddress(VAddr vaddr) const {
|
||||
return IsValidVirtualAddress(*system.CurrentProcess(), vaddr);
|
||||
}
|
||||
|
||||
u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const {
|
||||
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const {
|
||||
const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]};
|
||||
|
||||
if (!paddr) {
|
||||
@@ -82,18 +68,6 @@ struct Memory::Impl {
|
||||
return system.DeviceMemory().GetPointer(paddr) + vaddr;
|
||||
}
|
||||
|
||||
u8* GetPointer(const VAddr vaddr) const {
|
||||
const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
|
||||
if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
|
||||
return pointer + vaddr;
|
||||
}
|
||||
const auto type = Common::PageTable::PageInfo::ExtractType(raw_pointer);
|
||||
if (type == Common::PageType::RasterizerCachedMemory) {
|
||||
return GetPointerFromRasterizerCachedMemory(vaddr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u8 Read8(const VAddr addr) {
|
||||
return Read<u8>(addr);
|
||||
}
|
||||
@@ -179,7 +153,7 @@ struct Memory::Impl {
|
||||
std::string string;
|
||||
string.reserve(max_length);
|
||||
for (std::size_t i = 0; i < max_length; ++i) {
|
||||
const char c = Read8(vaddr);
|
||||
const char c = Read<s8>(vaddr);
|
||||
if (c == '\0') {
|
||||
break;
|
||||
}
|
||||
@@ -190,15 +164,14 @@ struct Memory::Impl {
|
||||
return string;
|
||||
}
|
||||
|
||||
void ReadBlock(const Kernel::KProcess& process, const VAddr src_addr, void* dest_buffer,
|
||||
const std::size_t size) {
|
||||
void WalkBlock(const Kernel::KProcess& process, const VAddr addr, const std::size_t size,
|
||||
auto on_unmapped, auto on_memory, auto on_rasterizer, auto increment) {
|
||||
const auto& page_table = process.PageTable().PageTableImpl();
|
||||
|
||||
std::size_t remaining_size = size;
|
||||
std::size_t page_index = src_addr >> PAGE_BITS;
|
||||
std::size_t page_offset = src_addr & PAGE_MASK;
|
||||
std::size_t page_index = addr >> PAGE_BITS;
|
||||
std::size_t page_offset = addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
while (remaining_size) {
|
||||
const std::size_t copy_amount =
|
||||
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
@@ -206,22 +179,18 @@ struct Memory::Impl {
|
||||
const auto [pointer, type] = page_table.pointers[page_index].PointerType();
|
||||
switch (type) {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, src_addr, size);
|
||||
std::memset(dest_buffer, 0, copy_amount);
|
||||
on_unmapped(copy_amount, current_vaddr);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(pointer);
|
||||
const u8* const src_ptr = pointer + page_offset + (page_index << PAGE_BITS);
|
||||
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||
u8* mem_ptr = pointer + page_offset + (page_index << PAGE_BITS);
|
||||
on_memory(copy_amount, mem_ptr);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
||||
system.GPU().FlushRegion(current_vaddr, copy_amount);
|
||||
std::memcpy(dest_buffer, host_ptr, copy_amount);
|
||||
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
||||
on_rasterizer(current_vaddr, copy_amount, host_ptr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -230,248 +199,122 @@ struct Memory::Impl {
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
|
||||
increment(copy_amount);
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBlockUnsafe(const Kernel::KProcess& process, const VAddr src_addr, void* dest_buffer,
|
||||
const std::size_t size) {
|
||||
const auto& page_table = process.PageTable().PageTableImpl();
|
||||
|
||||
std::size_t remaining_size = size;
|
||||
std::size_t page_index = src_addr >> PAGE_BITS;
|
||||
std::size_t page_offset = src_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const std::size_t copy_amount =
|
||||
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
const auto [pointer, type] = page_table.pointers[page_index].PointerType();
|
||||
switch (type) {
|
||||
case Common::PageType::Unmapped: {
|
||||
template <bool UNSAFE>
|
||||
void ReadBlockImpl(const Kernel::KProcess& process, const VAddr src_addr, void* dest_buffer,
|
||||
const std::size_t size) {
|
||||
WalkBlock(
|
||||
process, src_addr, size,
|
||||
[src_addr, size, &dest_buffer](const std::size_t copy_amount,
|
||||
const VAddr current_vaddr) {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, src_addr, size);
|
||||
std::memset(dest_buffer, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(pointer);
|
||||
const u8* const src_ptr = pointer + page_offset + (page_index << PAGE_BITS);
|
||||
},
|
||||
[&dest_buffer](const std::size_t copy_amount, const u8* const src_ptr) {
|
||||
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
||||
},
|
||||
[&system = system, &dest_buffer](const VAddr current_vaddr,
|
||||
const std::size_t copy_amount,
|
||||
const u8* const host_ptr) {
|
||||
if constexpr (!UNSAFE) {
|
||||
system.GPU().FlushRegion(current_vaddr, copy_amount);
|
||||
}
|
||||
std::memcpy(dest_buffer, host_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
},
|
||||
[&dest_buffer](const std::size_t copy_amount) {
|
||||
dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
|
||||
});
|
||||
}
|
||||
|
||||
void ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) {
|
||||
ReadBlock(*system.CurrentProcess(), src_addr, dest_buffer, size);
|
||||
ReadBlockImpl<false>(*system.CurrentProcess(), src_addr, dest_buffer, size);
|
||||
}
|
||||
|
||||
void ReadBlockUnsafe(const VAddr src_addr, void* dest_buffer, const std::size_t size) {
|
||||
ReadBlockUnsafe(*system.CurrentProcess(), src_addr, dest_buffer, size);
|
||||
ReadBlockImpl<true>(*system.CurrentProcess(), src_addr, dest_buffer, size);
|
||||
}
|
||||
|
||||
void WriteBlock(const Kernel::KProcess& process, const VAddr dest_addr, const void* src_buffer,
|
||||
const std::size_t size) {
|
||||
const auto& page_table = process.PageTable().PageTableImpl();
|
||||
std::size_t remaining_size = size;
|
||||
std::size_t page_index = dest_addr >> PAGE_BITS;
|
||||
std::size_t page_offset = dest_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const std::size_t copy_amount =
|
||||
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
const auto [pointer, type] = page_table.pointers[page_index].PointerType();
|
||||
switch (type) {
|
||||
case Common::PageType::Unmapped: {
|
||||
template <bool UNSAFE>
|
||||
void WriteBlockImpl(const Kernel::KProcess& process, const VAddr dest_addr,
|
||||
const void* src_buffer, const std::size_t size) {
|
||||
WalkBlock(
|
||||
process, dest_addr, size,
|
||||
[dest_addr, size](const std::size_t copy_amount, const VAddr current_vaddr) {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(pointer);
|
||||
u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS);
|
||||
},
|
||||
[&src_buffer](const std::size_t copy_amount, u8* const dest_ptr) {
|
||||
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
||||
system.GPU().InvalidateRegion(current_vaddr, copy_amount);
|
||||
},
|
||||
[&system = system, &src_buffer](const VAddr current_vaddr,
|
||||
const std::size_t copy_amount, u8* const host_ptr) {
|
||||
if constexpr (!UNSAFE) {
|
||||
system.GPU().InvalidateRegion(current_vaddr, copy_amount);
|
||||
}
|
||||
std::memcpy(host_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteBlockUnsafe(const Kernel::KProcess& process, const VAddr dest_addr,
|
||||
const void* src_buffer, const std::size_t size) {
|
||||
const auto& page_table = process.PageTable().PageTableImpl();
|
||||
std::size_t remaining_size = size;
|
||||
std::size_t page_index = dest_addr >> PAGE_BITS;
|
||||
std::size_t page_offset = dest_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const std::size_t copy_amount =
|
||||
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
const auto [pointer, type] = page_table.pointers[page_index].PointerType();
|
||||
switch (type) {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(pointer);
|
||||
u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS);
|
||||
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
||||
std::memcpy(host_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
},
|
||||
[&src_buffer](const std::size_t copy_amount) {
|
||||
src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
|
||||
});
|
||||
}
|
||||
|
||||
void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) {
|
||||
WriteBlock(*system.CurrentProcess(), dest_addr, src_buffer, size);
|
||||
WriteBlockImpl<false>(*system.CurrentProcess(), dest_addr, src_buffer, size);
|
||||
}
|
||||
|
||||
void WriteBlockUnsafe(const VAddr dest_addr, const void* src_buffer, const std::size_t size) {
|
||||
WriteBlockUnsafe(*system.CurrentProcess(), dest_addr, src_buffer, size);
|
||||
WriteBlockImpl<true>(*system.CurrentProcess(), dest_addr, src_buffer, size);
|
||||
}
|
||||
|
||||
void ZeroBlock(const Kernel::KProcess& process, const VAddr dest_addr, const std::size_t size) {
|
||||
const auto& page_table = process.PageTable().PageTableImpl();
|
||||
std::size_t remaining_size = size;
|
||||
std::size_t page_index = dest_addr >> PAGE_BITS;
|
||||
std::size_t page_offset = dest_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const std::size_t copy_amount =
|
||||
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
const auto [pointer, type] = page_table.pointers[page_index].PointerType();
|
||||
switch (type) {
|
||||
case Common::PageType::Unmapped: {
|
||||
WalkBlock(
|
||||
process, dest_addr, size,
|
||||
[dest_addr, size](const std::size_t copy_amount, const VAddr current_vaddr) {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(pointer);
|
||||
u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS);
|
||||
},
|
||||
[](const std::size_t copy_amount, u8* const dest_ptr) {
|
||||
std::memset(dest_ptr, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
||||
},
|
||||
[&system = system](const VAddr current_vaddr, const std::size_t copy_amount,
|
||||
u8* const host_ptr) {
|
||||
system.GPU().InvalidateRegion(current_vaddr, copy_amount);
|
||||
std::memset(host_ptr, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
}
|
||||
|
||||
void ZeroBlock(const VAddr dest_addr, const std::size_t size) {
|
||||
ZeroBlock(*system.CurrentProcess(), dest_addr, size);
|
||||
},
|
||||
[](const std::size_t copy_amount) {});
|
||||
}
|
||||
|
||||
void CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr src_addr,
|
||||
const std::size_t size) {
|
||||
const auto& page_table = process.PageTable().PageTableImpl();
|
||||
std::size_t remaining_size = size;
|
||||
std::size_t page_index = src_addr >> PAGE_BITS;
|
||||
std::size_t page_offset = src_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const std::size_t copy_amount =
|
||||
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
const auto [pointer, type] = page_table.pointers[page_index].PointerType();
|
||||
switch (type) {
|
||||
case Common::PageType::Unmapped: {
|
||||
WalkBlock(
|
||||
process, dest_addr, size,
|
||||
[this, &process, &dest_addr, &src_addr, size](const std::size_t copy_amount,
|
||||
const VAddr current_vaddr) {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, src_addr, size);
|
||||
ZeroBlock(process, dest_addr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(pointer);
|
||||
const u8* src_ptr = pointer + page_offset + (page_index << PAGE_BITS);
|
||||
WriteBlock(process, dest_addr, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
||||
},
|
||||
[this, &process, &dest_addr](const std::size_t copy_amount, const u8* const src_ptr) {
|
||||
WriteBlockImpl<false>(process, dest_addr, src_ptr, copy_amount);
|
||||
},
|
||||
[this, &system = system, &process, &dest_addr](
|
||||
const VAddr current_vaddr, const std::size_t copy_amount, u8* const host_ptr) {
|
||||
system.GPU().FlushRegion(current_vaddr, copy_amount);
|
||||
WriteBlock(process, dest_addr, host_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
page_index++;
|
||||
page_offset = 0;
|
||||
dest_addr += static_cast<VAddr>(copy_amount);
|
||||
src_addr += static_cast<VAddr>(copy_amount);
|
||||
remaining_size -= copy_amount;
|
||||
}
|
||||
}
|
||||
|
||||
void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) {
|
||||
return CopyBlock(*system.CurrentProcess(), dest_addr, src_addr, size);
|
||||
WriteBlockImpl<false>(process, dest_addr, host_ptr, copy_amount);
|
||||
},
|
||||
[&dest_addr, &src_addr](const std::size_t copy_amount) {
|
||||
dest_addr += static_cast<VAddr>(copy_amount);
|
||||
src_addr += static_cast<VAddr>(copy_amount);
|
||||
});
|
||||
}
|
||||
|
||||
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
@@ -514,7 +357,7 @@ struct Memory::Impl {
|
||||
} else {
|
||||
// Switch page type to uncached if now uncached
|
||||
switch (page_type) {
|
||||
case Common::PageType::Unmapped:
|
||||
case Common::PageType::Unmapped: // NOLINT(bugprone-branch-clone)
|
||||
// It is not necessary for a process to have this region mapped into its address
|
||||
// space, for example, a system module need not have a VRAM mapping.
|
||||
break;
|
||||
@@ -597,6 +440,44 @@ struct Memory::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const {
|
||||
// AARCH64 masks the upper 16 bit of all memory accesses
|
||||
vaddr &= 0xffffffffffffLL;
|
||||
|
||||
if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
|
||||
on_unmapped();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Avoid adding any extra logic to this fast-path block
|
||||
const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
|
||||
if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
|
||||
return &pointer[vaddr];
|
||||
}
|
||||
switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
|
||||
case Common::PageType::Unmapped:
|
||||
on_unmapped();
|
||||
return nullptr;
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr);
|
||||
return nullptr;
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
|
||||
on_rasterizer();
|
||||
return host_ptr;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] u8* GetPointer(const VAddr vaddr) const {
|
||||
return GetPointerImpl(
|
||||
vaddr, [vaddr]() { LOG_ERROR(HW_Memory, "Unmapped GetPointer @ 0x{:016X}", vaddr); },
|
||||
[]() {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a particular data type out of memory at the given virtual address.
|
||||
*
|
||||
@@ -610,39 +491,17 @@ struct Memory::Impl {
|
||||
*/
|
||||
template <typename T>
|
||||
T Read(VAddr vaddr) {
|
||||
// AARCH64 masks the upper 16 bit of all memory accesses
|
||||
vaddr &= 0xffffffffffffLL;
|
||||
|
||||
if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
|
||||
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
|
||||
return 0;
|
||||
T result = 0;
|
||||
const u8* const ptr = GetPointerImpl(
|
||||
vaddr,
|
||||
[vaddr]() {
|
||||
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, vaddr);
|
||||
},
|
||||
[&system = system, vaddr]() { system.GPU().FlushRegion(vaddr, sizeof(T)); });
|
||||
if (ptr) {
|
||||
std::memcpy(&result, ptr, sizeof(T));
|
||||
}
|
||||
|
||||
// Avoid adding any extra logic to this fast-path block
|
||||
const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
|
||||
if (const u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
|
||||
T value;
|
||||
std::memcpy(&value, &pointer[vaddr], sizeof(T));
|
||||
return value;
|
||||
}
|
||||
switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
|
||||
case Common::PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
|
||||
return 0;
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
|
||||
system.GPU().FlushRegion(vaddr, sizeof(T));
|
||||
T value;
|
||||
std::memcpy(&value, host_ptr, sizeof(T));
|
||||
return value;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -656,110 +515,46 @@ struct Memory::Impl {
|
||||
*/
|
||||
template <typename T>
|
||||
void Write(VAddr vaddr, const T data) {
|
||||
// AARCH64 masks the upper 16 bit of all memory accesses
|
||||
vaddr &= 0xffffffffffffLL;
|
||||
|
||||
if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
|
||||
static_cast<u32>(data), vaddr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid adding any extra logic to this fast-path block
|
||||
const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
|
||||
if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
|
||||
std::memcpy(&pointer[vaddr], &data, sizeof(T));
|
||||
return;
|
||||
}
|
||||
switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
|
||||
case Common::PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
|
||||
static_cast<u32>(data), vaddr);
|
||||
return;
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
|
||||
system.GPU().InvalidateRegion(vaddr, sizeof(T));
|
||||
std::memcpy(host_ptr, &data, sizeof(T));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
u8* const ptr = GetPointerImpl(
|
||||
vaddr,
|
||||
[vaddr, data]() {
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8,
|
||||
vaddr, static_cast<u64>(data));
|
||||
},
|
||||
[&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(T)); });
|
||||
if (ptr) {
|
||||
std::memcpy(ptr, &data, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool WriteExclusive(VAddr vaddr, const T data, const T expected) {
|
||||
// AARCH64 masks the upper 16 bit of all memory accesses
|
||||
vaddr &= 0xffffffffffffLL;
|
||||
|
||||
if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
|
||||
static_cast<u32>(data), vaddr);
|
||||
return true;
|
||||
}
|
||||
|
||||
const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
|
||||
if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
|
||||
// NOTE: Avoid adding any extra logic to this fast-path block
|
||||
const auto volatile_pointer = reinterpret_cast<volatile T*>(&pointer[vaddr]);
|
||||
u8* const ptr = GetPointerImpl(
|
||||
vaddr,
|
||||
[vaddr, data]() {
|
||||
LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}",
|
||||
sizeof(T) * 8, vaddr, static_cast<u64>(data));
|
||||
},
|
||||
[&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(T)); });
|
||||
if (ptr) {
|
||||
const auto volatile_pointer = reinterpret_cast<volatile T*>(ptr);
|
||||
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
||||
}
|
||||
switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
|
||||
case Common::PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
|
||||
static_cast<u32>(data), vaddr);
|
||||
return true;
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
|
||||
system.GPU().InvalidateRegion(vaddr, sizeof(T));
|
||||
auto* pointer = reinterpret_cast<volatile T*>(&host_ptr);
|
||||
return Common::AtomicCompareAndSwap(pointer, data, expected);
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteExclusive128(VAddr vaddr, const u128 data, const u128 expected) {
|
||||
// AARCH64 masks the upper 16 bit of all memory accesses
|
||||
vaddr &= 0xffffffffffffLL;
|
||||
|
||||
if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
|
||||
static_cast<u32>(data[0]), vaddr);
|
||||
return true;
|
||||
}
|
||||
|
||||
const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
|
||||
if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
|
||||
// NOTE: Avoid adding any extra logic to this fast-path block
|
||||
const auto volatile_pointer = reinterpret_cast<volatile u64*>(&pointer[vaddr]);
|
||||
u8* const ptr = GetPointerImpl(
|
||||
vaddr,
|
||||
[vaddr, data]() {
|
||||
LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ 0x{:016X} = 0x{:016X}{:016X}",
|
||||
vaddr, static_cast<u64>(data[1]), static_cast<u64>(data[0]));
|
||||
},
|
||||
[&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(u128)); });
|
||||
if (ptr) {
|
||||
const auto volatile_pointer = reinterpret_cast<volatile u64*>(ptr);
|
||||
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
||||
}
|
||||
switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
|
||||
case Common::PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}{:016X}", sizeof(data) * 8,
|
||||
static_cast<u64>(data[1]), static_cast<u64>(data[0]), vaddr);
|
||||
return true;
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
|
||||
system.GPU().InvalidateRegion(vaddr, sizeof(u128));
|
||||
auto* pointer = reinterpret_cast<volatile u64*>(&host_ptr);
|
||||
return Common::AtomicCompareAndSwap(pointer, data, expected);
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -789,12 +584,11 @@ void Memory::UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
|
||||
impl->UnmapRegion(page_table, base, size);
|
||||
}
|
||||
|
||||
bool Memory::IsValidVirtualAddress(const Kernel::KProcess& process, const VAddr vaddr) const {
|
||||
return impl->IsValidVirtualAddress(process, vaddr);
|
||||
}
|
||||
|
||||
bool Memory::IsValidVirtualAddress(const VAddr vaddr) const {
|
||||
return impl->IsValidVirtualAddress(vaddr);
|
||||
const Kernel::KProcess& process = *system.CurrentProcess();
|
||||
const auto& page_table = process.PageTable().PageTableImpl();
|
||||
const auto [pointer, type] = page_table.pointers[vaddr >> PAGE_BITS].PointerType();
|
||||
return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
|
||||
}
|
||||
|
||||
u8* Memory::GetPointer(VAddr vaddr) {
|
||||
@@ -863,64 +657,38 @@ std::string Memory::ReadCString(VAddr vaddr, std::size_t max_length) {
|
||||
|
||||
void Memory::ReadBlock(const Kernel::KProcess& process, const VAddr src_addr, void* dest_buffer,
|
||||
const std::size_t size) {
|
||||
impl->ReadBlock(process, src_addr, dest_buffer, size);
|
||||
impl->ReadBlockImpl<false>(process, src_addr, dest_buffer, size);
|
||||
}
|
||||
|
||||
void Memory::ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) {
|
||||
impl->ReadBlock(src_addr, dest_buffer, size);
|
||||
}
|
||||
|
||||
void Memory::ReadBlockUnsafe(const Kernel::KProcess& process, const VAddr src_addr,
|
||||
void* dest_buffer, const std::size_t size) {
|
||||
impl->ReadBlockUnsafe(process, src_addr, dest_buffer, size);
|
||||
}
|
||||
|
||||
void Memory::ReadBlockUnsafe(const VAddr src_addr, void* dest_buffer, const std::size_t size) {
|
||||
impl->ReadBlockUnsafe(src_addr, dest_buffer, size);
|
||||
}
|
||||
|
||||
void Memory::WriteBlock(const Kernel::KProcess& process, VAddr dest_addr, const void* src_buffer,
|
||||
std::size_t size) {
|
||||
impl->WriteBlock(process, dest_addr, src_buffer, size);
|
||||
impl->WriteBlockImpl<false>(process, dest_addr, src_buffer, size);
|
||||
}
|
||||
|
||||
void Memory::WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) {
|
||||
impl->WriteBlock(dest_addr, src_buffer, size);
|
||||
}
|
||||
|
||||
void Memory::WriteBlockUnsafe(const Kernel::KProcess& process, VAddr dest_addr,
|
||||
const void* src_buffer, std::size_t size) {
|
||||
impl->WriteBlockUnsafe(process, dest_addr, src_buffer, size);
|
||||
}
|
||||
|
||||
void Memory::WriteBlockUnsafe(const VAddr dest_addr, const void* src_buffer,
|
||||
const std::size_t size) {
|
||||
impl->WriteBlockUnsafe(dest_addr, src_buffer, size);
|
||||
}
|
||||
|
||||
void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size) {
|
||||
impl->ZeroBlock(process, dest_addr, size);
|
||||
}
|
||||
|
||||
void Memory::ZeroBlock(VAddr dest_addr, std::size_t size) {
|
||||
impl->ZeroBlock(dest_addr, size);
|
||||
}
|
||||
|
||||
void Memory::CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr src_addr,
|
||||
const std::size_t size) {
|
||||
impl->CopyBlock(process, dest_addr, src_addr, size);
|
||||
}
|
||||
|
||||
void Memory::CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) {
|
||||
impl->CopyBlock(dest_addr, src_addr, size);
|
||||
}
|
||||
|
||||
void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
impl->RasterizerMarkRegionCached(vaddr, size, cached);
|
||||
}
|
||||
|
||||
bool IsKernelVirtualAddress(const VAddr vaddr) {
|
||||
return KERNEL_REGION_VADDR <= vaddr && vaddr < KERNEL_REGION_END;
|
||||
}
|
||||
|
||||
} // namespace Core::Memory
|
||||
|
||||
@@ -39,11 +39,6 @@ enum : VAddr {
|
||||
|
||||
/// Application stack
|
||||
DEFAULT_STACK_SIZE = 0x100000,
|
||||
|
||||
/// Kernel Virtual Address Range
|
||||
KERNEL_REGION_VADDR = 0xFFFFFF8000000000,
|
||||
KERNEL_REGION_SIZE = 0x7FFFE00000,
|
||||
KERNEL_REGION_END = KERNEL_REGION_VADDR + KERNEL_REGION_SIZE,
|
||||
};
|
||||
|
||||
/// Central class that handles all memory operations and state.
|
||||
@@ -56,7 +51,7 @@ public:
|
||||
Memory& operator=(const Memory&) = delete;
|
||||
|
||||
Memory(Memory&&) = default;
|
||||
Memory& operator=(Memory&&) = default;
|
||||
Memory& operator=(Memory&&) = delete;
|
||||
|
||||
/**
|
||||
* Resets the state of the Memory system.
|
||||
@@ -90,17 +85,6 @@ public:
|
||||
*/
|
||||
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size);
|
||||
|
||||
/**
|
||||
* Checks whether or not the supplied address is a valid virtual
|
||||
* address for the given process.
|
||||
*
|
||||
* @param process The emulated process to check the address against.
|
||||
* @param vaddr The virtual address to check the validity of.
|
||||
*
|
||||
* @returns True if the given virtual address is valid, false otherwise.
|
||||
*/
|
||||
bool IsValidVirtualAddress(const Kernel::KProcess& process, VAddr vaddr) const;
|
||||
|
||||
/**
|
||||
* Checks whether or not the supplied address is a valid virtual
|
||||
* address for the current process.
|
||||
@@ -109,7 +93,7 @@ public:
|
||||
*
|
||||
* @returns True if the given virtual address is valid, false otherwise.
|
||||
*/
|
||||
bool IsValidVirtualAddress(VAddr vaddr) const;
|
||||
[[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const;
|
||||
|
||||
/**
|
||||
* Gets a pointer to the given address.
|
||||
@@ -134,7 +118,7 @@ public:
|
||||
* @returns The pointer to the given address, if the address is valid.
|
||||
* If the address is not valid, nullptr will be returned.
|
||||
*/
|
||||
const u8* GetPointer(VAddr vaddr) const;
|
||||
[[nodiscard]] const u8* GetPointer(VAddr vaddr) const;
|
||||
|
||||
template <typename T>
|
||||
const T* GetPointer(VAddr vaddr) const {
|
||||
@@ -327,27 +311,6 @@ public:
|
||||
void ReadBlock(const Kernel::KProcess& process, VAddr src_addr, void* dest_buffer,
|
||||
std::size_t size);
|
||||
|
||||
/**
|
||||
* Reads a contiguous block of bytes from a specified process' address space.
|
||||
* This unsafe version does not trigger GPU flushing.
|
||||
*
|
||||
* @param process The process to read the data from.
|
||||
* @param src_addr The virtual address to begin reading from.
|
||||
* @param dest_buffer The buffer to place the read bytes into.
|
||||
* @param size The amount of data to read, in bytes.
|
||||
*
|
||||
* @note If a size of 0 is specified, then this function reads nothing and
|
||||
* no attempts to access memory are made at all.
|
||||
*
|
||||
* @pre dest_buffer must be at least size bytes in length, otherwise a
|
||||
* buffer overrun will occur.
|
||||
*
|
||||
* @post The range [dest_buffer, size) contains the read bytes from the
|
||||
* process' address space.
|
||||
*/
|
||||
void ReadBlockUnsafe(const Kernel::KProcess& process, VAddr src_addr, void* dest_buffer,
|
||||
std::size_t size);
|
||||
|
||||
/**
|
||||
* Reads a contiguous block of bytes from the current process' address space.
|
||||
*
|
||||
@@ -408,26 +371,6 @@ public:
|
||||
void WriteBlock(const Kernel::KProcess& process, VAddr dest_addr, const void* src_buffer,
|
||||
std::size_t size);
|
||||
|
||||
/**
|
||||
* Writes a range of bytes into a given process' address space at the specified
|
||||
* virtual address.
|
||||
* This unsafe version does not invalidate GPU Memory.
|
||||
*
|
||||
* @param process The process to write data into the address space of.
|
||||
* @param dest_addr The destination virtual address to begin writing the data at.
|
||||
* @param src_buffer The data to write into the process' address space.
|
||||
* @param size The size of the data to write, in bytes.
|
||||
*
|
||||
* @post The address range [dest_addr, size) in the process' address space
|
||||
* contains the data that was within src_buffer.
|
||||
*
|
||||
* @post If an attempt is made to write into an unmapped region of memory, the writes
|
||||
* will be ignored and an error will be logged.
|
||||
*
|
||||
*/
|
||||
void WriteBlockUnsafe(const Kernel::KProcess& process, VAddr dest_addr, const void* src_buffer,
|
||||
std::size_t size);
|
||||
|
||||
/**
|
||||
* Writes a range of bytes into the current process' address space at the specified
|
||||
* virtual address.
|
||||
@@ -467,29 +410,6 @@ public:
|
||||
*/
|
||||
void WriteBlockUnsafe(VAddr dest_addr, const void* src_buffer, std::size_t size);
|
||||
|
||||
/**
|
||||
* Fills the specified address range within a process' address space with zeroes.
|
||||
*
|
||||
* @param process The process that will have a portion of its memory zeroed out.
|
||||
* @param dest_addr The starting virtual address of the range to zero out.
|
||||
* @param size The size of the address range to zero out, in bytes.
|
||||
*
|
||||
* @post The range [dest_addr, size) within the process' address space is
|
||||
* filled with zeroes.
|
||||
*/
|
||||
void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size);
|
||||
|
||||
/**
|
||||
* Fills the specified address range within the current process' address space with zeroes.
|
||||
*
|
||||
* @param dest_addr The starting virtual address of the range to zero out.
|
||||
* @param size The size of the address range to zero out, in bytes.
|
||||
*
|
||||
* @post The range [dest_addr, size) within the current process' address space is
|
||||
* filled with zeroes.
|
||||
*/
|
||||
void ZeroBlock(VAddr dest_addr, std::size_t size);
|
||||
|
||||
/**
|
||||
* Copies data within a process' address space to another location within the
|
||||
* same address space.
|
||||
@@ -505,19 +425,6 @@ public:
|
||||
void CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr src_addr,
|
||||
std::size_t size);
|
||||
|
||||
/**
|
||||
* Copies data within the current process' address space to another location within the
|
||||
* same address space.
|
||||
*
|
||||
* @param dest_addr The destination virtual address to begin copying the data into.
|
||||
* @param src_addr The source virtual address to begin copying the data from.
|
||||
* @param size The size of the data to copy, in bytes.
|
||||
*
|
||||
* @post The range [dest_addr, size) within the current process' address space
|
||||
* contains the same data within the range [src_addr, size).
|
||||
*/
|
||||
void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size);
|
||||
|
||||
/**
|
||||
* Marks each page within the specified address range as cached or uncached.
|
||||
*
|
||||
@@ -535,7 +442,4 @@ private:
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
/// Determines if the given VAddr is a kernel address
|
||||
bool IsKernelVirtualAddress(VAddr vaddr);
|
||||
|
||||
} // namespace Core::Memory
|
||||
|
||||
@@ -304,10 +304,10 @@ std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers([
|
||||
}
|
||||
|
||||
std::string GenerateKeyboardParam(int key_code) {
|
||||
Common::ParamPackage param{
|
||||
{"engine", "keyboard"},
|
||||
{"code", std::to_string(key_code)},
|
||||
};
|
||||
Common::ParamPackage param;
|
||||
param.Set("engine", "keyboard");
|
||||
param.Set("code", key_code);
|
||||
param.Set("toggle", false);
|
||||
return param.Serialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ Common::ParamPackage MouseButtonFactory::GetNextInput() const {
|
||||
if (pad.button != MouseInput::MouseButton::Undefined) {
|
||||
params.Set("engine", "mouse");
|
||||
params.Set("button", static_cast<u16>(pad.button));
|
||||
params.Set("toggle", false);
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,12 @@ public:
|
||||
state.buttons.insert_or_assign(button, value);
|
||||
}
|
||||
|
||||
void PreSetButton(int button) {
|
||||
if (!state.buttons.contains(button)) {
|
||||
SetButton(button, false);
|
||||
}
|
||||
}
|
||||
|
||||
void SetMotion(SDL_ControllerSensorEvent event) {
|
||||
constexpr float gravity_constant = 9.80665f;
|
||||
std::lock_guard lock{mutex};
|
||||
@@ -155,9 +161,16 @@ public:
|
||||
state.axes.insert_or_assign(axis, value);
|
||||
}
|
||||
|
||||
float GetAxis(int axis, float range) const {
|
||||
void PreSetAxis(int axis) {
|
||||
if (!state.axes.contains(axis)) {
|
||||
SetAxis(axis, 0);
|
||||
}
|
||||
}
|
||||
|
||||
float GetAxis(int axis, float range, float offset) const {
|
||||
std::lock_guard lock{mutex};
|
||||
return static_cast<float>(state.axes.at(axis)) / (32767.0f * range);
|
||||
const float value = static_cast<float>(state.axes.at(axis)) / 32767.0f;
|
||||
return (value + offset) / range;
|
||||
}
|
||||
|
||||
bool RumblePlay(u16 amp_low, u16 amp_high) {
|
||||
@@ -174,9 +187,10 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const {
|
||||
float x = GetAxis(axis_x, range);
|
||||
float y = GetAxis(axis_y, range);
|
||||
std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range, float offset_x,
|
||||
float offset_y) const {
|
||||
float x = GetAxis(axis_x, range, offset_x);
|
||||
float y = GetAxis(axis_y, range, offset_y);
|
||||
y = -y; // 3DS uses an y-axis inverse from SDL
|
||||
|
||||
// Make sure the coordinates are in the unit circle,
|
||||
@@ -483,7 +497,7 @@ public:
|
||||
trigger_if_greater(trigger_if_greater_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
const float axis_value = joystick->GetAxis(axis, 1.0f);
|
||||
const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
|
||||
if (trigger_if_greater) {
|
||||
return axis_value > threshold;
|
||||
}
|
||||
@@ -500,12 +514,14 @@ private:
|
||||
class SDLAnalog final : public Input::AnalogDevice {
|
||||
public:
|
||||
explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_,
|
||||
bool invert_x_, bool invert_y_, float deadzone_, float range_)
|
||||
bool invert_x_, bool invert_y_, float deadzone_, float range_,
|
||||
float offset_x_, float offset_y_)
|
||||
: joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_),
|
||||
invert_y(invert_y_), deadzone(deadzone_), range(range_) {}
|
||||
invert_y(invert_y_), deadzone(deadzone_), range(range_), offset_x(offset_x_),
|
||||
offset_y(offset_y_) {}
|
||||
|
||||
std::tuple<float, float> GetStatus() const override {
|
||||
auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);
|
||||
auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range, offset_x, offset_y);
|
||||
const float r = std::sqrt((x * x) + (y * y));
|
||||
if (invert_x) {
|
||||
x = -x;
|
||||
@@ -522,8 +538,8 @@ public:
|
||||
}
|
||||
|
||||
std::tuple<float, float> GetRawStatus() const override {
|
||||
const float x = joystick->GetAxis(axis_x, range);
|
||||
const float y = joystick->GetAxis(axis_y, range);
|
||||
const float x = joystick->GetAxis(axis_x, range, offset_x);
|
||||
const float y = joystick->GetAxis(axis_y, range, offset_y);
|
||||
return {x, -y};
|
||||
}
|
||||
|
||||
@@ -555,6 +571,8 @@ private:
|
||||
const bool invert_y;
|
||||
const float deadzone;
|
||||
const float range;
|
||||
const float offset_x;
|
||||
const float offset_y;
|
||||
};
|
||||
|
||||
class SDLVibration final : public Input::VibrationDevice {
|
||||
@@ -621,7 +639,7 @@ public:
|
||||
trigger_if_greater(trigger_if_greater_) {}
|
||||
|
||||
Input::MotionStatus GetStatus() const override {
|
||||
const float axis_value = joystick->GetAxis(axis, 1.0f);
|
||||
const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
|
||||
bool trigger = axis_value < threshold;
|
||||
if (trigger_if_greater) {
|
||||
trigger = axis_value > threshold;
|
||||
@@ -720,13 +738,13 @@ public:
|
||||
LOG_ERROR(Input, "Unknown direction {}", direction_name);
|
||||
}
|
||||
// This is necessary so accessing GetAxis with axis won't crash
|
||||
joystick->SetAxis(axis, 0);
|
||||
joystick->PreSetAxis(axis);
|
||||
return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
|
||||
}
|
||||
|
||||
const int button = params.Get("button", 0);
|
||||
// This is necessary so accessing GetButton with button won't crash
|
||||
joystick->SetButton(button, false);
|
||||
joystick->PreSetButton(button);
|
||||
return std::make_unique<SDLButton>(joystick, button, toggle);
|
||||
}
|
||||
|
||||
@@ -757,13 +775,15 @@ public:
|
||||
const std::string invert_y_value = params.Get("invert_y", "+");
|
||||
const bool invert_x = invert_x_value == "-";
|
||||
const bool invert_y = invert_y_value == "-";
|
||||
const float offset_x = params.Get("offset_x", 0.0f);
|
||||
const float offset_y = params.Get("offset_y", 0.0f);
|
||||
auto joystick = state.GetSDLJoystickByGUID(guid, port);
|
||||
|
||||
// This is necessary so accessing GetAxis with axis_x and axis_y won't crash
|
||||
joystick->SetAxis(axis_x, 0);
|
||||
joystick->SetAxis(axis_y, 0);
|
||||
joystick->PreSetAxis(axis_x);
|
||||
joystick->PreSetAxis(axis_y);
|
||||
return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone,
|
||||
range);
|
||||
range, offset_x, offset_y);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -844,13 +864,13 @@ public:
|
||||
LOG_ERROR(Input, "Unknown direction {}", direction_name);
|
||||
}
|
||||
// This is necessary so accessing GetAxis with axis won't crash
|
||||
joystick->SetAxis(axis, 0);
|
||||
joystick->PreSetAxis(axis);
|
||||
return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
|
||||
}
|
||||
|
||||
const int button = params.Get("button", 0);
|
||||
// This is necessary so accessing GetButton with button won't crash
|
||||
joystick->SetButton(button, false);
|
||||
joystick->PreSetButton(button);
|
||||
return std::make_unique<SDLButtonMotion>(joystick, button);
|
||||
}
|
||||
|
||||
@@ -869,6 +889,9 @@ SDLState::SDLState() {
|
||||
RegisterFactory<VibrationDevice>("sdl", vibration_factory);
|
||||
RegisterFactory<MotionDevice>("sdl", motion_factory);
|
||||
|
||||
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
|
||||
|
||||
// Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
@@ -995,6 +1018,7 @@ Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid
|
||||
params.Set("port", port);
|
||||
params.Set("guid", std::move(guid));
|
||||
params.Set("button", button);
|
||||
params.Set("toggle", false);
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -1134,13 +1158,15 @@ Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& gu
|
||||
}
|
||||
|
||||
Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
|
||||
int axis_y) {
|
||||
int axis_y, float offset_x, float offset_y) {
|
||||
Common::ParamPackage params;
|
||||
params.Set("engine", "sdl");
|
||||
params.Set("port", port);
|
||||
params.Set("guid", guid);
|
||||
params.Set("axis_x", axis_x);
|
||||
params.Set("axis_y", axis_y);
|
||||
params.Set("offset_x", offset_x);
|
||||
params.Set("offset_y", offset_y);
|
||||
params.Set("invert_x", "+");
|
||||
params.Set("invert_y", "+");
|
||||
return params;
|
||||
@@ -1342,24 +1368,39 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa
|
||||
const auto& binding_left_y =
|
||||
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
|
||||
if (params.Has("guid2")) {
|
||||
joystick2->PreSetAxis(binding_left_x.value.axis);
|
||||
joystick2->PreSetAxis(binding_left_y.value.axis);
|
||||
const auto left_offset_x = -joystick2->GetAxis(binding_left_x.value.axis, 1.0f, 0);
|
||||
const auto left_offset_y = -joystick2->GetAxis(binding_left_y.value.axis, 1.0f, 0);
|
||||
mapping.insert_or_assign(
|
||||
Settings::NativeAnalog::LStick,
|
||||
BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(),
|
||||
binding_left_x.value.axis, binding_left_y.value.axis));
|
||||
binding_left_x.value.axis, binding_left_y.value.axis,
|
||||
left_offset_x, left_offset_y));
|
||||
} else {
|
||||
joystick->PreSetAxis(binding_left_x.value.axis);
|
||||
joystick->PreSetAxis(binding_left_y.value.axis);
|
||||
const auto left_offset_x = -joystick->GetAxis(binding_left_x.value.axis, 1.0f, 0);
|
||||
const auto left_offset_y = -joystick->GetAxis(binding_left_y.value.axis, 1.0f, 0);
|
||||
mapping.insert_or_assign(
|
||||
Settings::NativeAnalog::LStick,
|
||||
BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
|
||||
binding_left_x.value.axis, binding_left_y.value.axis));
|
||||
binding_left_x.value.axis, binding_left_y.value.axis,
|
||||
left_offset_x, left_offset_y));
|
||||
}
|
||||
const auto& binding_right_x =
|
||||
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||
const auto& binding_right_y =
|
||||
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||
joystick->PreSetAxis(binding_right_x.value.axis);
|
||||
joystick->PreSetAxis(binding_right_y.value.axis);
|
||||
const auto right_offset_x = -joystick->GetAxis(binding_right_x.value.axis, 1.0f, 0);
|
||||
const auto right_offset_y = -joystick->GetAxis(binding_right_y.value.axis, 1.0f, 0);
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::RStick,
|
||||
BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
|
||||
binding_right_x.value.axis,
|
||||
binding_right_y.value.axis));
|
||||
binding_right_y.value.axis, right_offset_x,
|
||||
right_offset_y));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@@ -1563,8 +1604,9 @@ public:
|
||||
}
|
||||
|
||||
if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
|
||||
// Set offset to zero since the joystick is not on center
|
||||
auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
|
||||
first_axis, axis);
|
||||
first_axis, axis, 0, 0);
|
||||
first_axis = -1;
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <math.h>
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/param_package.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
TEST_CASE("ParamPackage", "[common]") {
|
||||
Common::Log::DisableLoggingInTests();
|
||||
ParamPackage original{
|
||||
{"abc", "xyz"},
|
||||
{"def", "42"},
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
add_subdirectory(host_shaders)
|
||||
|
||||
if(LIBVA_FOUND)
|
||||
set_source_files_properties(command_classes/codecs/codec.cpp
|
||||
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
|
||||
endif()
|
||||
|
||||
add_library(video_core STATIC
|
||||
buffer_cache/buffer_base.h
|
||||
buffer_cache/buffer_cache.cpp
|
||||
@@ -92,6 +97,7 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_stream_buffer.h
|
||||
renderer_opengl/gl_texture_cache.cpp
|
||||
renderer_opengl/gl_texture_cache.h
|
||||
renderer_opengl/gl_texture_cache_base.cpp
|
||||
renderer_opengl/gl_query_cache.cpp
|
||||
renderer_opengl/gl_query_cache.h
|
||||
renderer_opengl/maxwell_to_gl.h
|
||||
@@ -150,6 +156,7 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/vk_swapchain.h
|
||||
renderer_vulkan/vk_texture_cache.cpp
|
||||
renderer_vulkan/vk_texture_cache.h
|
||||
renderer_vulkan/vk_texture_cache_base.cpp
|
||||
renderer_vulkan/vk_update_descriptor.cpp
|
||||
renderer_vulkan/vk_update_descriptor.h
|
||||
shader_cache.cpp
|
||||
@@ -181,6 +188,7 @@ add_library(video_core STATIC
|
||||
texture_cache/samples_helper.h
|
||||
texture_cache/slot_vector.h
|
||||
texture_cache/texture_cache.h
|
||||
texture_cache/texture_cache_base.h
|
||||
texture_cache/types.h
|
||||
texture_cache/util.cpp
|
||||
texture_cache/util.h
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
@@ -17,10 +16,47 @@ extern "C" {
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
#if defined(LIBVA_FOUND)
|
||||
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license
|
||||
namespace {
|
||||
constexpr std::array<const char*, 2> VAAPI_DRIVERS = {
|
||||
"i915",
|
||||
"amdgpu",
|
||||
};
|
||||
|
||||
AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) {
|
||||
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
|
||||
if (*p == AV_PIX_FMT_VAAPI) {
|
||||
return AV_PIX_FMT_VAAPI;
|
||||
}
|
||||
}
|
||||
LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
|
||||
return *pix_fmts;
|
||||
}
|
||||
|
||||
bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) {
|
||||
AVDictionary* hwdevice_options = nullptr;
|
||||
av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
|
||||
for (const auto& driver : VAAPI_DRIVERS) {
|
||||
av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
|
||||
const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI,
|
||||
nullptr, hwdevice_options, 0);
|
||||
if (hwdevice_error >= 0) {
|
||||
LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver);
|
||||
av_dict_free(&hwdevice_options);
|
||||
return true;
|
||||
}
|
||||
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error);
|
||||
}
|
||||
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers");
|
||||
av_dict_free(&hwdevice_options);
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
void AVFrameDeleter(AVFrame* ptr) {
|
||||
av_frame_unref(ptr);
|
||||
av_free(ptr);
|
||||
av_frame_free(&ptr);
|
||||
}
|
||||
|
||||
Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
|
||||
@@ -32,19 +68,31 @@ Codec::~Codec() {
|
||||
return;
|
||||
}
|
||||
// Free libav memory
|
||||
AVFrame* av_frame{nullptr};
|
||||
avcodec_send_packet(av_codec_ctx, nullptr);
|
||||
av_frame = av_frame_alloc();
|
||||
AVFrame* av_frame = av_frame_alloc();
|
||||
avcodec_receive_frame(av_codec_ctx, av_frame);
|
||||
avcodec_flush_buffers(av_codec_ctx);
|
||||
|
||||
av_frame_unref(av_frame);
|
||||
av_free(av_frame);
|
||||
av_frame_free(&av_frame);
|
||||
avcodec_close(av_codec_ctx);
|
||||
av_buffer_unref(&av_hw_device);
|
||||
}
|
||||
|
||||
void Codec::InitializeHwdec() {
|
||||
// Prioritize integrated GPU to mitigate bandwidth bottlenecks
|
||||
#if defined(LIBVA_FOUND)
|
||||
if (CreateVaapiHwdevice(&av_hw_device)) {
|
||||
const auto hw_device_ctx = av_buffer_ref(av_hw_device);
|
||||
ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
|
||||
av_codec_ctx->hw_device_ctx = hw_device_ctx;
|
||||
av_codec_ctx->get_format = GetHwFormat;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// TODO more GPU accelerated decoders
|
||||
}
|
||||
|
||||
void Codec::Initialize() {
|
||||
AVCodecID codec{AV_CODEC_ID_NONE};
|
||||
AVCodecID codec;
|
||||
switch (current_codec) {
|
||||
case NvdecCommon::VideoCodec::H264:
|
||||
codec = AV_CODEC_ID_H264;
|
||||
@@ -53,22 +101,24 @@ void Codec::Initialize() {
|
||||
codec = AV_CODEC_ID_VP9;
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
|
||||
return;
|
||||
}
|
||||
av_codec = avcodec_find_decoder(codec);
|
||||
av_codec_ctx = avcodec_alloc_context3(av_codec);
|
||||
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
|
||||
|
||||
// TODO(ameerj): libavcodec gpu hw acceleration
|
||||
|
||||
InitializeHwdec();
|
||||
if (!av_codec_ctx->hw_device_ctx) {
|
||||
LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
|
||||
}
|
||||
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
|
||||
if (av_error < 0) {
|
||||
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
|
||||
avcodec_close(av_codec_ctx);
|
||||
av_buffer_unref(&av_hw_device);
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
|
||||
@@ -80,36 +130,64 @@ void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
|
||||
|
||||
void Codec::Decode() {
|
||||
const bool is_first_frame = !initialized;
|
||||
if (!initialized) {
|
||||
if (is_first_frame) {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
bool vp9_hidden_frame = false;
|
||||
AVPacket packet{};
|
||||
av_init_packet(&packet);
|
||||
std::vector<u8> frame_data;
|
||||
|
||||
if (current_codec == NvdecCommon::VideoCodec::H264) {
|
||||
frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame);
|
||||
} else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
|
||||
frame_data = vp9_decoder->ComposeFrameHeader(state);
|
||||
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
|
||||
}
|
||||
|
||||
AVPacket packet{};
|
||||
av_init_packet(&packet);
|
||||
packet.data = frame_data.data();
|
||||
packet.size = static_cast<s32>(frame_data.size());
|
||||
|
||||
avcodec_send_packet(av_codec_ctx, &packet);
|
||||
|
||||
if (!vp9_hidden_frame) {
|
||||
// Only receive/store visible frames
|
||||
AVFramePtr frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
|
||||
avcodec_receive_frame(av_codec_ctx, frame.get());
|
||||
av_frames.push(std::move(frame));
|
||||
// Limit queue to 10 frames. Workaround for ZLA decode and queue spam
|
||||
if (av_frames.size() > 10) {
|
||||
av_frames.pop();
|
||||
}
|
||||
if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) {
|
||||
LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret);
|
||||
return;
|
||||
}
|
||||
// Only receive/store visible frames
|
||||
if (vp9_hidden_frame) {
|
||||
return;
|
||||
}
|
||||
AVFrame* hw_frame = av_frame_alloc();
|
||||
AVFrame* sw_frame = hw_frame;
|
||||
ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed");
|
||||
if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) {
|
||||
LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
|
||||
av_frame_free(&hw_frame);
|
||||
return;
|
||||
}
|
||||
if (!hw_frame->width || !hw_frame->height) {
|
||||
LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
|
||||
av_frame_free(&hw_frame);
|
||||
return;
|
||||
}
|
||||
#if defined(LIBVA_FOUND)
|
||||
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license
|
||||
if (hw_frame->format == AV_PIX_FMT_VAAPI) {
|
||||
sw_frame = av_frame_alloc();
|
||||
ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed");
|
||||
// Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
|
||||
// because Intel drivers crash unless using AV_PIX_FMT_NV12
|
||||
sw_frame->format = AV_PIX_FMT_NV12;
|
||||
const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
|
||||
ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret);
|
||||
av_frame_free(&hw_frame);
|
||||
}
|
||||
#endif
|
||||
if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) {
|
||||
UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format);
|
||||
av_frame_free(&sw_frame);
|
||||
return;
|
||||
}
|
||||
av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter});
|
||||
if (av_frames.size() > 10) {
|
||||
LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
|
||||
av_frames.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +197,6 @@ AVFramePtr Codec::GetCurrentFrame() {
|
||||
if (av_frames.empty()) {
|
||||
return AVFramePtr{nullptr, AVFrameDeleter};
|
||||
}
|
||||
|
||||
AVFramePtr frame = std::move(av_frames.front());
|
||||
av_frames.pop();
|
||||
return frame;
|
||||
@@ -144,6 +221,5 @@ std::string_view Codec::GetCurrentCodecName() const {
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Tegra
|
||||
|
||||
@@ -22,7 +22,6 @@ extern "C" {
|
||||
|
||||
namespace Tegra {
|
||||
class GPU;
|
||||
struct VicRegisters;
|
||||
|
||||
void AVFrameDeleter(AVFrame* ptr);
|
||||
using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
|
||||
@@ -55,10 +54,13 @@ public:
|
||||
[[nodiscard]] std::string_view GetCurrentCodecName() const;
|
||||
|
||||
private:
|
||||
void InitializeHwdec();
|
||||
|
||||
bool initialized{};
|
||||
NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
|
||||
|
||||
AVCodec* av_codec{nullptr};
|
||||
AVBufferRef* av_hw_device{nullptr};
|
||||
AVCodecContext* av_codec_ctx{nullptr};
|
||||
|
||||
GPU& gpu;
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
namespace Tegra::Decoder {
|
||||
namespace {
|
||||
constexpr u32 diff_update_probability = 252;
|
||||
constexpr u32 frame_sync_code = 0x498342;
|
||||
|
||||
// Default compressed header probabilities once frame context resets
|
||||
constexpr Vp9EntropyProbs default_probs{
|
||||
.y_mode_prob{
|
||||
@@ -361,8 +364,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state)
|
||||
InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
|
||||
|
||||
// surface_luma_offset[0:3] contains the address of the reference frame offsets in the following
|
||||
// order: last, golden, altref, current. It may be worthwhile to track the updates done here
|
||||
// to avoid buffering frame data needed for reference frame updating in the header composition.
|
||||
// order: last, golden, altref, current.
|
||||
std::copy(state.surface_luma_offset.begin(), state.surface_luma_offset.begin() + 4,
|
||||
vp9_info.frame_offsets.begin());
|
||||
|
||||
@@ -384,40 +386,25 @@ Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state)
|
||||
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(),
|
||||
current_frame.info.bitstream_size);
|
||||
}
|
||||
// Buffer two frames, saving the last show frame info
|
||||
if (!next_next_frame.bit_stream.empty()) {
|
||||
if (!next_frame.bit_stream.empty()) {
|
||||
Vp9FrameContainer temp{
|
||||
.info = current_frame.info,
|
||||
.bit_stream = std::move(current_frame.bit_stream),
|
||||
};
|
||||
next_next_frame.info.show_frame = current_frame.info.last_frame_shown;
|
||||
current_frame.info = next_next_frame.info;
|
||||
current_frame.bit_stream = std::move(next_next_frame.bit_stream);
|
||||
next_next_frame = std::move(temp);
|
||||
|
||||
if (!next_frame.bit_stream.empty()) {
|
||||
Vp9FrameContainer temp2{
|
||||
.info = current_frame.info,
|
||||
.bit_stream = std::move(current_frame.bit_stream),
|
||||
};
|
||||
next_frame.info.show_frame = current_frame.info.last_frame_shown;
|
||||
current_frame.info = next_frame.info;
|
||||
current_frame.bit_stream = std::move(next_frame.bit_stream);
|
||||
next_frame = std::move(temp2);
|
||||
} else {
|
||||
next_frame.info = current_frame.info;
|
||||
next_frame.bit_stream = std::move(current_frame.bit_stream);
|
||||
}
|
||||
next_frame.info.show_frame = current_frame.info.last_frame_shown;
|
||||
current_frame.info = next_frame.info;
|
||||
current_frame.bit_stream = std::move(next_frame.bit_stream);
|
||||
next_frame = std::move(temp);
|
||||
} else {
|
||||
next_next_frame.info = current_frame.info;
|
||||
next_next_frame.bit_stream = std::move(current_frame.bit_stream);
|
||||
next_frame.info = current_frame.info;
|
||||
next_frame.bit_stream = current_frame.bit_stream;
|
||||
}
|
||||
return current_frame;
|
||||
}
|
||||
|
||||
std::vector<u8> VP9::ComposeCompressedHeader() {
|
||||
VpxRangeEncoder writer{};
|
||||
const bool update_probs = current_frame_info.show_frame && !current_frame_info.is_key_frame;
|
||||
const bool update_probs = !current_frame_info.is_key_frame && current_frame_info.show_frame;
|
||||
if (!current_frame_info.lossless) {
|
||||
if (static_cast<u32>(current_frame_info.transform_mode) >= 3) {
|
||||
writer.Write(3, 2);
|
||||
@@ -613,86 +600,64 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
|
||||
|
||||
// Reset context
|
||||
prev_frame_probs = default_probs;
|
||||
swap_next_golden = false;
|
||||
swap_ref_indices = false;
|
||||
loop_filter_ref_deltas.fill(0);
|
||||
loop_filter_mode_deltas.fill(0);
|
||||
|
||||
// allow frames offsets to stabilize before checking for golden frames
|
||||
grace_period = 4;
|
||||
|
||||
// On key frames, all frame slots are set to the current frame,
|
||||
// so the value of the selected slot doesn't really matter.
|
||||
frame_ctxs.fill({current_frame_number, false, default_probs});
|
||||
frame_ctxs.fill(default_probs);
|
||||
|
||||
// intra only, meaning the frame can be recreated with no other references
|
||||
current_frame_info.intra_only = true;
|
||||
|
||||
} else {
|
||||
|
||||
if (!current_frame_info.show_frame) {
|
||||
uncomp_writer.WriteBit(current_frame_info.intra_only);
|
||||
if (!current_frame_info.last_frame_was_key) {
|
||||
swap_next_golden = !swap_next_golden;
|
||||
}
|
||||
} else {
|
||||
current_frame_info.intra_only = false;
|
||||
}
|
||||
if (!current_frame_info.error_resilient_mode) {
|
||||
uncomp_writer.WriteU(0, 2); // Reset frame context.
|
||||
}
|
||||
|
||||
// Last, Golden, Altref frames
|
||||
std::array<s32, 3> ref_frame_index{0, 1, 2};
|
||||
|
||||
// Set when next frame is hidden
|
||||
// altref and golden references are swapped
|
||||
if (swap_next_golden) {
|
||||
ref_frame_index = std::array<s32, 3>{0, 2, 1};
|
||||
const auto& curr_offsets = current_frame_info.frame_offsets;
|
||||
const auto& next_offsets = next_frame.info.frame_offsets;
|
||||
const bool ref_frames_different = curr_offsets[1] != curr_offsets[2];
|
||||
const bool next_references_swap =
|
||||
(next_offsets[1] == curr_offsets[2]) || (next_offsets[2] == curr_offsets[1]);
|
||||
const bool needs_ref_swap = ref_frames_different && next_references_swap;
|
||||
if (needs_ref_swap) {
|
||||
swap_ref_indices = !swap_ref_indices;
|
||||
}
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<0, 1, u32> refresh_last;
|
||||
BitField<1, 2, u32> refresh_golden;
|
||||
BitField<2, 1, u32> refresh_alt;
|
||||
} refresh_frame_flags;
|
||||
|
||||
// update Last Frame
|
||||
u64 refresh_frame_flags = 1;
|
||||
|
||||
// golden frame may refresh, determined if the next golden frame offset is changed
|
||||
bool golden_refresh = false;
|
||||
if (grace_period <= 0) {
|
||||
for (s32 index = 1; index < 3; ++index) {
|
||||
if (current_frame_info.frame_offsets[index] !=
|
||||
next_frame.info.frame_offsets[index]) {
|
||||
current_frame_info.refresh_frame[index] = true;
|
||||
golden_refresh = true;
|
||||
grace_period = 3;
|
||||
}
|
||||
refresh_frame_flags.raw = 0;
|
||||
for (u32 index = 0; index < 3; ++index) {
|
||||
// Refresh indices that use the current frame as an index
|
||||
if (curr_offsets[3] == next_offsets[index]) {
|
||||
refresh_frame_flags.raw |= 1u << index;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_frame_info.show_frame &&
|
||||
(!next_frame.info.show_frame || next_frame.info.is_key_frame)) {
|
||||
// Update golden frame
|
||||
refresh_frame_flags = swap_next_golden ? 2 : 4;
|
||||
if (swap_ref_indices) {
|
||||
const u32 temp = refresh_frame_flags.refresh_golden;
|
||||
refresh_frame_flags.refresh_golden.Assign(refresh_frame_flags.refresh_alt.Value());
|
||||
refresh_frame_flags.refresh_alt.Assign(temp);
|
||||
}
|
||||
|
||||
if (!current_frame_info.show_frame) {
|
||||
// Update altref
|
||||
refresh_frame_flags = swap_next_golden ? 2 : 4;
|
||||
} else if (golden_refresh) {
|
||||
refresh_frame_flags = 3;
|
||||
}
|
||||
|
||||
if (current_frame_info.intra_only) {
|
||||
uncomp_writer.WriteU(frame_sync_code, 24);
|
||||
uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8);
|
||||
uncomp_writer.WriteU(refresh_frame_flags.raw, 8);
|
||||
uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
|
||||
uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
|
||||
uncomp_writer.WriteBit(false); // Render and frame size different.
|
||||
} else {
|
||||
uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8);
|
||||
|
||||
for (s32 index = 1; index < 4; index++) {
|
||||
const bool swap_indices = needs_ref_swap ^ swap_ref_indices;
|
||||
const auto ref_frame_index = swap_indices ? std::array{0, 2, 1} : std::array{0, 1, 2};
|
||||
uncomp_writer.WriteU(refresh_frame_flags.raw, 8);
|
||||
for (size_t index = 1; index < 4; index++) {
|
||||
uncomp_writer.WriteU(ref_frame_index[index - 1], 3);
|
||||
uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1);
|
||||
}
|
||||
|
||||
uncomp_writer.WriteBit(true); // Frame size with refs.
|
||||
uncomp_writer.WriteBit(false); // Render and frame size different.
|
||||
uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv);
|
||||
@@ -714,10 +679,9 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
|
||||
frame_ctx_idx = 1;
|
||||
}
|
||||
|
||||
uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index.
|
||||
prev_frame_probs =
|
||||
frame_ctxs[frame_ctx_idx].probs; // reference probabilities for compressed header
|
||||
frame_ctxs[frame_ctx_idx] = {current_frame_number, false, current_frame_info.entropy};
|
||||
uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index.
|
||||
prev_frame_probs = frame_ctxs[frame_ctx_idx]; // reference probabilities for compressed header
|
||||
frame_ctxs[frame_ctx_idx] = current_frame_info.entropy;
|
||||
|
||||
uncomp_writer.WriteU(current_frame_info.first_level, 6);
|
||||
uncomp_writer.WriteU(current_frame_info.sharpness_level, 3);
|
||||
@@ -812,7 +776,6 @@ const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters
|
||||
current_frame_info = curr_frame.info;
|
||||
bitstream = std::move(curr_frame.bit_stream);
|
||||
}
|
||||
|
||||
// The uncompressed header routine sets PrevProb parameters needed for the compressed header
|
||||
auto uncomp_writer = ComposeUncompressedHeader();
|
||||
std::vector<u8> compressed_header = ComposeCompressedHeader();
|
||||
@@ -828,13 +791,6 @@ const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters
|
||||
frame.begin() + uncompressed_header.size());
|
||||
std::copy(bitstream.begin(), bitstream.end(),
|
||||
frame.begin() + uncompressed_header.size() + compressed_header.size());
|
||||
|
||||
// keep track of frame number
|
||||
current_frame_number++;
|
||||
grace_period--;
|
||||
|
||||
// don't display hidden frames
|
||||
hidden = !current_frame_info.show_frame;
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
namespace Tegra {
|
||||
class GPU;
|
||||
enum class FrameType { KeyFrame = 0, InterFrame = 1 };
|
||||
namespace Decoder {
|
||||
|
||||
/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
|
||||
@@ -124,7 +123,7 @@ public:
|
||||
|
||||
/// Returns true if the most recent frame was a hidden frame.
|
||||
[[nodiscard]] bool WasFrameHidden() const {
|
||||
return hidden;
|
||||
return !current_frame_info.show_frame;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -178,19 +177,12 @@ private:
|
||||
std::array<s8, 4> loop_filter_ref_deltas{};
|
||||
std::array<s8, 2> loop_filter_mode_deltas{};
|
||||
|
||||
bool hidden = false;
|
||||
s64 current_frame_number = -2; // since we buffer 2 frames
|
||||
s32 grace_period = 6; // frame offsets need to stabilize
|
||||
std::array<FrameContexts, 4> frame_ctxs{};
|
||||
Vp9FrameContainer next_frame{};
|
||||
Vp9FrameContainer next_next_frame{};
|
||||
bool swap_next_golden{};
|
||||
std::array<Vp9EntropyProbs, 4> frame_ctxs{};
|
||||
bool swap_ref_indices{};
|
||||
|
||||
Vp9PictureInfo current_frame_info{};
|
||||
Vp9EntropyProbs prev_frame_probs{};
|
||||
|
||||
s32 diff_update_probability = 252;
|
||||
s32 frame_sync_code = 0x498342;
|
||||
};
|
||||
|
||||
} // namespace Decoder
|
||||
|
||||
@@ -176,7 +176,7 @@ struct PictureInfo {
|
||||
.frame_size_changed = (vp9_flags & FrameFlags::FrameSizeChanged) != 0,
|
||||
.error_resilient_mode = (vp9_flags & FrameFlags::ErrorResilientMode) != 0,
|
||||
.last_frame_shown = (vp9_flags & FrameFlags::LastShowFrame) != 0,
|
||||
.show_frame = false,
|
||||
.show_frame = true,
|
||||
.ref_frame_sign_bias = ref_frame_sign_bias,
|
||||
.base_q_index = base_q_index,
|
||||
.y_dc_delta_q = y_dc_delta_q,
|
||||
@@ -296,12 +296,6 @@ struct RefPoolElement {
|
||||
bool refresh{};
|
||||
};
|
||||
|
||||
struct FrameContexts {
|
||||
s64 from;
|
||||
bool adapted;
|
||||
Vp9EntropyProbs probs;
|
||||
};
|
||||
|
||||
#define ASSERT_POSITION(field_name, position) \
|
||||
static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \
|
||||
"Field " #field_name " has invalid position")
|
||||
|
||||
@@ -39,7 +39,7 @@ void Nvdec::Execute() {
|
||||
codec->Decode();
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unknown codec {}", static_cast<u32>(codec->GetCurrentCodec()));
|
||||
UNIMPLEMENTED_MSG("Codec {}", codec->GetCurrentCodecName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,8 @@ void Vic::ProcessMethod(Method method, u32 argument) {
|
||||
case Method::SetOutputSurfaceLumaOffset:
|
||||
output_surface_luma_address = arg;
|
||||
break;
|
||||
case Method::SetOutputSurfaceChromaUOffset:
|
||||
output_surface_chroma_u_address = arg;
|
||||
break;
|
||||
case Method::SetOutputSurfaceChromaVOffset:
|
||||
output_surface_chroma_v_address = arg;
|
||||
case Method::SetOutputSurfaceChromaOffset:
|
||||
output_surface_chroma_address = arg;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -65,11 +62,10 @@ void Vic::Execute() {
|
||||
const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)};
|
||||
const AVFramePtr frame_ptr = nvdec_processor->GetFrame();
|
||||
const auto* frame = frame_ptr.get();
|
||||
if (!frame || frame->width == 0 || frame->height == 0) {
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
const VideoPixelFormat pixel_format =
|
||||
static_cast<VideoPixelFormat>(config.pixel_format.Value());
|
||||
const auto pixel_format = static_cast<VideoPixelFormat>(config.pixel_format.Value());
|
||||
switch (pixel_format) {
|
||||
case VideoPixelFormat::BGRA8:
|
||||
case VideoPixelFormat::RGBA8: {
|
||||
@@ -83,37 +79,37 @@ void Vic::Execute() {
|
||||
sws_freeContext(scaler_ctx);
|
||||
scaler_ctx = nullptr;
|
||||
|
||||
// FFmpeg returns all frames in YUV420, convert it into expected format
|
||||
scaler_ctx =
|
||||
sws_getContext(frame->width, frame->height, AV_PIX_FMT_YUV420P, frame->width,
|
||||
frame->height, target_format, 0, nullptr, nullptr, nullptr);
|
||||
// Frames are decoded into either YUV420 or NV12 formats. Convert to desired format
|
||||
scaler_ctx = sws_getContext(frame->width, frame->height,
|
||||
static_cast<AVPixelFormat>(frame->format), frame->width,
|
||||
frame->height, target_format, 0, nullptr, nullptr, nullptr);
|
||||
|
||||
scaler_width = frame->width;
|
||||
scaler_height = frame->height;
|
||||
}
|
||||
// Get Converted frame
|
||||
const std::size_t linear_size = frame->width * frame->height * 4;
|
||||
const u32 width = static_cast<u32>(frame->width);
|
||||
const u32 height = static_cast<u32>(frame->height);
|
||||
const std::size_t linear_size = width * height * 4;
|
||||
|
||||
// Only allocate frame_buffer once per stream, as the size is not expected to change
|
||||
if (!converted_frame_buffer) {
|
||||
converted_frame_buffer = AVMallocPtr{static_cast<u8*>(av_malloc(linear_size)), av_free};
|
||||
}
|
||||
|
||||
const int converted_stride{frame->width * 4};
|
||||
const std::array<int, 4> converted_stride{frame->width * 4, frame->height * 4, 0, 0};
|
||||
u8* const converted_frame_buf_addr{converted_frame_buffer.get()};
|
||||
|
||||
sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height,
|
||||
&converted_frame_buf_addr, &converted_stride);
|
||||
&converted_frame_buf_addr, converted_stride.data());
|
||||
|
||||
const u32 blk_kind = static_cast<u32>(config.block_linear_kind);
|
||||
if (blk_kind != 0) {
|
||||
// swizzle pitch linear to block linear
|
||||
const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
|
||||
const auto size = Tegra::Texture::CalculateSize(true, 4, frame->width, frame->height, 1,
|
||||
block_height, 0);
|
||||
const auto size =
|
||||
Tegra::Texture::CalculateSize(true, 4, width, height, 1, block_height, 0);
|
||||
luma_buffer.resize(size);
|
||||
Tegra::Texture::SwizzleSubrect(frame->width, frame->height, frame->width * 4,
|
||||
frame->width, 4, luma_buffer.data(),
|
||||
Tegra::Texture::SwizzleSubrect(width, height, width * 4, width, 4, luma_buffer.data(),
|
||||
converted_frame_buffer.get(), block_height, 0, 0);
|
||||
|
||||
gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), size);
|
||||
@@ -131,41 +127,65 @@ void Vic::Execute() {
|
||||
const std::size_t surface_height = config.surface_height_minus1 + 1;
|
||||
const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width));
|
||||
const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height));
|
||||
const std::size_t half_width = frame_width / 2;
|
||||
const std::size_t half_height = frame_height / 2;
|
||||
const std::size_t aligned_width = (surface_width + 0xff) & ~0xff;
|
||||
const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL;
|
||||
|
||||
const auto* luma_ptr = frame->data[0];
|
||||
const auto* chroma_b_ptr = frame->data[1];
|
||||
const auto* chroma_r_ptr = frame->data[2];
|
||||
const auto stride = static_cast<size_t>(frame->linesize[0]);
|
||||
const auto half_stride = static_cast<size_t>(frame->linesize[1]);
|
||||
|
||||
luma_buffer.resize(aligned_width * surface_height);
|
||||
chroma_buffer.resize(aligned_width * surface_height / 2);
|
||||
|
||||
// Populate luma buffer
|
||||
const u8* luma_src = frame->data[0];
|
||||
for (std::size_t y = 0; y < frame_height; ++y) {
|
||||
const std::size_t src = y * stride;
|
||||
const std::size_t dst = y * aligned_width;
|
||||
for (std::size_t x = 0; x < frame_width; ++x) {
|
||||
luma_buffer[dst + x] = luma_ptr[src + x];
|
||||
luma_buffer[dst + x] = luma_src[src + x];
|
||||
}
|
||||
}
|
||||
gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(),
|
||||
luma_buffer.size());
|
||||
|
||||
// Populate chroma buffer from both channels with interleaving.
|
||||
for (std::size_t y = 0; y < half_height; ++y) {
|
||||
const std::size_t src = y * half_stride;
|
||||
const std::size_t dst = y * aligned_width;
|
||||
// Chroma
|
||||
const std::size_t half_height = frame_height / 2;
|
||||
const auto half_stride = static_cast<size_t>(frame->linesize[1]);
|
||||
|
||||
for (std::size_t x = 0; x < half_width; ++x) {
|
||||
chroma_buffer[dst + x * 2] = chroma_b_ptr[src + x];
|
||||
chroma_buffer[dst + x * 2 + 1] = chroma_r_ptr[src + x];
|
||||
switch (frame->format) {
|
||||
case AV_PIX_FMT_YUV420P: {
|
||||
// Frame from FFmpeg software
|
||||
// Populate chroma buffer from both channels with interleaving.
|
||||
const std::size_t half_width = frame_width / 2;
|
||||
const u8* chroma_b_src = frame->data[1];
|
||||
const u8* chroma_r_src = frame->data[2];
|
||||
for (std::size_t y = 0; y < half_height; ++y) {
|
||||
const std::size_t src = y * half_stride;
|
||||
const std::size_t dst = y * aligned_width;
|
||||
|
||||
for (std::size_t x = 0; x < half_width; ++x) {
|
||||
chroma_buffer[dst + x * 2] = chroma_b_src[src + x];
|
||||
chroma_buffer[dst + x * 2 + 1] = chroma_r_src[src + x];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(),
|
||||
case AV_PIX_FMT_NV12: {
|
||||
// Frame from VA-API hardware
|
||||
// This is already interleaved so just copy
|
||||
const u8* chroma_src = frame->data[1];
|
||||
for (std::size_t y = 0; y < half_height; ++y) {
|
||||
const std::size_t src = y * stride;
|
||||
const std::size_t dst = y * aligned_width;
|
||||
for (std::size_t x = 0; x < frame_width; ++x) {
|
||||
chroma_buffer[dst + x] = chroma_src[src + x];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
gpu.MemoryManager().WriteBlock(output_surface_chroma_address, chroma_buffer.data(),
|
||||
chroma_buffer.size());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ public:
|
||||
SetControlParams = 0x1c1,
|
||||
SetConfigStructOffset = 0x1c2,
|
||||
SetOutputSurfaceLumaOffset = 0x1c8,
|
||||
SetOutputSurfaceChromaUOffset = 0x1c9,
|
||||
SetOutputSurfaceChromaVOffset = 0x1ca
|
||||
SetOutputSurfaceChromaOffset = 0x1c9,
|
||||
SetOutputSurfaceChromaUnusedOffset = 0x1ca
|
||||
};
|
||||
|
||||
explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor);
|
||||
@@ -64,8 +64,7 @@ private:
|
||||
|
||||
GPUVAddr config_struct_address{};
|
||||
GPUVAddr output_surface_luma_address{};
|
||||
GPUVAddr output_surface_chroma_u_address{};
|
||||
GPUVAddr output_surface_chroma_v_address{};
|
||||
GPUVAddr output_surface_chroma_address{};
|
||||
|
||||
SwsContext* scaler_ctx{};
|
||||
s32 scaler_width{};
|
||||
|
||||
@@ -10,33 +10,27 @@
|
||||
#define END_PUSH_CONSTANTS };
|
||||
#define UNIFORM(n)
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_ENC_BUFFER 1
|
||||
#define BINDING_SWIZZLE_BUFFER 2
|
||||
#define BINDING_OUTPUT_IMAGE 3
|
||||
#define BINDING_OUTPUT_IMAGE 1
|
||||
|
||||
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
|
||||
|
||||
#define BEGIN_PUSH_CONSTANTS
|
||||
#define END_PUSH_CONSTANTS
|
||||
#define UNIFORM(n) layout(location = n) uniform
|
||||
#define BINDING_SWIZZLE_BUFFER 0
|
||||
#define BINDING_INPUT_BUFFER 1
|
||||
#define BINDING_ENC_BUFFER 2
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_OUTPUT_IMAGE 0
|
||||
|
||||
#endif
|
||||
|
||||
layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
|
||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||
|
||||
BEGIN_PUSH_CONSTANTS
|
||||
UNIFORM(1) uvec2 block_dims;
|
||||
|
||||
UNIFORM(2) uint bytes_per_block_log2;
|
||||
UNIFORM(3) uint layer_stride;
|
||||
UNIFORM(4) uint block_size;
|
||||
UNIFORM(5) uint x_shift;
|
||||
UNIFORM(6) uint block_height;
|
||||
UNIFORM(7) uint block_height_mask;
|
||||
UNIFORM(2) uint layer_stride;
|
||||
UNIFORM(3) uint block_size;
|
||||
UNIFORM(4) uint x_shift;
|
||||
UNIFORM(5) uint block_height;
|
||||
UNIFORM(6) uint block_height_mask;
|
||||
END_PUSH_CONSTANTS
|
||||
|
||||
struct EncodingData {
|
||||
@@ -55,45 +49,35 @@ struct TexelWeightParams {
|
||||
bool void_extent_hdr;
|
||||
};
|
||||
|
||||
// Swizzle data
|
||||
layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
|
||||
uint swizzle_table[];
|
||||
};
|
||||
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 {
|
||||
uint astc_data[];
|
||||
};
|
||||
|
||||
// ASTC Encodings data
|
||||
layout(binding = BINDING_ENC_BUFFER, std430) readonly buffer EncodingsValues {
|
||||
EncodingData encoding_values[];
|
||||
uvec4 astc_data[];
|
||||
};
|
||||
|
||||
layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly image2DArray dest_image;
|
||||
|
||||
const uint GOB_SIZE_X = 64;
|
||||
const uint GOB_SIZE_Y = 8;
|
||||
const uint GOB_SIZE_Z = 1;
|
||||
const uint GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z;
|
||||
|
||||
const uint GOB_SIZE_X_SHIFT = 6;
|
||||
const uint GOB_SIZE_Y_SHIFT = 3;
|
||||
const uint GOB_SIZE_Z_SHIFT = 0;
|
||||
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
|
||||
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
|
||||
|
||||
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1);
|
||||
|
||||
const int BLOCK_SIZE_IN_BYTES = 16;
|
||||
|
||||
const int BLOCK_INFO_ERROR = 0;
|
||||
const int BLOCK_INFO_VOID_EXTENT_HDR = 1;
|
||||
const int BLOCK_INFO_VOID_EXTENT_LDR = 2;
|
||||
const int BLOCK_INFO_NORMAL = 3;
|
||||
const uint BYTES_PER_BLOCK_LOG2 = 4;
|
||||
|
||||
const int JUST_BITS = 0;
|
||||
const int QUINT = 1;
|
||||
const int TRIT = 2;
|
||||
|
||||
// ASTC Encodings data, sorted in ascending order based on their BitLength value
|
||||
// (see GetBitLength() function)
|
||||
EncodingData encoding_values[22] = EncodingData[](
|
||||
EncodingData(JUST_BITS, 0, 0, 0), EncodingData(JUST_BITS, 1, 0, 0), EncodingData(TRIT, 0, 0, 0),
|
||||
EncodingData(JUST_BITS, 2, 0, 0), EncodingData(QUINT, 0, 0, 0), EncodingData(TRIT, 1, 0, 0),
|
||||
EncodingData(JUST_BITS, 3, 0, 0), EncodingData(QUINT, 1, 0, 0), EncodingData(TRIT, 2, 0, 0),
|
||||
EncodingData(JUST_BITS, 4, 0, 0), EncodingData(QUINT, 2, 0, 0), EncodingData(TRIT, 3, 0, 0),
|
||||
EncodingData(JUST_BITS, 5, 0, 0), EncodingData(QUINT, 3, 0, 0), EncodingData(TRIT, 4, 0, 0),
|
||||
EncodingData(JUST_BITS, 6, 0, 0), EncodingData(QUINT, 4, 0, 0), EncodingData(TRIT, 5, 0, 0),
|
||||
EncodingData(JUST_BITS, 7, 0, 0), EncodingData(QUINT, 5, 0, 0), EncodingData(TRIT, 6, 0, 0),
|
||||
EncodingData(JUST_BITS, 8, 0, 0)
|
||||
);
|
||||
|
||||
// The following constants are expanded variants of the Replicate()
|
||||
// function calls corresponding to the following arguments:
|
||||
// value: index into the generated table
|
||||
@@ -135,44 +119,37 @@ const uint REPLICATE_7_BIT_TO_8_TABLE[128] =
|
||||
// Input ASTC texture globals
|
||||
uint current_index = 0;
|
||||
int bitsread = 0;
|
||||
uint total_bitsread = 0;
|
||||
uint local_buff[16];
|
||||
int total_bitsread = 0;
|
||||
uvec4 local_buff;
|
||||
|
||||
// Color data globals
|
||||
uint color_endpoint_data[16];
|
||||
uvec4 color_endpoint_data;
|
||||
int color_bitsread = 0;
|
||||
uint total_color_bitsread = 0;
|
||||
int color_index = 0;
|
||||
|
||||
// Four values, two endpoints, four maximum paritions
|
||||
uint color_values[32];
|
||||
int colvals_index = 0;
|
||||
|
||||
// Weight data globals
|
||||
uint texel_weight_data[16];
|
||||
uvec4 texel_weight_data;
|
||||
int texel_bitsread = 0;
|
||||
uint total_texel_bitsread = 0;
|
||||
int texel_index = 0;
|
||||
|
||||
bool texel_flag = false;
|
||||
|
||||
// Global "vectors" to be pushed into when decoding
|
||||
EncodingData result_vector[100];
|
||||
EncodingData result_vector[144];
|
||||
int result_index = 0;
|
||||
|
||||
EncodingData texel_vector[100];
|
||||
EncodingData texel_vector[144];
|
||||
int texel_vector_index = 0;
|
||||
|
||||
uint unquantized_texel_weights[2][144];
|
||||
|
||||
uint SwizzleOffset(uvec2 pos) {
|
||||
pos = pos & SWIZZLE_MASK;
|
||||
return swizzle_table[pos.y * 64 + pos.x];
|
||||
}
|
||||
|
||||
uint ReadTexel(uint offset) {
|
||||
// extract the 8-bit value from the 32-bit packed data.
|
||||
return bitfieldExtract(astc_data[offset / 4], int((offset * 8) & 24), 8);
|
||||
uint x = pos.x;
|
||||
uint y = pos.y;
|
||||
return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
|
||||
(y % 2) * 16 + (x % 16);
|
||||
}
|
||||
|
||||
// Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)]
|
||||
@@ -278,14 +255,10 @@ uint Hash52(uint p) {
|
||||
return p;
|
||||
}
|
||||
|
||||
uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bool small_block) {
|
||||
if (partition_count == 1) {
|
||||
return 0;
|
||||
}
|
||||
uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) {
|
||||
if (small_block) {
|
||||
x <<= 1;
|
||||
y <<= 1;
|
||||
z <<= 1;
|
||||
}
|
||||
|
||||
seed += (partition_count - 1) * 1024;
|
||||
@@ -299,10 +272,6 @@ uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bo
|
||||
uint seed6 = uint((rnum >> 20) & 0xF);
|
||||
uint seed7 = uint((rnum >> 24) & 0xF);
|
||||
uint seed8 = uint((rnum >> 28) & 0xF);
|
||||
uint seed9 = uint((rnum >> 18) & 0xF);
|
||||
uint seed10 = uint((rnum >> 22) & 0xF);
|
||||
uint seed11 = uint((rnum >> 26) & 0xF);
|
||||
uint seed12 = uint(((rnum >> 30) | (rnum << 2)) & 0xF);
|
||||
|
||||
seed1 = (seed1 * seed1);
|
||||
seed2 = (seed2 * seed2);
|
||||
@@ -312,12 +281,8 @@ uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bo
|
||||
seed6 = (seed6 * seed6);
|
||||
seed7 = (seed7 * seed7);
|
||||
seed8 = (seed8 * seed8);
|
||||
seed9 = (seed9 * seed9);
|
||||
seed10 = (seed10 * seed10);
|
||||
seed11 = (seed11 * seed11);
|
||||
seed12 = (seed12 * seed12);
|
||||
|
||||
int sh1, sh2, sh3;
|
||||
uint sh1, sh2;
|
||||
if ((seed & 1) > 0) {
|
||||
sh1 = (seed & 2) > 0 ? 4 : 5;
|
||||
sh2 = (partition_count == 3) ? 6 : 5;
|
||||
@@ -325,25 +290,19 @@ uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bo
|
||||
sh1 = (partition_count == 3) ? 6 : 5;
|
||||
sh2 = (seed & 2) > 0 ? 4 : 5;
|
||||
}
|
||||
sh3 = (seed & 0x10) > 0 ? sh1 : sh2;
|
||||
seed1 >>= sh1;
|
||||
seed2 >>= sh2;
|
||||
seed3 >>= sh1;
|
||||
seed4 >>= sh2;
|
||||
seed5 >>= sh1;
|
||||
seed6 >>= sh2;
|
||||
seed7 >>= sh1;
|
||||
seed8 >>= sh2;
|
||||
|
||||
seed1 = (seed1 >> sh1);
|
||||
seed2 = (seed2 >> sh2);
|
||||
seed3 = (seed3 >> sh1);
|
||||
seed4 = (seed4 >> sh2);
|
||||
seed5 = (seed5 >> sh1);
|
||||
seed6 = (seed6 >> sh2);
|
||||
seed7 = (seed7 >> sh1);
|
||||
seed8 = (seed8 >> sh2);
|
||||
seed9 = (seed9 >> sh3);
|
||||
seed10 = (seed10 >> sh3);
|
||||
seed11 = (seed11 >> sh3);
|
||||
seed12 = (seed12 >> sh3);
|
||||
|
||||
uint a = seed1 * x + seed2 * y + seed11 * z + (rnum >> 14);
|
||||
uint b = seed3 * x + seed4 * y + seed12 * z + (rnum >> 10);
|
||||
uint c = seed5 * x + seed6 * y + seed9 * z + (rnum >> 6);
|
||||
uint d = seed7 * x + seed8 * y + seed10 * z + (rnum >> 2);
|
||||
uint a = seed1 * x + seed2 * y + (rnum >> 14);
|
||||
uint b = seed3 * x + seed4 * y + (rnum >> 10);
|
||||
uint c = seed5 * x + seed6 * y + (rnum >> 6);
|
||||
uint d = seed7 * x + seed8 * y + (rnum >> 2);
|
||||
|
||||
a &= 0x3F;
|
||||
b &= 0x3F;
|
||||
@@ -368,58 +327,37 @@ uint SelectPartition(uint seed, uint x, uint y, uint z, uint partition_count, bo
|
||||
}
|
||||
}
|
||||
|
||||
uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) {
|
||||
return SelectPartition(seed, x, y, 0, partition_count, small_block);
|
||||
}
|
||||
|
||||
uint ReadBit() {
|
||||
if (current_index >= local_buff.length()) {
|
||||
uint ExtractBits(uvec4 payload, int offset, int bits) {
|
||||
if (bits <= 0) {
|
||||
return 0;
|
||||
}
|
||||
uint bit = bitfieldExtract(local_buff[current_index], bitsread, 1);
|
||||
++bitsread;
|
||||
++total_bitsread;
|
||||
if (bitsread == 8) {
|
||||
++current_index;
|
||||
bitsread = 0;
|
||||
int last_offset = offset + bits - 1;
|
||||
int shifted_offset = offset >> 5;
|
||||
if ((last_offset >> 5) == shifted_offset) {
|
||||
return bitfieldExtract(payload[shifted_offset], offset & 31, bits);
|
||||
}
|
||||
return bit;
|
||||
int first_bits = 32 - (offset & 31);
|
||||
int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits));
|
||||
int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits));
|
||||
return result_first | (result_second << first_bits);
|
||||
}
|
||||
|
||||
uint StreamBits(uint num_bits) {
|
||||
uint ret = 0;
|
||||
for (uint i = 0; i < num_bits; i++) {
|
||||
ret |= ((ReadBit() & 1) << i);
|
||||
}
|
||||
int int_bits = int(num_bits);
|
||||
uint ret = ExtractBits(local_buff, total_bitsread, int_bits);
|
||||
total_bitsread += int_bits;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint ReadColorBit() {
|
||||
uint bit = 0;
|
||||
if (texel_flag) {
|
||||
bit = bitfieldExtract(texel_weight_data[texel_index], texel_bitsread, 1);
|
||||
++texel_bitsread;
|
||||
++total_texel_bitsread;
|
||||
if (texel_bitsread == 8) {
|
||||
++texel_index;
|
||||
texel_bitsread = 0;
|
||||
}
|
||||
} else {
|
||||
bit = bitfieldExtract(color_endpoint_data[color_index], color_bitsread, 1);
|
||||
++color_bitsread;
|
||||
++total_color_bitsread;
|
||||
if (color_bitsread == 8) {
|
||||
++color_index;
|
||||
color_bitsread = 0;
|
||||
}
|
||||
}
|
||||
return bit;
|
||||
}
|
||||
|
||||
uint StreamColorBits(uint num_bits) {
|
||||
uint ret = 0;
|
||||
for (uint i = 0; i < num_bits; i++) {
|
||||
ret |= ((ReadColorBit() & 1) << i);
|
||||
int int_bits = int(num_bits);
|
||||
if (texel_flag) {
|
||||
ret = ExtractBits(texel_weight_data, texel_bitsread, int_bits);
|
||||
texel_bitsread += int_bits;
|
||||
} else {
|
||||
ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits);
|
||||
color_bitsread += int_bits;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -596,22 +534,16 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
|
||||
for (uint i = 0; i < num_partitions; i++) {
|
||||
num_values += ((modes[i] >> 2) + 1) << 1;
|
||||
}
|
||||
int range = 256;
|
||||
while (--range > 0) {
|
||||
EncodingData val = encoding_values[range];
|
||||
// Find the largest encoding that's within color_data_bits
|
||||
// TODO(ameerj): profile with binary search
|
||||
int range = 0;
|
||||
while (++range < encoding_values.length()) {
|
||||
uint bit_length = GetBitLength(num_values, range);
|
||||
if (bit_length <= color_data_bits) {
|
||||
while (--range > 0) {
|
||||
EncodingData newval = encoding_values[range];
|
||||
if (newval.encoding != val.encoding && newval.num_bits != val.num_bits) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
++range;
|
||||
if (bit_length > color_data_bits) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
DecodeIntegerSequence(range, num_values);
|
||||
DecodeIntegerSequence(range - 1, num_values);
|
||||
uint out_index = 0;
|
||||
for (int itr = 0; itr < result_index; ++itr) {
|
||||
if (out_index >= num_values) {
|
||||
@@ -1028,7 +960,7 @@ int FindLayout(uint mode) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
TexelWeightParams DecodeBlockInfo(uint block_index) {
|
||||
TexelWeightParams DecodeBlockInfo() {
|
||||
TexelWeightParams params = TexelWeightParams(uvec2(0), 0, false, false, false, false);
|
||||
uint mode = StreamBits(11);
|
||||
if ((mode & 0x1ff) == 0x1fc) {
|
||||
@@ -1110,10 +1042,10 @@ TexelWeightParams DecodeBlockInfo(uint block_index) {
|
||||
}
|
||||
weight_index -= 2;
|
||||
if ((mode_layout != 9) && ((mode & 0x200) != 0)) {
|
||||
const int max_weights[6] = int[6](9, 11, 15, 19, 23, 31);
|
||||
const int max_weights[6] = int[6](7, 8, 9, 10, 11, 12);
|
||||
params.max_weight = max_weights[weight_index];
|
||||
} else {
|
||||
const int max_weights[6] = int[6](1, 2, 3, 4, 5, 7);
|
||||
const int max_weights[6] = int[6](1, 2, 3, 4, 5, 6);
|
||||
params.max_weight = max_weights[weight_index];
|
||||
}
|
||||
return params;
|
||||
@@ -1144,8 +1076,8 @@ void FillVoidExtentLDR(ivec3 coord) {
|
||||
}
|
||||
}
|
||||
|
||||
void DecompressBlock(ivec3 coord, uint block_index) {
|
||||
TexelWeightParams params = DecodeBlockInfo(block_index);
|
||||
void DecompressBlock(ivec3 coord) {
|
||||
TexelWeightParams params = DecodeBlockInfo();
|
||||
if (params.error_state) {
|
||||
FillError(coord);
|
||||
return;
|
||||
@@ -1212,7 +1144,7 @@ void DecompressBlock(ivec3 coord, uint block_index) {
|
||||
// Read color data...
|
||||
uint color_data_bits = remaining_bits;
|
||||
while (remaining_bits > 0) {
|
||||
int nb = int(min(remaining_bits, 8U));
|
||||
int nb = int(min(remaining_bits, 32U));
|
||||
uint b = StreamBits(nb);
|
||||
color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb));
|
||||
++ced_pointer;
|
||||
@@ -1254,25 +1186,20 @@ void DecompressBlock(ivec3 coord, uint block_index) {
|
||||
ComputeEndpoints(endpoints[i][0], endpoints[i][1], color_endpoint_mode[i]);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 16; i++) {
|
||||
texel_weight_data[i] = local_buff[i];
|
||||
}
|
||||
for (uint i = 0; i < 8; i++) {
|
||||
#define REVERSE_BYTE(b) ((b * 0x0802U & 0x22110U) | (b * 0x8020U & 0x88440U)) * 0x10101U >> 16
|
||||
uint a = REVERSE_BYTE(texel_weight_data[i]);
|
||||
uint b = REVERSE_BYTE(texel_weight_data[15 - i]);
|
||||
#undef REVERSE_BYTE
|
||||
texel_weight_data[i] = uint(bitfieldExtract(b, 0, 8));
|
||||
texel_weight_data[15 - i] = uint(bitfieldExtract(a, 0, 8));
|
||||
}
|
||||
texel_weight_data = local_buff;
|
||||
texel_weight_data = bitfieldReverse(texel_weight_data).wzyx;
|
||||
uint clear_byte_start =
|
||||
(GetPackedBitSize(params.size, params.dual_plane, params.max_weight) >> 3) + 1;
|
||||
texel_weight_data[clear_byte_start - 1] =
|
||||
texel_weight_data[clear_byte_start - 1] &
|
||||
|
||||
uint byte_insert = ExtractBits(texel_weight_data, int(clear_byte_start - 1) * 8, 8) &
|
||||
uint(
|
||||
((1 << (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) % 8)) - 1));
|
||||
for (uint i = 0; i < 16 - clear_byte_start; i++) {
|
||||
texel_weight_data[clear_byte_start + i] = 0U;
|
||||
uint vec_index = (clear_byte_start - 1) >> 2;
|
||||
texel_weight_data[vec_index] =
|
||||
bitfieldInsert(texel_weight_data[vec_index], byte_insert, int((clear_byte_start - 1) % 4) * 8, 8);
|
||||
for (uint i = clear_byte_start; i < 16; ++i) {
|
||||
uint idx = i >> 2;
|
||||
texel_weight_data[idx] = bitfieldInsert(texel_weight_data[idx], 0, int(i % 4) * 8, 8);
|
||||
}
|
||||
texel_flag = true; // use texel "vector" and bit stream in integer decoding
|
||||
DecodeIntegerSequence(params.max_weight, GetNumWeightValues(params.size, params.dual_plane));
|
||||
@@ -1281,8 +1208,11 @@ void DecompressBlock(ivec3 coord, uint block_index) {
|
||||
|
||||
for (uint j = 0; j < block_dims.y; j++) {
|
||||
for (uint i = 0; i < block_dims.x; i++) {
|
||||
uint local_partition = Select2DPartition(partition_index, i, j, num_partitions,
|
||||
uint local_partition = 0;
|
||||
if (num_partitions > 1) {
|
||||
local_partition = Select2DPartition(partition_index, i, j, num_partitions,
|
||||
(block_dims.y * block_dims.x) < 32);
|
||||
}
|
||||
vec4 p;
|
||||
uvec4 C0 = ReplicateByteTo16(endpoints[local_partition][0]);
|
||||
uvec4 C1 = ReplicateByteTo16(endpoints[local_partition][1]);
|
||||
@@ -1303,7 +1233,7 @@ void DecompressBlock(ivec3 coord, uint block_index) {
|
||||
|
||||
void main() {
|
||||
uvec3 pos = gl_GlobalInvocationID;
|
||||
pos.x <<= bytes_per_block_log2;
|
||||
pos.x <<= BYTES_PER_BLOCK_LOG2;
|
||||
|
||||
// Read as soon as possible due to its latency
|
||||
const uint swizzle = SwizzleOffset(pos.xy);
|
||||
@@ -1321,13 +1251,8 @@ void main() {
|
||||
if (any(greaterThanEqual(coord, imageSize(dest_image)))) {
|
||||
return;
|
||||
}
|
||||
uint block_index =
|
||||
pos.z * gl_WorkGroupSize.x * gl_WorkGroupSize.y + pos.y * gl_WorkGroupSize.x + pos.x;
|
||||
|
||||
current_index = 0;
|
||||
bitsread = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
local_buff[i] = ReadTexel(offset + i);
|
||||
}
|
||||
DecompressBlock(coord, block_index);
|
||||
local_buff = astc_data[offset / 16];
|
||||
DecompressBlock(coord);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/renderer_opengl/gl_state_tracker.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
|
||||
#if defined(_MSC_VER) && defined(NDEBUG)
|
||||
#define LAMBDA_FORCEINLINE [[msvc::forceinline]]
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "video_core/renderer_opengl/maxwell_to_gl.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/shader_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
#include "video_core/renderer_opengl/maxwell_to_gl.h"
|
||||
#include "video_core/renderer_opengl/util_shaders.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache/format_lookup_table.h"
|
||||
#include "video_core/texture_cache/formatter.h"
|
||||
#include "video_core/texture_cache/samples_helper.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
namespace OpenGL {
|
||||
namespace {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/util_shaders.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
|
||||
10
src/video_core/renderer_opengl/gl_texture_cache_base.cpp
Normal file
10
src/video_core/renderer_opengl/gl_texture_cache_base.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/renderer_opengl/gl_texture_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
template class VideoCommon::TextureCache<OpenGL::TextureCacheParams>;
|
||||
}
|
||||
@@ -60,19 +60,14 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_)
|
||||
copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)) {
|
||||
const auto swizzle_table = Tegra::Texture::MakeSwizzleTable();
|
||||
swizzle_table_buffer.Create();
|
||||
astc_buffer.Create();
|
||||
glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0);
|
||||
glNamedBufferStorage(astc_buffer.handle, sizeof(ASTC_ENCODINGS_VALUES), &ASTC_ENCODINGS_VALUES,
|
||||
0);
|
||||
}
|
||||
|
||||
UtilShaders::~UtilShaders() = default;
|
||||
|
||||
void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map,
|
||||
std::span<const VideoCommon::SwizzleParameters> swizzles) {
|
||||
static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0;
|
||||
static constexpr GLuint BINDING_INPUT_BUFFER = 1;
|
||||
static constexpr GLuint BINDING_ENC_BUFFER = 2;
|
||||
static constexpr GLuint BINDING_INPUT_BUFFER = 0;
|
||||
static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
|
||||
|
||||
const Extent2D tile_size{
|
||||
@@ -80,34 +75,32 @@ void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map,
|
||||
.height = VideoCore::Surface::DefaultBlockHeight(image.info.format),
|
||||
};
|
||||
program_manager.BindComputeProgram(astc_decoder_program.handle);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_ENC_BUFFER, astc_buffer.handle);
|
||||
|
||||
glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);
|
||||
glUniform2ui(1, tile_size.width, tile_size.height);
|
||||
|
||||
// Ensure buffer data is valid before dispatching
|
||||
glFlush();
|
||||
for (const SwizzleParameters& swizzle : swizzles) {
|
||||
const size_t input_offset = swizzle.buffer_offset + map.offset;
|
||||
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U);
|
||||
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U);
|
||||
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 8U);
|
||||
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U);
|
||||
|
||||
const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info);
|
||||
ASSERT(params.origin == (std::array<u32, 3>{0, 0, 0}));
|
||||
ASSERT(params.destination == (std::array<s32, 3>{0, 0, 0}));
|
||||
ASSERT(params.bytes_per_block_log2 == 4);
|
||||
|
||||
glUniform1ui(2, params.bytes_per_block_log2);
|
||||
glUniform1ui(3, params.layer_stride);
|
||||
glUniform1ui(4, params.block_size);
|
||||
glUniform1ui(5, params.x_shift);
|
||||
glUniform1ui(6, params.block_height);
|
||||
glUniform1ui(7, params.block_height_mask);
|
||||
glUniform1ui(2, params.layer_stride);
|
||||
glUniform1ui(3, params.block_size);
|
||||
glUniform1ui(4, params.x_shift);
|
||||
glUniform1ui(5, params.block_height);
|
||||
glUniform1ui(6, params.block_height_mask);
|
||||
|
||||
glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0,
|
||||
GL_WRITE_ONLY, GL_RGBA8);
|
||||
// ASTC texture data
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset,
|
||||
image.guest_size_bytes - swizzle.buffer_offset);
|
||||
glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0,
|
||||
GL_WRITE_ONLY, GL_RGBA8);
|
||||
|
||||
glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ private:
|
||||
ProgramManager& program_manager;
|
||||
|
||||
OGLBuffer swizzle_table_buffer;
|
||||
OGLBuffer astc_buffer;
|
||||
|
||||
OGLProgram astc_decoder_program;
|
||||
OGLProgram block_linear_unswizzle_2d_program;
|
||||
|
||||
@@ -30,16 +30,12 @@
|
||||
namespace Vulkan {
|
||||
|
||||
using Tegra::Texture::SWIZZLE_TABLE;
|
||||
using Tegra::Texture::ASTC::ASTC_ENCODINGS_VALUES;
|
||||
using namespace Tegra::Texture::ASTC;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 ASTC_BINDING_INPUT_BUFFER = 0;
|
||||
constexpr u32 ASTC_BINDING_ENC_BUFFER = 1;
|
||||
constexpr u32 ASTC_BINDING_SWIZZLE_BUFFER = 2;
|
||||
constexpr u32 ASTC_BINDING_OUTPUT_IMAGE = 3;
|
||||
constexpr size_t ASTC_NUM_BINDINGS = 4;
|
||||
constexpr u32 ASTC_BINDING_OUTPUT_IMAGE = 1;
|
||||
constexpr size_t ASTC_NUM_BINDINGS = 2;
|
||||
|
||||
template <size_t size>
|
||||
inline constexpr VkPushConstantRange COMPUTE_PUSH_CONSTANT_RANGE{
|
||||
@@ -75,7 +71,7 @@ constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
|
||||
.score = 2,
|
||||
};
|
||||
|
||||
constexpr std::array<VkDescriptorSetLayoutBinding, 4> ASTC_DESCRIPTOR_SET_BINDINGS{{
|
||||
constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{
|
||||
{
|
||||
.binding = ASTC_BINDING_INPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
@@ -83,20 +79,6 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 4> ASTC_DESCRIPTOR_SET_BINDIN
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
{
|
||||
.binding = ASTC_BINDING_ENC_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
{
|
||||
.binding = ASTC_BINDING_SWIZZLE_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
{
|
||||
.binding = ASTC_BINDING_OUTPUT_IMAGE,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||
@@ -108,12 +90,12 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 4> ASTC_DESCRIPTOR_SET_BINDIN
|
||||
|
||||
constexpr DescriptorBankInfo ASTC_BANK_INFO{
|
||||
.uniform_buffers = 0,
|
||||
.storage_buffers = 3,
|
||||
.storage_buffers = 1,
|
||||
.texture_buffers = 0,
|
||||
.image_buffers = 0,
|
||||
.textures = 0,
|
||||
.images = 1,
|
||||
.score = 4,
|
||||
.score = 2,
|
||||
};
|
||||
|
||||
constexpr VkDescriptorUpdateTemplateEntryKHR INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{
|
||||
@@ -135,22 +117,6 @@ constexpr std::array<VkDescriptorUpdateTemplateEntryKHR, ASTC_NUM_BINDINGS>
|
||||
.offset = ASTC_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
{
|
||||
.dstBinding = ASTC_BINDING_ENC_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = ASTC_BINDING_ENC_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
{
|
||||
.dstBinding = ASTC_BINDING_SWIZZLE_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = ASTC_BINDING_SWIZZLE_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
{
|
||||
.dstBinding = ASTC_BINDING_OUTPUT_IMAGE,
|
||||
.dstArrayElement = 0,
|
||||
@@ -163,7 +129,6 @@ constexpr std::array<VkDescriptorUpdateTemplateEntryKHR, ASTC_NUM_BINDINGS>
|
||||
|
||||
struct AstcPushConstants {
|
||||
std::array<u32, 2> blocks_dims;
|
||||
u32 bytes_per_block_log2;
|
||||
u32 layer_stride;
|
||||
u32 block_size;
|
||||
u32 x_shift;
|
||||
@@ -354,46 +319,6 @@ ASTCDecoderPass::ASTCDecoderPass(const Device& device_, VKScheduler& scheduler_,
|
||||
|
||||
ASTCDecoderPass::~ASTCDecoderPass() = default;
|
||||
|
||||
void ASTCDecoderPass::MakeDataBuffer() {
|
||||
constexpr size_t TOTAL_BUFFER_SIZE = sizeof(ASTC_ENCODINGS_VALUES) + sizeof(SWIZZLE_TABLE);
|
||||
data_buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = TOTAL_BUFFER_SIZE,
|
||||
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
});
|
||||
data_buffer_commit = memory_allocator.Commit(data_buffer, MemoryUsage::Upload);
|
||||
|
||||
const auto staging_ref = staging_buffer_pool.Request(TOTAL_BUFFER_SIZE, MemoryUsage::Upload);
|
||||
std::memcpy(staging_ref.mapped_span.data(), &ASTC_ENCODINGS_VALUES,
|
||||
sizeof(ASTC_ENCODINGS_VALUES));
|
||||
// Tack on the swizzle table at the end of the buffer
|
||||
std::memcpy(staging_ref.mapped_span.data() + sizeof(ASTC_ENCODINGS_VALUES), &SWIZZLE_TABLE,
|
||||
sizeof(SWIZZLE_TABLE));
|
||||
|
||||
scheduler.Record([src = staging_ref.buffer, offset = staging_ref.offset, dst = *data_buffer,
|
||||
TOTAL_BUFFER_SIZE](vk::CommandBuffer cmdbuf) {
|
||||
static constexpr VkMemoryBarrier write_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
};
|
||||
const VkBufferCopy copy{
|
||||
.srcOffset = offset,
|
||||
.dstOffset = 0,
|
||||
.size = TOTAL_BUFFER_SIZE,
|
||||
};
|
||||
cmdbuf.CopyBuffer(src, dst, copy);
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, write_barrier);
|
||||
});
|
||||
}
|
||||
|
||||
void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
|
||||
std::span<const VideoCommon::SwizzleParameters> swizzles) {
|
||||
using namespace VideoCommon::Accelerated;
|
||||
@@ -402,9 +327,6 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
|
||||
VideoCore::Surface::DefaultBlockHeight(image.info.format),
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
if (!data_buffer) {
|
||||
MakeDataBuffer();
|
||||
}
|
||||
const VkPipeline vk_pipeline = *pipeline;
|
||||
const VkImageAspectFlags aspect_mask = image.AspectMask();
|
||||
const VkImage vk_image = image.Handle();
|
||||
@@ -436,16 +358,13 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
|
||||
});
|
||||
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
|
||||
const size_t input_offset = swizzle.buffer_offset + map.offset;
|
||||
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U);
|
||||
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U);
|
||||
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 8U);
|
||||
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U);
|
||||
const u32 num_dispatches_z = image.info.resources.layers;
|
||||
|
||||
update_descriptor_queue.Acquire();
|
||||
update_descriptor_queue.AddBuffer(map.buffer, input_offset,
|
||||
image.guest_size_bytes - swizzle.buffer_offset);
|
||||
update_descriptor_queue.AddBuffer(*data_buffer, 0, sizeof(ASTC_ENCODINGS_VALUES));
|
||||
update_descriptor_queue.AddBuffer(*data_buffer, sizeof(ASTC_ENCODINGS_VALUES),
|
||||
sizeof(SWIZZLE_TABLE));
|
||||
update_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
|
||||
const void* const descriptor_data{update_descriptor_queue.UpdateData()};
|
||||
|
||||
@@ -453,11 +372,11 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
|
||||
const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info);
|
||||
ASSERT(params.origin == (std::array<u32, 3>{0, 0, 0}));
|
||||
ASSERT(params.destination == (std::array<s32, 3>{0, 0, 0}));
|
||||
ASSERT(params.bytes_per_block_log2 == 4);
|
||||
scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, block_dims,
|
||||
params, descriptor_data](vk::CommandBuffer cmdbuf) {
|
||||
const AstcPushConstants uniforms{
|
||||
.blocks_dims = block_dims,
|
||||
.bytes_per_block_log2 = params.bytes_per_block_log2,
|
||||
.layer_stride = params.layer_stride,
|
||||
.block_size = params.block_size,
|
||||
.x_shift = params.x_shift,
|
||||
|
||||
@@ -96,15 +96,10 @@ public:
|
||||
std::span<const VideoCommon::SwizzleParameters> swizzles);
|
||||
|
||||
private:
|
||||
void MakeDataBuffer();
|
||||
|
||||
VKScheduler& scheduler;
|
||||
StagingBufferPool& staging_buffer_pool;
|
||||
VKUpdateDescriptorQueue& update_descriptor_queue;
|
||||
MemoryAllocator& memory_allocator;
|
||||
|
||||
vk::Buffer data_buffer;
|
||||
MemoryCommit data_buffer_commit;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/shader_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/texture_cache/formatter.h"
|
||||
#include "video_core/texture_cache/samples_helper.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
|
||||
10
src/video_core/renderer_vulkan/vk_texture_cache_base.cpp
Normal file
10
src/video_core/renderer_vulkan/vk_texture_cache_base.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
template class VideoCommon::TextureCache<Vulkan::TextureCacheParams>;
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "video_core/texture_cache/image_view_info.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
#include "video_core/texture_cache/types.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace VideoCommon {
|
||||
|
||||
namespace {
|
||||
|
||||
using Tegra::Texture::TextureType;
|
||||
|
||||
constexpr u8 RENDER_TARGET_SWIZZLE = std::numeric_limits<u8>::max();
|
||||
|
||||
[[nodiscard]] u8 CastSwizzle(SwizzleSource source) {
|
||||
|
||||
@@ -4,48 +4,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/literals.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"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache/descriptor_table.h"
|
||||
#include "video_core/texture_cache/format_lookup_table.h"
|
||||
#include "video_core/texture_cache/formatter.h"
|
||||
#include "video_core/texture_cache/image_base.h"
|
||||
#include "video_core/texture_cache/image_info.h"
|
||||
#include "video_core/texture_cache/image_view_base.h"
|
||||
#include "video_core/texture_cache/image_view_info.h"
|
||||
#include "video_core/texture_cache/render_targets.h"
|
||||
#include "video_core/texture_cache/samples_helper.h"
|
||||
#include "video_core/texture_cache/slot_vector.h"
|
||||
#include "video_core/texture_cache/types.h"
|
||||
#include "video_core/texture_cache/util.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
@@ -61,352 +24,6 @@ using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||
using VideoCore::Surface::SurfaceType;
|
||||
using namespace Common::Literals;
|
||||
|
||||
template <class P>
|
||||
class TextureCache {
|
||||
/// Address shift for caching images into a hash table
|
||||
static constexpr u64 PAGE_BITS = 20;
|
||||
|
||||
/// Enables debugging features to the texture cache
|
||||
static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION;
|
||||
/// Implement blits as copies between framebuffers
|
||||
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 = 1_GiB;
|
||||
static constexpr u64 DEFAULT_CRITICAL_MEMORY = 2_GiB;
|
||||
|
||||
using Runtime = typename P::Runtime;
|
||||
using Image = typename P::Image;
|
||||
using ImageAlloc = typename P::ImageAlloc;
|
||||
using ImageView = typename P::ImageView;
|
||||
using Sampler = typename P::Sampler;
|
||||
using Framebuffer = typename P::Framebuffer;
|
||||
|
||||
struct BlitImages {
|
||||
ImageId dst_id;
|
||||
ImageId src_id;
|
||||
PixelFormat dst_format;
|
||||
PixelFormat src_format;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IdentityHash {
|
||||
[[nodiscard]] size_t operator()(T value) const noexcept {
|
||||
return static_cast<size_t>(value);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&, Tegra::Engines::Maxwell3D&,
|
||||
Tegra::Engines::KeplerCompute&, Tegra::MemoryManager&);
|
||||
|
||||
/// Notify the cache that a new frame has been queued
|
||||
void TickFrame();
|
||||
|
||||
/// Return a constant reference to the given image view id
|
||||
[[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept;
|
||||
|
||||
/// Return a reference to the given image view id
|
||||
[[nodiscard]] ImageView& GetImageView(ImageViewId id) noexcept;
|
||||
|
||||
/// Mark an image as modified from the GPU
|
||||
void MarkModification(ImageId id) noexcept;
|
||||
|
||||
/// Fill image_view_ids with the graphics images in indices
|
||||
void FillGraphicsImageViews(std::span<const u32> indices,
|
||||
std::span<ImageViewId> image_view_ids);
|
||||
|
||||
/// Fill image_view_ids with the compute images in indices
|
||||
void FillComputeImageViews(std::span<const u32> indices, std::span<ImageViewId> image_view_ids);
|
||||
|
||||
/// Get the sampler from the graphics descriptor table in the specified index
|
||||
Sampler* GetGraphicsSampler(u32 index);
|
||||
|
||||
/// Get the sampler from the compute descriptor table in the specified index
|
||||
Sampler* GetComputeSampler(u32 index);
|
||||
|
||||
/// Refresh the state for graphics image view and sampler descriptors
|
||||
void SynchronizeGraphicsDescriptors();
|
||||
|
||||
/// Refresh the state for compute image view and sampler descriptors
|
||||
void SynchronizeComputeDescriptors();
|
||||
|
||||
/// Update bound render targets and upload memory if necessary
|
||||
/// @param is_clear True when the render targets are being used for clears
|
||||
void UpdateRenderTargets(bool is_clear);
|
||||
|
||||
/// Find a framebuffer with the currently bound render targets
|
||||
/// UpdateRenderTargets should be called before this
|
||||
Framebuffer* GetFramebuffer();
|
||||
|
||||
/// Mark images in a range as modified from the CPU
|
||||
void WriteMemory(VAddr cpu_addr, size_t size);
|
||||
|
||||
/// Download contents of host images to guest memory in a region
|
||||
void DownloadMemory(VAddr cpu_addr, size_t size);
|
||||
|
||||
/// Remove images in a region
|
||||
void UnmapMemory(VAddr cpu_addr, size_t size);
|
||||
|
||||
/// Remove images in a region
|
||||
void UnmapGPUMemory(GPUVAddr gpu_addr, size_t size);
|
||||
|
||||
/// Blit an image with the given parameters
|
||||
void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
const Tegra::Engines::Fermi2D::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Config& copy);
|
||||
|
||||
/// Invalidate the contents of the color buffer index
|
||||
/// These contents become unspecified, the cache can assume aggressive optimizations.
|
||||
void InvalidateColorBuffer(size_t index);
|
||||
|
||||
/// Invalidate the contents of the depth buffer
|
||||
/// These contents become unspecified, the cache can assume aggressive optimizations.
|
||||
void InvalidateDepthBuffer();
|
||||
|
||||
/// Try to find a cached image view in the given CPU address
|
||||
[[nodiscard]] ImageView* TryFindFramebufferImageView(VAddr cpu_addr);
|
||||
|
||||
/// Return true when there are uncommitted images to be downloaded
|
||||
[[nodiscard]] bool HasUncommittedFlushes() const noexcept;
|
||||
|
||||
/// Return true when the caller should wait for async downloads
|
||||
[[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept;
|
||||
|
||||
/// Commit asynchronous downloads
|
||||
void CommitAsyncFlushes();
|
||||
|
||||
/// Pop asynchronous downloads
|
||||
void PopAsyncFlushes();
|
||||
|
||||
/// Return true when a CPU region is modified from the GPU
|
||||
[[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
|
||||
|
||||
std::mutex mutex;
|
||||
|
||||
private:
|
||||
/// Iterate over all page indices in a range
|
||||
template <typename Func>
|
||||
static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) {
|
||||
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
|
||||
const u64 page_end = (addr + size - 1) >> PAGE_BITS;
|
||||
for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
|
||||
if constexpr (RETURNS_BOOL) {
|
||||
if (func(page)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
func(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) {
|
||||
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
|
||||
const u64 page_end = (addr + size - 1) >> PAGE_BITS;
|
||||
for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
|
||||
if constexpr (RETURNS_BOOL) {
|
||||
if (func(page)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
func(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the Garbage Collector.
|
||||
void RunGarbageCollector();
|
||||
|
||||
/// Fills image_view_ids in the image views in indices
|
||||
void FillImageViews(DescriptorTable<TICEntry>& table,
|
||||
std::span<ImageViewId> cached_image_view_ids, std::span<const u32> indices,
|
||||
std::span<ImageViewId> image_view_ids);
|
||||
|
||||
/// Find or create an image view in the guest descriptor table
|
||||
ImageViewId VisitImageView(DescriptorTable<TICEntry>& table,
|
||||
std::span<ImageViewId> cached_image_view_ids, u32 index);
|
||||
|
||||
/// Find or create a framebuffer with the given render target parameters
|
||||
FramebufferId GetFramebufferId(const RenderTargets& key);
|
||||
|
||||
/// Refresh the contents (pixel data) of an image
|
||||
void RefreshContents(Image& image, ImageId image_id);
|
||||
|
||||
/// Upload data from guest to an image
|
||||
template <typename StagingBuffer>
|
||||
void UploadImageContents(Image& image, StagingBuffer& staging_buffer);
|
||||
|
||||
/// Find or create an image view from a guest descriptor
|
||||
[[nodiscard]] ImageViewId FindImageView(const TICEntry& config);
|
||||
|
||||
/// Create a new image view from a guest descriptor
|
||||
[[nodiscard]] ImageViewId CreateImageView(const TICEntry& config);
|
||||
|
||||
/// Find or create an image from the given parameters
|
||||
[[nodiscard]] ImageId FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
RelaxedOptions options = RelaxedOptions{});
|
||||
|
||||
/// Find an image from the given parameters
|
||||
[[nodiscard]] ImageId FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
RelaxedOptions options);
|
||||
|
||||
/// Create an image from the given parameters
|
||||
[[nodiscard]] ImageId InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
RelaxedOptions options);
|
||||
|
||||
/// Create a new image and join perfectly matching existing images
|
||||
/// Remove joined images from the cache
|
||||
[[nodiscard]] ImageId JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr);
|
||||
|
||||
/// Return a blit image pair from the given guest blit parameters
|
||||
[[nodiscard]] BlitImages GetBlitImages(const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
const Tegra::Engines::Fermi2D::Surface& src);
|
||||
|
||||
/// Find or create a sampler from a guest descriptor sampler
|
||||
[[nodiscard]] SamplerId FindSampler(const TSCEntry& config);
|
||||
|
||||
/// Find or create an image view for the given color buffer index
|
||||
[[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear);
|
||||
|
||||
/// Find or create an image view for the depth buffer
|
||||
[[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear);
|
||||
|
||||
/// Find or create a view for a render target with the given image parameters
|
||||
[[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
bool is_clear);
|
||||
|
||||
/// Iterates over all the images in a region calling func
|
||||
template <typename Func>
|
||||
void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func);
|
||||
|
||||
template <typename Func>
|
||||
void ForEachImageInRegionGPU(GPUVAddr gpu_addr, size_t size, Func&& func);
|
||||
|
||||
template <typename Func>
|
||||
void ForEachSparseImageInRegion(GPUVAddr gpu_addr, size_t size, Func&& func);
|
||||
|
||||
/// Iterates over all the images in a region calling func
|
||||
template <typename Func>
|
||||
void ForEachSparseSegment(ImageBase& image, Func&& func);
|
||||
|
||||
/// Find or create an image view in the given image with the passed parameters
|
||||
[[nodiscard]] ImageViewId FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info);
|
||||
|
||||
/// Register image in the page table
|
||||
void RegisterImage(ImageId image);
|
||||
|
||||
/// Unregister image from the page table
|
||||
void UnregisterImage(ImageId image);
|
||||
|
||||
/// Track CPU reads and writes for image
|
||||
void TrackImage(ImageBase& image, ImageId image_id);
|
||||
|
||||
/// Stop tracking CPU reads and writes for image
|
||||
void UntrackImage(ImageBase& image, ImageId image_id);
|
||||
|
||||
/// Delete image from the cache
|
||||
void DeleteImage(ImageId image);
|
||||
|
||||
/// Remove image views references from the cache
|
||||
void RemoveImageViewReferences(std::span<const ImageViewId> removed_views);
|
||||
|
||||
/// Remove framebuffers using the given image views from the cache
|
||||
void RemoveFramebuffers(std::span<const ImageViewId> removed_views);
|
||||
|
||||
/// Mark an image as modified from the GPU
|
||||
void MarkModification(ImageBase& image) noexcept;
|
||||
|
||||
/// Synchronize image aliases, copying data if needed
|
||||
void SynchronizeAliases(ImageId image_id);
|
||||
|
||||
/// Prepare an image to be used
|
||||
void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
|
||||
|
||||
/// Prepare an image view to be used
|
||||
void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate);
|
||||
|
||||
/// Execute copies from one image to the other, even if they are incompatible
|
||||
void CopyImage(ImageId dst_id, ImageId src_id, std::span<const ImageCopy> copies);
|
||||
|
||||
/// Bind an image view as render target, downloading resources preemtively if needed
|
||||
void BindRenderTarget(ImageViewId* old_id, ImageViewId new_id);
|
||||
|
||||
/// Create a render target from a given image and image view parameters
|
||||
[[nodiscard]] std::pair<FramebufferId, ImageViewId> RenderTargetFromImage(
|
||||
ImageId, const ImageViewInfo& view_info);
|
||||
|
||||
/// Returns true if the current clear parameters clear the whole image of a given image view
|
||||
[[nodiscard]] bool IsFullClear(ImageViewId id);
|
||||
|
||||
Runtime& runtime;
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||
Tegra::Engines::KeplerCompute& kepler_compute;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
|
||||
DescriptorTable<TICEntry> graphics_image_table{gpu_memory};
|
||||
DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory};
|
||||
std::vector<SamplerId> graphics_sampler_ids;
|
||||
std::vector<ImageViewId> graphics_image_view_ids;
|
||||
|
||||
DescriptorTable<TICEntry> compute_image_table{gpu_memory};
|
||||
DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory};
|
||||
std::vector<SamplerId> compute_sampler_ids;
|
||||
std::vector<ImageViewId> compute_image_view_ids;
|
||||
|
||||
RenderTargets render_targets;
|
||||
|
||||
std::unordered_map<TICEntry, ImageViewId> image_views;
|
||||
std::unordered_map<TSCEntry, SamplerId> samplers;
|
||||
std::unordered_map<RenderTargets, FramebufferId> framebuffers;
|
||||
|
||||
std::unordered_map<u64, std::vector<ImageMapId>, IdentityHash<u64>> page_table;
|
||||
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> gpu_page_table;
|
||||
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> sparse_page_table;
|
||||
|
||||
std::unordered_map<ImageId, std::vector<ImageViewId>> sparse_views;
|
||||
|
||||
VAddr virtual_invalid_space{};
|
||||
|
||||
bool has_deleted_images = false;
|
||||
u64 total_used_memory = 0;
|
||||
u64 minimum_memory;
|
||||
u64 expected_memory;
|
||||
u64 critical_memory;
|
||||
|
||||
SlotVector<Image> slot_images;
|
||||
SlotVector<ImageMapView> slot_map_views;
|
||||
SlotVector<ImageView> slot_image_views;
|
||||
SlotVector<ImageAlloc> slot_image_allocs;
|
||||
SlotVector<Sampler> slot_samplers;
|
||||
SlotVector<Framebuffer> slot_framebuffers;
|
||||
|
||||
// TODO: This data structure is not optimal and it should be reworked
|
||||
std::vector<ImageId> uncommitted_downloads;
|
||||
std::queue<std::vector<ImageId>> committed_downloads;
|
||||
|
||||
static constexpr size_t TICKS_TO_DESTROY = 6;
|
||||
DelayedDestructionRing<Image, TICKS_TO_DESTROY> sentenced_images;
|
||||
DelayedDestructionRing<ImageView, TICKS_TO_DESTROY> sentenced_image_view;
|
||||
DelayedDestructionRing<Framebuffer, TICKS_TO_DESTROY> sentenced_framebuffers;
|
||||
|
||||
std::unordered_map<GPUVAddr, ImageAllocId> image_allocs_table;
|
||||
|
||||
u64 modification_tick = 0;
|
||||
u64 frame_tick = 0;
|
||||
typename SlotVector<Image>::Iterator deletion_iterator;
|
||||
};
|
||||
|
||||
template <class P>
|
||||
TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& rasterizer_,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d_,
|
||||
@@ -820,40 +437,6 @@ void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::InvalidateColorBuffer(size_t index) {
|
||||
ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index];
|
||||
color_buffer_id = FindColorBuffer(index, false);
|
||||
if (!color_buffer_id) {
|
||||
LOG_ERROR(HW_GPU, "Invalidating invalid color buffer in index={}", index);
|
||||
return;
|
||||
}
|
||||
// When invalidating a color buffer, the old contents are no longer relevant
|
||||
ImageView& color_buffer = slot_image_views[color_buffer_id];
|
||||
Image& image = slot_images[color_buffer.image_id];
|
||||
image.flags &= ~ImageFlagBits::CpuModified;
|
||||
image.flags &= ~ImageFlagBits::GpuModified;
|
||||
|
||||
runtime.InvalidateColorBuffer(color_buffer, index);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void TextureCache<P>::InvalidateDepthBuffer() {
|
||||
ImageViewId& depth_buffer_id = render_targets.depth_buffer_id;
|
||||
depth_buffer_id = FindDepthBuffer(false);
|
||||
if (!depth_buffer_id) {
|
||||
LOG_ERROR(HW_GPU, "Invalidating invalid depth buffer");
|
||||
return;
|
||||
}
|
||||
// When invalidating the depth buffer, the old contents are no longer relevant
|
||||
ImageBase& image = slot_images[slot_image_views[depth_buffer_id].image_id];
|
||||
image.flags &= ~ImageFlagBits::CpuModified;
|
||||
image.flags &= ~ImageFlagBits::GpuModified;
|
||||
|
||||
ImageView& depth_buffer = slot_image_views[depth_buffer_id];
|
||||
runtime.InvalidateDepthBuffer(depth_buffer);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_addr) {
|
||||
// TODO: Properly implement this
|
||||
|
||||
385
src/video_core/texture_cache/texture_cache_base.h
Normal file
385
src/video_core/texture_cache/texture_cache_base.h
Normal file
@@ -0,0 +1,385 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/literals.h"
|
||||
#include "video_core/compatible_formats.h"
|
||||
#include "video_core/delayed_destruction_ring.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache/descriptor_table.h"
|
||||
#include "video_core/texture_cache/image_base.h"
|
||||
#include "video_core/texture_cache/image_info.h"
|
||||
#include "video_core/texture_cache/image_view_info.h"
|
||||
#include "video_core/texture_cache/render_targets.h"
|
||||
#include "video_core/texture_cache/slot_vector.h"
|
||||
#include "video_core/texture_cache/types.h"
|
||||
#include "video_core/texture_cache/util.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
using Tegra::Texture::SwizzleSource;
|
||||
using Tegra::Texture::TICEntry;
|
||||
using Tegra::Texture::TSCEntry;
|
||||
using VideoCore::Surface::GetFormatType;
|
||||
using VideoCore::Surface::IsCopyCompatible;
|
||||
using VideoCore::Surface::PixelFormat;
|
||||
using VideoCore::Surface::PixelFormatFromDepthFormat;
|
||||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||
using namespace Common::Literals;
|
||||
|
||||
template <class P>
|
||||
class TextureCache {
|
||||
/// Address shift for caching images into a hash table
|
||||
static constexpr u64 PAGE_BITS = 20;
|
||||
|
||||
/// Enables debugging features to the texture cache
|
||||
static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION;
|
||||
/// Implement blits as copies between framebuffers
|
||||
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 = 1_GiB;
|
||||
static constexpr u64 DEFAULT_CRITICAL_MEMORY = 2_GiB;
|
||||
|
||||
using Runtime = typename P::Runtime;
|
||||
using Image = typename P::Image;
|
||||
using ImageAlloc = typename P::ImageAlloc;
|
||||
using ImageView = typename P::ImageView;
|
||||
using Sampler = typename P::Sampler;
|
||||
using Framebuffer = typename P::Framebuffer;
|
||||
|
||||
struct BlitImages {
|
||||
ImageId dst_id;
|
||||
ImageId src_id;
|
||||
PixelFormat dst_format;
|
||||
PixelFormat src_format;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IdentityHash {
|
||||
[[nodiscard]] size_t operator()(T value) const noexcept {
|
||||
return static_cast<size_t>(value);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&, Tegra::Engines::Maxwell3D&,
|
||||
Tegra::Engines::KeplerCompute&, Tegra::MemoryManager&);
|
||||
|
||||
/// Notify the cache that a new frame has been queued
|
||||
void TickFrame();
|
||||
|
||||
/// Return a constant reference to the given image view id
|
||||
[[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept;
|
||||
|
||||
/// Return a reference to the given image view id
|
||||
[[nodiscard]] ImageView& GetImageView(ImageViewId id) noexcept;
|
||||
|
||||
/// Mark an image as modified from the GPU
|
||||
void MarkModification(ImageId id) noexcept;
|
||||
|
||||
/// Fill image_view_ids with the graphics images in indices
|
||||
void FillGraphicsImageViews(std::span<const u32> indices,
|
||||
std::span<ImageViewId> image_view_ids);
|
||||
|
||||
/// Fill image_view_ids with the compute images in indices
|
||||
void FillComputeImageViews(std::span<const u32> indices, std::span<ImageViewId> image_view_ids);
|
||||
|
||||
/// Get the sampler from the graphics descriptor table in the specified index
|
||||
Sampler* GetGraphicsSampler(u32 index);
|
||||
|
||||
/// Get the sampler from the compute descriptor table in the specified index
|
||||
Sampler* GetComputeSampler(u32 index);
|
||||
|
||||
/// Refresh the state for graphics image view and sampler descriptors
|
||||
void SynchronizeGraphicsDescriptors();
|
||||
|
||||
/// Refresh the state for compute image view and sampler descriptors
|
||||
void SynchronizeComputeDescriptors();
|
||||
|
||||
/// Update bound render targets and upload memory if necessary
|
||||
/// @param is_clear True when the render targets are being used for clears
|
||||
void UpdateRenderTargets(bool is_clear);
|
||||
|
||||
/// Find a framebuffer with the currently bound render targets
|
||||
/// UpdateRenderTargets should be called before this
|
||||
Framebuffer* GetFramebuffer();
|
||||
|
||||
/// Mark images in a range as modified from the CPU
|
||||
void WriteMemory(VAddr cpu_addr, size_t size);
|
||||
|
||||
/// Download contents of host images to guest memory in a region
|
||||
void DownloadMemory(VAddr cpu_addr, size_t size);
|
||||
|
||||
/// Remove images in a region
|
||||
void UnmapMemory(VAddr cpu_addr, size_t size);
|
||||
|
||||
/// Remove images in a region
|
||||
void UnmapGPUMemory(GPUVAddr gpu_addr, size_t size);
|
||||
|
||||
/// Blit an image with the given parameters
|
||||
void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
const Tegra::Engines::Fermi2D::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Config& copy);
|
||||
|
||||
/// Try to find a cached image view in the given CPU address
|
||||
[[nodiscard]] ImageView* TryFindFramebufferImageView(VAddr cpu_addr);
|
||||
|
||||
/// Return true when there are uncommitted images to be downloaded
|
||||
[[nodiscard]] bool HasUncommittedFlushes() const noexcept;
|
||||
|
||||
/// Return true when the caller should wait for async downloads
|
||||
[[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept;
|
||||
|
||||
/// Commit asynchronous downloads
|
||||
void CommitAsyncFlushes();
|
||||
|
||||
/// Pop asynchronous downloads
|
||||
void PopAsyncFlushes();
|
||||
|
||||
/// Return true when a CPU region is modified from the GPU
|
||||
[[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
|
||||
|
||||
std::mutex mutex;
|
||||
|
||||
private:
|
||||
/// Iterate over all page indices in a range
|
||||
template <typename Func>
|
||||
static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) {
|
||||
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
|
||||
const u64 page_end = (addr + size - 1) >> PAGE_BITS;
|
||||
for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
|
||||
if constexpr (RETURNS_BOOL) {
|
||||
if (func(page)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
func(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) {
|
||||
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
|
||||
const u64 page_end = (addr + size - 1) >> PAGE_BITS;
|
||||
for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
|
||||
if constexpr (RETURNS_BOOL) {
|
||||
if (func(page)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
func(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the Garbage Collector.
|
||||
void RunGarbageCollector();
|
||||
|
||||
/// Fills image_view_ids in the image views in indices
|
||||
void FillImageViews(DescriptorTable<TICEntry>& table,
|
||||
std::span<ImageViewId> cached_image_view_ids, std::span<const u32> indices,
|
||||
std::span<ImageViewId> image_view_ids);
|
||||
|
||||
/// Find or create an image view in the guest descriptor table
|
||||
ImageViewId VisitImageView(DescriptorTable<TICEntry>& table,
|
||||
std::span<ImageViewId> cached_image_view_ids, u32 index);
|
||||
|
||||
/// Find or create a framebuffer with the given render target parameters
|
||||
FramebufferId GetFramebufferId(const RenderTargets& key);
|
||||
|
||||
/// Refresh the contents (pixel data) of an image
|
||||
void RefreshContents(Image& image, ImageId image_id);
|
||||
|
||||
/// Upload data from guest to an image
|
||||
template <typename StagingBuffer>
|
||||
void UploadImageContents(Image& image, StagingBuffer& staging_buffer);
|
||||
|
||||
/// Find or create an image view from a guest descriptor
|
||||
[[nodiscard]] ImageViewId FindImageView(const TICEntry& config);
|
||||
|
||||
/// Create a new image view from a guest descriptor
|
||||
[[nodiscard]] ImageViewId CreateImageView(const TICEntry& config);
|
||||
|
||||
/// Find or create an image from the given parameters
|
||||
[[nodiscard]] ImageId FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
RelaxedOptions options = RelaxedOptions{});
|
||||
|
||||
/// Find an image from the given parameters
|
||||
[[nodiscard]] ImageId FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
RelaxedOptions options);
|
||||
|
||||
/// Create an image from the given parameters
|
||||
[[nodiscard]] ImageId InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
RelaxedOptions options);
|
||||
|
||||
/// Create a new image and join perfectly matching existing images
|
||||
/// Remove joined images from the cache
|
||||
[[nodiscard]] ImageId JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr);
|
||||
|
||||
/// Return a blit image pair from the given guest blit parameters
|
||||
[[nodiscard]] BlitImages GetBlitImages(const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
const Tegra::Engines::Fermi2D::Surface& src);
|
||||
|
||||
/// Find or create a sampler from a guest descriptor sampler
|
||||
[[nodiscard]] SamplerId FindSampler(const TSCEntry& config);
|
||||
|
||||
/// Find or create an image view for the given color buffer index
|
||||
[[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear);
|
||||
|
||||
/// Find or create an image view for the depth buffer
|
||||
[[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear);
|
||||
|
||||
/// Find or create a view for a render target with the given image parameters
|
||||
[[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr,
|
||||
bool is_clear);
|
||||
|
||||
/// Iterates over all the images in a region calling func
|
||||
template <typename Func>
|
||||
void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func);
|
||||
|
||||
template <typename Func>
|
||||
void ForEachImageInRegionGPU(GPUVAddr gpu_addr, size_t size, Func&& func);
|
||||
|
||||
template <typename Func>
|
||||
void ForEachSparseImageInRegion(GPUVAddr gpu_addr, size_t size, Func&& func);
|
||||
|
||||
/// Iterates over all the images in a region calling func
|
||||
template <typename Func>
|
||||
void ForEachSparseSegment(ImageBase& image, Func&& func);
|
||||
|
||||
/// Find or create an image view in the given image with the passed parameters
|
||||
[[nodiscard]] ImageViewId FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info);
|
||||
|
||||
/// Register image in the page table
|
||||
void RegisterImage(ImageId image);
|
||||
|
||||
/// Unregister image from the page table
|
||||
void UnregisterImage(ImageId image);
|
||||
|
||||
/// Track CPU reads and writes for image
|
||||
void TrackImage(ImageBase& image, ImageId image_id);
|
||||
|
||||
/// Stop tracking CPU reads and writes for image
|
||||
void UntrackImage(ImageBase& image, ImageId image_id);
|
||||
|
||||
/// Delete image from the cache
|
||||
void DeleteImage(ImageId image);
|
||||
|
||||
/// Remove image views references from the cache
|
||||
void RemoveImageViewReferences(std::span<const ImageViewId> removed_views);
|
||||
|
||||
/// Remove framebuffers using the given image views from the cache
|
||||
void RemoveFramebuffers(std::span<const ImageViewId> removed_views);
|
||||
|
||||
/// Mark an image as modified from the GPU
|
||||
void MarkModification(ImageBase& image) noexcept;
|
||||
|
||||
/// Synchronize image aliases, copying data if needed
|
||||
void SynchronizeAliases(ImageId image_id);
|
||||
|
||||
/// Prepare an image to be used
|
||||
void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
|
||||
|
||||
/// Prepare an image view to be used
|
||||
void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate);
|
||||
|
||||
/// Execute copies from one image to the other, even if they are incompatible
|
||||
void CopyImage(ImageId dst_id, ImageId src_id, std::span<const ImageCopy> copies);
|
||||
|
||||
/// Bind an image view as render target, downloading resources preemtively if needed
|
||||
void BindRenderTarget(ImageViewId* old_id, ImageViewId new_id);
|
||||
|
||||
/// Create a render target from a given image and image view parameters
|
||||
[[nodiscard]] std::pair<FramebufferId, ImageViewId> RenderTargetFromImage(
|
||||
ImageId, const ImageViewInfo& view_info);
|
||||
|
||||
/// Returns true if the current clear parameters clear the whole image of a given image view
|
||||
[[nodiscard]] bool IsFullClear(ImageViewId id);
|
||||
|
||||
Runtime& runtime;
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||
Tegra::Engines::KeplerCompute& kepler_compute;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
|
||||
DescriptorTable<TICEntry> graphics_image_table{gpu_memory};
|
||||
DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory};
|
||||
std::vector<SamplerId> graphics_sampler_ids;
|
||||
std::vector<ImageViewId> graphics_image_view_ids;
|
||||
|
||||
DescriptorTable<TICEntry> compute_image_table{gpu_memory};
|
||||
DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory};
|
||||
std::vector<SamplerId> compute_sampler_ids;
|
||||
std::vector<ImageViewId> compute_image_view_ids;
|
||||
|
||||
RenderTargets render_targets;
|
||||
|
||||
std::unordered_map<TICEntry, ImageViewId> image_views;
|
||||
std::unordered_map<TSCEntry, SamplerId> samplers;
|
||||
std::unordered_map<RenderTargets, FramebufferId> framebuffers;
|
||||
|
||||
std::unordered_map<u64, std::vector<ImageMapId>, IdentityHash<u64>> page_table;
|
||||
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> gpu_page_table;
|
||||
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> sparse_page_table;
|
||||
|
||||
std::unordered_map<ImageId, std::vector<ImageViewId>> sparse_views;
|
||||
|
||||
VAddr virtual_invalid_space{};
|
||||
|
||||
bool has_deleted_images = false;
|
||||
u64 total_used_memory = 0;
|
||||
u64 minimum_memory;
|
||||
u64 expected_memory;
|
||||
u64 critical_memory;
|
||||
|
||||
SlotVector<Image> slot_images;
|
||||
SlotVector<ImageMapView> slot_map_views;
|
||||
SlotVector<ImageView> slot_image_views;
|
||||
SlotVector<ImageAlloc> slot_image_allocs;
|
||||
SlotVector<Sampler> slot_samplers;
|
||||
SlotVector<Framebuffer> slot_framebuffers;
|
||||
|
||||
// TODO: This data structure is not optimal and it should be reworked
|
||||
std::vector<ImageId> uncommitted_downloads;
|
||||
std::queue<std::vector<ImageId>> committed_downloads;
|
||||
|
||||
static constexpr size_t TICKS_TO_DESTROY = 6;
|
||||
DelayedDestructionRing<Image, TICKS_TO_DESTROY> sentenced_images;
|
||||
DelayedDestructionRing<ImageView, TICKS_TO_DESTROY> sentenced_image_view;
|
||||
DelayedDestructionRing<Framebuffer, TICKS_TO_DESTROY> sentenced_framebuffers;
|
||||
|
||||
std::unordered_map<GPUVAddr, ImageAllocId> image_allocs_table;
|
||||
|
||||
u64 modification_tick = 0;
|
||||
u64 frame_tick = 0;
|
||||
typename SlotVector<Image>::Iterator deletion_iterator;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -151,6 +151,76 @@ private:
|
||||
const IntType& m_Bits;
|
||||
};
|
||||
|
||||
enum class IntegerEncoding { JustBits, Quint, Trit };
|
||||
|
||||
struct IntegerEncodedValue {
|
||||
constexpr IntegerEncodedValue() = default;
|
||||
|
||||
constexpr IntegerEncodedValue(IntegerEncoding encoding_, u32 num_bits_)
|
||||
: encoding{encoding_}, num_bits{num_bits_} {}
|
||||
|
||||
constexpr bool MatchesEncoding(const IntegerEncodedValue& other) const {
|
||||
return encoding == other.encoding && num_bits == other.num_bits;
|
||||
}
|
||||
|
||||
// Returns the number of bits required to encode num_vals values.
|
||||
u32 GetBitLength(u32 num_vals) const {
|
||||
u32 total_bits = num_bits * num_vals;
|
||||
if (encoding == IntegerEncoding::Trit) {
|
||||
total_bits += (num_vals * 8 + 4) / 5;
|
||||
} else if (encoding == IntegerEncoding::Quint) {
|
||||
total_bits += (num_vals * 7 + 2) / 3;
|
||||
}
|
||||
return total_bits;
|
||||
}
|
||||
|
||||
IntegerEncoding encoding{};
|
||||
u32 num_bits = 0;
|
||||
u32 bit_value = 0;
|
||||
union {
|
||||
u32 quint_value = 0;
|
||||
u32 trit_value;
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a new instance of this struct that corresponds to the
|
||||
// can take no more than mav_value values
|
||||
static constexpr IntegerEncodedValue CreateEncoding(u32 mav_value) {
|
||||
while (mav_value > 0) {
|
||||
u32 check = mav_value + 1;
|
||||
|
||||
// Is mav_value a power of two?
|
||||
if (!(check & (check - 1))) {
|
||||
return IntegerEncodedValue(IntegerEncoding::JustBits, std::popcount(mav_value));
|
||||
}
|
||||
|
||||
// Is mav_value of the type 3*2^n - 1?
|
||||
if ((check % 3 == 0) && !((check / 3) & ((check / 3) - 1))) {
|
||||
return IntegerEncodedValue(IntegerEncoding::Trit, std::popcount(check / 3 - 1));
|
||||
}
|
||||
|
||||
// Is mav_value of the type 5*2^n - 1?
|
||||
if ((check % 5 == 0) && !((check / 5) & ((check / 5) - 1))) {
|
||||
return IntegerEncodedValue(IntegerEncoding::Quint, std::popcount(check / 5 - 1));
|
||||
}
|
||||
|
||||
// Apparently it can't be represented with a bounded integer sequence...
|
||||
// just iterate.
|
||||
mav_value--;
|
||||
}
|
||||
return IntegerEncodedValue(IntegerEncoding::JustBits, 0);
|
||||
}
|
||||
|
||||
static constexpr std::array<IntegerEncodedValue, 256> MakeEncodedValues() {
|
||||
std::array<IntegerEncodedValue, 256> encodings{};
|
||||
for (std::size_t i = 0; i < encodings.size(); ++i) {
|
||||
encodings[i] = CreateEncoding(static_cast<u32>(i));
|
||||
}
|
||||
return encodings;
|
||||
}
|
||||
|
||||
static constexpr std::array<IntegerEncodedValue, 256> ASTC_ENCODINGS_VALUES = MakeEncodedValues();
|
||||
|
||||
namespace Tegra::Texture::ASTC {
|
||||
using IntegerEncodedVector = boost::container::static_vector<
|
||||
IntegerEncodedValue, 256,
|
||||
@@ -521,35 +591,41 @@ static TexelWeightParams DecodeBlockInfo(InputBitStream& strm) {
|
||||
return params;
|
||||
}
|
||||
|
||||
static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 blockWidth,
|
||||
u32 blockHeight) {
|
||||
// Don't actually care about the void extent, just read the bits...
|
||||
for (s32 i = 0; i < 4; ++i) {
|
||||
strm.ReadBits<13>();
|
||||
// Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)]
|
||||
// is the same as [(num_bits - 1):0] and repeats all the way down.
|
||||
template <typename IntType>
|
||||
static constexpr IntType Replicate(IntType val, u32 num_bits, u32 to_bit) {
|
||||
if (num_bits == 0 || to_bit == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Decode the RGBA components and renormalize them to the range [0, 255]
|
||||
u16 r = static_cast<u16>(strm.ReadBits<16>());
|
||||
u16 g = static_cast<u16>(strm.ReadBits<16>());
|
||||
u16 b = static_cast<u16>(strm.ReadBits<16>());
|
||||
u16 a = static_cast<u16>(strm.ReadBits<16>());
|
||||
|
||||
u32 rgba = (r >> 8) | (g & 0xFF00) | (static_cast<u32>(b) & 0xFF00) << 8 |
|
||||
(static_cast<u32>(a) & 0xFF00) << 16;
|
||||
|
||||
for (u32 j = 0; j < blockHeight; j++) {
|
||||
for (u32 i = 0; i < blockWidth; i++) {
|
||||
outBuf[j * blockWidth + i] = rgba;
|
||||
const IntType v = val & static_cast<IntType>((1 << num_bits) - 1);
|
||||
IntType res = v;
|
||||
u32 reslen = num_bits;
|
||||
while (reslen < to_bit) {
|
||||
u32 comp = 0;
|
||||
if (num_bits > to_bit - reslen) {
|
||||
u32 newshift = to_bit - reslen;
|
||||
comp = num_bits - newshift;
|
||||
num_bits = newshift;
|
||||
}
|
||||
res = static_cast<IntType>(res << num_bits);
|
||||
res = static_cast<IntType>(res | (v >> comp));
|
||||
reslen += num_bits;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
|
||||
for (u32 j = 0; j < blockHeight; j++) {
|
||||
for (u32 i = 0; i < blockWidth; i++) {
|
||||
outBuf[j * blockWidth + i] = 0xFFFF00FF;
|
||||
}
|
||||
static constexpr std::size_t NumReplicateEntries(u32 num_bits) {
|
||||
return std::size_t(1) << num_bits;
|
||||
}
|
||||
|
||||
template <typename IntType, u32 num_bits, u32 to_bit>
|
||||
static constexpr auto MakeReplicateTable() {
|
||||
std::array<IntType, NumReplicateEntries(num_bits)> table{};
|
||||
for (IntType value = 0; value < static_cast<IntType>(std::size(table)); ++value) {
|
||||
table[value] = Replicate(value, num_bits, to_bit);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
static constexpr auto REPLICATE_BYTE_TO_16_TABLE = MakeReplicateTable<u32, 8, 16>();
|
||||
@@ -572,6 +648,9 @@ static constexpr auto REPLICATE_2_BIT_TO_8_TABLE = MakeReplicateTable<u32, 2, 8>
|
||||
static constexpr auto REPLICATE_3_BIT_TO_8_TABLE = MakeReplicateTable<u32, 3, 8>();
|
||||
static constexpr auto REPLICATE_4_BIT_TO_8_TABLE = MakeReplicateTable<u32, 4, 8>();
|
||||
static constexpr auto REPLICATE_5_BIT_TO_8_TABLE = MakeReplicateTable<u32, 5, 8>();
|
||||
static constexpr auto REPLICATE_6_BIT_TO_8_TABLE = MakeReplicateTable<u32, 6, 8>();
|
||||
static constexpr auto REPLICATE_7_BIT_TO_8_TABLE = MakeReplicateTable<u32, 7, 8>();
|
||||
static constexpr auto REPLICATE_8_BIT_TO_8_TABLE = MakeReplicateTable<u32, 8, 8>();
|
||||
/// Use a precompiled table with the most common usages, if it's not in the expected range, fallback
|
||||
/// to the runtime implementation
|
||||
static constexpr u32 FastReplicateTo8(u32 value, u32 num_bits) {
|
||||
@@ -1316,6 +1395,37 @@ static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues,
|
||||
#undef READ_INT_VALUES
|
||||
}
|
||||
|
||||
static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 blockWidth,
|
||||
u32 blockHeight) {
|
||||
// Don't actually care about the void extent, just read the bits...
|
||||
for (s32 i = 0; i < 4; ++i) {
|
||||
strm.ReadBits<13>();
|
||||
}
|
||||
|
||||
// Decode the RGBA components and renormalize them to the range [0, 255]
|
||||
u16 r = static_cast<u16>(strm.ReadBits<16>());
|
||||
u16 g = static_cast<u16>(strm.ReadBits<16>());
|
||||
u16 b = static_cast<u16>(strm.ReadBits<16>());
|
||||
u16 a = static_cast<u16>(strm.ReadBits<16>());
|
||||
|
||||
u32 rgba = (r >> 8) | (g & 0xFF00) | (static_cast<u32>(b) & 0xFF00) << 8 |
|
||||
(static_cast<u32>(a) & 0xFF00) << 16;
|
||||
|
||||
for (u32 j = 0; j < blockHeight; j++) {
|
||||
for (u32 i = 0; i < blockWidth; i++) {
|
||||
outBuf[j * blockWidth + i] = rgba;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
|
||||
for (u32 j = 0; j < blockHeight; j++) {
|
||||
for (u32 i = 0; i < blockWidth; i++) {
|
||||
outBuf[j * blockWidth + i] = 0xFFFF00FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth,
|
||||
const u32 blockHeight, std::span<u32, 12 * 12> outBuf) {
|
||||
InputBitStream strm(inBuf);
|
||||
|
||||
@@ -9,117 +9,6 @@
|
||||
|
||||
namespace Tegra::Texture::ASTC {
|
||||
|
||||
enum class IntegerEncoding { JustBits, Quint, Trit };
|
||||
|
||||
struct IntegerEncodedValue {
|
||||
constexpr IntegerEncodedValue() = default;
|
||||
|
||||
constexpr IntegerEncodedValue(IntegerEncoding encoding_, u32 num_bits_)
|
||||
: encoding{encoding_}, num_bits{num_bits_} {}
|
||||
|
||||
constexpr bool MatchesEncoding(const IntegerEncodedValue& other) const {
|
||||
return encoding == other.encoding && num_bits == other.num_bits;
|
||||
}
|
||||
|
||||
// Returns the number of bits required to encode num_vals values.
|
||||
u32 GetBitLength(u32 num_vals) const {
|
||||
u32 total_bits = num_bits * num_vals;
|
||||
if (encoding == IntegerEncoding::Trit) {
|
||||
total_bits += (num_vals * 8 + 4) / 5;
|
||||
} else if (encoding == IntegerEncoding::Quint) {
|
||||
total_bits += (num_vals * 7 + 2) / 3;
|
||||
}
|
||||
return total_bits;
|
||||
}
|
||||
|
||||
IntegerEncoding encoding{};
|
||||
u32 num_bits = 0;
|
||||
u32 bit_value = 0;
|
||||
union {
|
||||
u32 quint_value = 0;
|
||||
u32 trit_value;
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a new instance of this struct that corresponds to the
|
||||
// can take no more than mav_value values
|
||||
constexpr IntegerEncodedValue CreateEncoding(u32 mav_value) {
|
||||
while (mav_value > 0) {
|
||||
u32 check = mav_value + 1;
|
||||
|
||||
// Is mav_value a power of two?
|
||||
if (!(check & (check - 1))) {
|
||||
return IntegerEncodedValue(IntegerEncoding::JustBits, std::popcount(mav_value));
|
||||
}
|
||||
|
||||
// Is mav_value of the type 3*2^n - 1?
|
||||
if ((check % 3 == 0) && !((check / 3) & ((check / 3) - 1))) {
|
||||
return IntegerEncodedValue(IntegerEncoding::Trit, std::popcount(check / 3 - 1));
|
||||
}
|
||||
|
||||
// Is mav_value of the type 5*2^n - 1?
|
||||
if ((check % 5 == 0) && !((check / 5) & ((check / 5) - 1))) {
|
||||
return IntegerEncodedValue(IntegerEncoding::Quint, std::popcount(check / 5 - 1));
|
||||
}
|
||||
|
||||
// Apparently it can't be represented with a bounded integer sequence...
|
||||
// just iterate.
|
||||
mav_value--;
|
||||
}
|
||||
return IntegerEncodedValue(IntegerEncoding::JustBits, 0);
|
||||
}
|
||||
|
||||
constexpr std::array<IntegerEncodedValue, 256> MakeEncodedValues() {
|
||||
std::array<IntegerEncodedValue, 256> encodings{};
|
||||
for (std::size_t i = 0; i < encodings.size(); ++i) {
|
||||
encodings[i] = CreateEncoding(static_cast<u32>(i));
|
||||
}
|
||||
return encodings;
|
||||
}
|
||||
|
||||
constexpr std::array<IntegerEncodedValue, 256> ASTC_ENCODINGS_VALUES = MakeEncodedValues();
|
||||
|
||||
// Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)]
|
||||
// is the same as [(num_bits - 1):0] and repeats all the way down.
|
||||
template <typename IntType>
|
||||
constexpr IntType Replicate(IntType val, u32 num_bits, u32 to_bit) {
|
||||
if (num_bits == 0 || to_bit == 0) {
|
||||
return 0;
|
||||
}
|
||||
const IntType v = val & static_cast<IntType>((1 << num_bits) - 1);
|
||||
IntType res = v;
|
||||
u32 reslen = num_bits;
|
||||
while (reslen < to_bit) {
|
||||
u32 comp = 0;
|
||||
if (num_bits > to_bit - reslen) {
|
||||
u32 newshift = to_bit - reslen;
|
||||
comp = num_bits - newshift;
|
||||
num_bits = newshift;
|
||||
}
|
||||
res = static_cast<IntType>(res << num_bits);
|
||||
res = static_cast<IntType>(res | (v >> comp));
|
||||
reslen += num_bits;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
constexpr std::size_t NumReplicateEntries(u32 num_bits) {
|
||||
return std::size_t(1) << num_bits;
|
||||
}
|
||||
|
||||
template <typename IntType, u32 num_bits, u32 to_bit>
|
||||
constexpr auto MakeReplicateTable() {
|
||||
std::array<IntType, NumReplicateEntries(num_bits)> table{};
|
||||
for (IntType value = 0; value < static_cast<IntType>(std::size(table)); ++value) {
|
||||
table[value] = Replicate(value, num_bits, to_bit);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
constexpr auto REPLICATE_6_BIT_TO_8_TABLE = MakeReplicateTable<u32, 6, 8>();
|
||||
constexpr auto REPLICATE_7_BIT_TO_8_TABLE = MakeReplicateTable<u32, 7, 8>();
|
||||
constexpr auto REPLICATE_8_BIT_TO_8_TABLE = MakeReplicateTable<u32, 8, 8>();
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -228,7 +228,9 @@ void MemoryCommit::Release() {
|
||||
|
||||
MemoryAllocator::MemoryAllocator(const Device& device_, bool export_allocations_)
|
||||
: device{device_}, properties{device_.GetPhysical().GetMemoryProperties()},
|
||||
export_allocations{export_allocations_} {}
|
||||
export_allocations{export_allocations_},
|
||||
buffer_image_granularity{
|
||||
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {}
|
||||
|
||||
MemoryAllocator::~MemoryAllocator() = default;
|
||||
|
||||
@@ -258,7 +260,9 @@ MemoryCommit MemoryAllocator::Commit(const vk::Buffer& buffer, MemoryUsage usage
|
||||
}
|
||||
|
||||
MemoryCommit MemoryAllocator::Commit(const vk::Image& image, MemoryUsage usage) {
|
||||
auto commit = Commit(device.GetLogical().GetImageMemoryRequirements(*image), usage);
|
||||
VkMemoryRequirements requirements = device.GetLogical().GetImageMemoryRequirements(*image);
|
||||
requirements.size = Common::AlignUp(requirements.size, buffer_image_granularity);
|
||||
auto commit = Commit(requirements, usage);
|
||||
image.BindMemory(commit.Memory(), commit.Offset());
|
||||
return commit;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,8 @@ private:
|
||||
const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
|
||||
const bool export_allocations; ///< True when memory allocations have to be exported.
|
||||
std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
|
||||
VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
|
||||
// and optimal images
|
||||
};
|
||||
|
||||
/// Returns true when a memory usage is guaranteed to be host visible.
|
||||
|
||||
@@ -24,6 +24,36 @@
|
||||
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="fps_cap_label">
|
||||
<property name="text">
|
||||
<string>Framerate Cap</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Requires the use of the FPS Limiter Toggle hotkey to take effect.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="fps_cap">
|
||||
<property name="suffix">
|
||||
<string>x</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
@@ -51,36 +81,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="fps_cap_label">
|
||||
<property name="text">
|
||||
<string>Framerate Cap</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Requires the use of the FPS Limiter Toggle hotkey to take effect.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="fps_cap">
|
||||
<property name="suffix">
|
||||
<string>x</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_multi_core">
|
||||
<property name="text">
|
||||
|
||||
@@ -82,14 +82,17 @@
|
||||
<string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use asynchronous shader building</string>
|
||||
<string>Use asynchronous shader building (hack)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_fast_gpu_time">
|
||||
<property name="toolTip">
|
||||
<string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Fast GPU Time</string>
|
||||
<string>Use Fast GPU Time (hack)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -309,11 +309,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
buttons_param[button_id].Clear();
|
||||
button_map[button_id]->setText(tr("[not set]"));
|
||||
});
|
||||
context_menu.addAction(tr("Toggle button"), [&] {
|
||||
const bool toggle_value = !buttons_param[button_id].Get("toggle", false);
|
||||
buttons_param[button_id].Set("toggle", toggle_value);
|
||||
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
|
||||
});
|
||||
if (buttons_param[button_id].Has("toggle")) {
|
||||
context_menu.addAction(tr("Toggle button"), [&] {
|
||||
const bool toggle_value =
|
||||
!buttons_param[button_id].Get("toggle", false);
|
||||
buttons_param[button_id].Set("toggle", toggle_value);
|
||||
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
|
||||
});
|
||||
}
|
||||
if (buttons_param[button_id].Has("threshold")) {
|
||||
context_menu.addAction(tr("Set threshold"), [&] {
|
||||
const int button_threshold = static_cast<int>(
|
||||
|
||||
@@ -122,6 +122,7 @@ void PlayerControlPreview::UpdateColors() {
|
||||
colors.slider_arrow = QColor(14, 15, 18);
|
||||
colors.font2 = QColor(255, 255, 255);
|
||||
colors.indicator = QColor(170, 238, 255);
|
||||
colors.indicator2 = QColor(100, 255, 100);
|
||||
colors.deadzone = QColor(204, 136, 136);
|
||||
colors.slider_button = colors.button;
|
||||
}
|
||||
@@ -139,6 +140,7 @@ void PlayerControlPreview::UpdateColors() {
|
||||
colors.slider_arrow = QColor(65, 68, 73);
|
||||
colors.font2 = QColor(0, 0, 0);
|
||||
colors.indicator = QColor(0, 0, 200);
|
||||
colors.indicator2 = QColor(0, 150, 0);
|
||||
colors.deadzone = QColor(170, 0, 0);
|
||||
colors.slider_button = QColor(153, 149, 149);
|
||||
}
|
||||
@@ -317,8 +319,7 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center)
|
||||
using namespace Settings::NativeAnalog;
|
||||
DrawJoystick(p, center + QPointF(9, -69) + (axis_values[LStick].value * 8), 1.8f,
|
||||
button_values[Settings::NativeButton::LStick]);
|
||||
DrawRawJoystick(p, center + QPointF(-140, 90), axis_values[LStick].raw_value,
|
||||
axis_values[LStick].properties);
|
||||
DrawRawJoystick(p, center + QPointF(-140, 90), QPointF(0, 0));
|
||||
}
|
||||
|
||||
using namespace Settings::NativeButton;
|
||||
@@ -432,8 +433,7 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center
|
||||
using namespace Settings::NativeAnalog;
|
||||
DrawJoystick(p, center + QPointF(-9, 11) + (axis_values[RStick].value * 8), 1.8f,
|
||||
button_values[Settings::NativeButton::RStick]);
|
||||
DrawRawJoystick(p, center + QPointF(140, 90), axis_values[RStick].raw_value,
|
||||
axis_values[RStick].properties);
|
||||
DrawRawJoystick(p, QPointF(0, 0), center + QPointF(140, 90));
|
||||
}
|
||||
|
||||
using namespace Settings::NativeButton;
|
||||
@@ -547,8 +547,7 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center)
|
||||
|
||||
DrawJoystick(p, center + QPointF(-65, -65) + (l_stick.value * 7), 1.62f, l_button);
|
||||
DrawJoystick(p, center + QPointF(65, 12) + (r_stick.value * 7), 1.62f, r_button);
|
||||
DrawRawJoystick(p, center + QPointF(-180, 90), l_stick.raw_value, l_stick.properties);
|
||||
DrawRawJoystick(p, center + QPointF(180, 90), r_stick.raw_value, r_stick.properties);
|
||||
DrawRawJoystick(p, center + QPointF(-180, 90), center + QPointF(180, 90));
|
||||
}
|
||||
|
||||
using namespace Settings::NativeButton;
|
||||
@@ -634,8 +633,7 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen
|
||||
|
||||
DrawJoystick(p, center + QPointF(-171, -41) + (l_stick.value * 4), 1.0f, l_button);
|
||||
DrawJoystick(p, center + QPointF(171, 8) + (r_stick.value * 4), 1.0f, r_button);
|
||||
DrawRawJoystick(p, center + QPointF(-50, 0), l_stick.raw_value, l_stick.properties);
|
||||
DrawRawJoystick(p, center + QPointF(50, 0), r_stick.raw_value, r_stick.properties);
|
||||
DrawRawJoystick(p, center + QPointF(-50, 0), center + QPointF(50, 0));
|
||||
}
|
||||
|
||||
using namespace Settings::NativeButton;
|
||||
@@ -728,10 +726,7 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center)
|
||||
button_values[Settings::NativeButton::LStick]);
|
||||
DrawProJoystick(p, center + QPointF(51, 0), axis_values[RStick].value, 11,
|
||||
button_values[Settings::NativeButton::RStick]);
|
||||
DrawRawJoystick(p, center + QPointF(-50, 105), axis_values[LStick].raw_value,
|
||||
axis_values[LStick].properties);
|
||||
DrawRawJoystick(p, center + QPointF(50, 105), axis_values[RStick].raw_value,
|
||||
axis_values[RStick].properties);
|
||||
DrawRawJoystick(p, center + QPointF(-50, 105), center + QPointF(50, 105));
|
||||
}
|
||||
|
||||
using namespace Settings::NativeButton;
|
||||
@@ -821,10 +816,7 @@ void PlayerControlPreview::DrawGCController(QPainter& p, const QPointF center) {
|
||||
p.setBrush(colors.font);
|
||||
DrawSymbol(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), Symbol::C,
|
||||
1.0f);
|
||||
DrawRawJoystick(p, center + QPointF(-198, -125), axis_values[LStick].raw_value,
|
||||
axis_values[LStick].properties);
|
||||
DrawRawJoystick(p, center + QPointF(198, -125), axis_values[RStick].raw_value,
|
||||
axis_values[RStick].properties);
|
||||
DrawRawJoystick(p, center + QPointF(-198, -125), center + QPointF(198, -125));
|
||||
}
|
||||
|
||||
using namespace Settings::NativeButton;
|
||||
@@ -2358,8 +2350,33 @@ void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, boo
|
||||
DrawCircle(p, center, 7.5f);
|
||||
}
|
||||
|
||||
void PlayerControlPreview::DrawRawJoystick(QPainter& p, const QPointF center, const QPointF value,
|
||||
const Input::AnalogProperties& properties) {
|
||||
void PlayerControlPreview::DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right) {
|
||||
using namespace Settings::NativeAnalog;
|
||||
if (controller_type != Settings::ControllerType::LeftJoycon) {
|
||||
DrawJoystickProperties(p, center_right, axis_values[RStick].properties);
|
||||
p.setPen(colors.indicator);
|
||||
p.setBrush(colors.indicator);
|
||||
DrawJoystickDot(p, center_right, axis_values[RStick].raw_value,
|
||||
axis_values[RStick].properties);
|
||||
p.setPen(colors.indicator2);
|
||||
p.setBrush(colors.indicator2);
|
||||
DrawJoystickDot(p, center_right, axis_values[RStick].value, axis_values[RStick].properties);
|
||||
}
|
||||
|
||||
if (controller_type != Settings::ControllerType::RightJoycon) {
|
||||
DrawJoystickProperties(p, center_left, axis_values[LStick].properties);
|
||||
p.setPen(colors.indicator);
|
||||
p.setBrush(colors.indicator);
|
||||
DrawJoystickDot(p, center_left, axis_values[LStick].raw_value,
|
||||
axis_values[LStick].properties);
|
||||
p.setPen(colors.indicator2);
|
||||
p.setBrush(colors.indicator2);
|
||||
DrawJoystickDot(p, center_left, axis_values[LStick].value, axis_values[LStick].properties);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerControlPreview::DrawJoystickProperties(QPainter& p, const QPointF center,
|
||||
const Input::AnalogProperties& properties) {
|
||||
constexpr float size = 45.0f;
|
||||
const float range = size * properties.range;
|
||||
const float deadzone = size * properties.deadzone;
|
||||
@@ -2376,10 +2393,14 @@ void PlayerControlPreview::DrawRawJoystick(QPainter& p, const QPointF center, co
|
||||
pen.setColor(colors.deadzone);
|
||||
p.setPen(pen);
|
||||
DrawCircle(p, center, deadzone);
|
||||
}
|
||||
|
||||
void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center, const QPointF value,
|
||||
const Input::AnalogProperties& properties) {
|
||||
constexpr float size = 45.0f;
|
||||
const float range = size * properties.range;
|
||||
|
||||
// Dot pointer
|
||||
p.setPen(colors.indicator);
|
||||
p.setBrush(colors.indicator);
|
||||
DrawCircle(p, center + (value * range), 2);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ private:
|
||||
QColor highlight2{};
|
||||
QColor transparent{};
|
||||
QColor indicator{};
|
||||
QColor indicator2{};
|
||||
QColor led_on{};
|
||||
QColor led_off{};
|
||||
QColor slider{};
|
||||
@@ -139,7 +140,10 @@ private:
|
||||
// Draw joystick functions
|
||||
void DrawJoystick(QPainter& p, QPointF center, float size, bool pressed);
|
||||
void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size, bool pressed);
|
||||
void DrawRawJoystick(QPainter& p, QPointF center, QPointF value,
|
||||
void DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right);
|
||||
void DrawJoystickProperties(QPainter& p, QPointF center,
|
||||
const Input::AnalogProperties& properties);
|
||||
void DrawJoystickDot(QPainter& p, QPointF center, QPointF value,
|
||||
const Input::AnalogProperties& properties);
|
||||
void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar, bool pressed);
|
||||
void DrawGCJoystick(QPainter& p, QPointF center, bool pressed);
|
||||
|
||||
@@ -21,6 +21,7 @@ void ToggleConsole() {
|
||||
console_shown = UISettings::values.show_console.GetValue();
|
||||
}
|
||||
|
||||
using namespace Common::Log;
|
||||
#if defined(_WIN32) && !defined(_DEBUG)
|
||||
FILE* temp;
|
||||
if (UISettings::values.show_console) {
|
||||
@@ -29,24 +30,20 @@ void ToggleConsole() {
|
||||
freopen_s(&temp, "CONIN$", "r", stdin);
|
||||
freopen_s(&temp, "CONOUT$", "w", stdout);
|
||||
freopen_s(&temp, "CONOUT$", "w", stderr);
|
||||
Common::Log::AddBackend(std::make_unique<Common::Log::ColorConsoleBackend>());
|
||||
SetColorConsoleBackendEnabled(true);
|
||||
}
|
||||
} else {
|
||||
if (FreeConsole()) {
|
||||
// In order to close the console, we have to also detach the streams on it.
|
||||
// Just redirect them to NUL if there is no console window
|
||||
Common::Log::RemoveBackend(Common::Log::ColorConsoleBackend::Name());
|
||||
SetColorConsoleBackendEnabled(false);
|
||||
freopen_s(&temp, "NUL", "r", stdin);
|
||||
freopen_s(&temp, "NUL", "w", stdout);
|
||||
freopen_s(&temp, "NUL", "w", stderr);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (UISettings::values.show_console) {
|
||||
Common::Log::AddBackend(std::make_unique<Common::Log::ColorConsoleBackend>());
|
||||
} else {
|
||||
Common::Log::RemoveBackend(Common::Log::ColorConsoleBackend::Name());
|
||||
}
|
||||
SetColorConsoleBackendEnabled(UISettings::values.show_console.GetValue());
|
||||
#endif
|
||||
}
|
||||
} // namespace Debugger
|
||||
|
||||
@@ -175,21 +175,6 @@ void GMainWindow::ShowTelemetryCallout() {
|
||||
|
||||
const int GMainWindow::max_recent_files_item;
|
||||
|
||||
static void InitializeLogging() {
|
||||
using namespace Common;
|
||||
|
||||
Log::Filter log_filter;
|
||||
log_filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
||||
Log::SetGlobalFilter(log_filter);
|
||||
|
||||
const auto log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir);
|
||||
void(FS::CreateDir(log_dir));
|
||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE));
|
||||
#ifdef _WIN32
|
||||
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
||||
#endif
|
||||
}
|
||||
|
||||
static void RemoveCachedContents() {
|
||||
const auto cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir);
|
||||
const auto offline_fonts = cache_dir / "fonts";
|
||||
@@ -207,8 +192,6 @@ GMainWindow::GMainWindow()
|
||||
: input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
|
||||
config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
|
||||
provider{std::make_unique<FileSys::ManualContentProvider>()} {
|
||||
InitializeLogging();
|
||||
|
||||
LoadTranslation();
|
||||
|
||||
setAcceptDrops(true);
|
||||
@@ -2814,8 +2797,6 @@ void GMainWindow::OnToggleFilterBar() {
|
||||
}
|
||||
|
||||
void GMainWindow::OnCaptureScreenshot() {
|
||||
OnPauseGame();
|
||||
|
||||
const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
const auto screenshot_path =
|
||||
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir));
|
||||
@@ -2827,23 +2808,22 @@ void GMainWindow::OnCaptureScreenshot() {
|
||||
.arg(date);
|
||||
|
||||
if (!Common::FS::CreateDir(screenshot_path.toStdString())) {
|
||||
OnStartGame();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (UISettings::values.enable_screenshot_save_as) {
|
||||
OnPauseGame();
|
||||
filename = QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), filename,
|
||||
tr("PNG Image (*.png)"));
|
||||
OnStartGame();
|
||||
if (filename.isEmpty()) {
|
||||
OnStartGame();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor.GetValue(),
|
||||
filename);
|
||||
OnStartGame();
|
||||
}
|
||||
|
||||
// TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant
|
||||
@@ -3401,6 +3381,7 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Common::Log::Initialize();
|
||||
Common::DetachedTasks detached_tasks;
|
||||
MicroProfileOnThreadCreate("Frontend");
|
||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||
@@ -3440,6 +3421,7 @@ int main(int argc, char* argv[]) {
|
||||
// generating shaders
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
Core::System::InitializeGlobalInstance();
|
||||
GMainWindow main_window;
|
||||
// After settings have been loaded by GMainWindow, apply the filter
|
||||
main_window.show();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
|
||||
|
||||
# Credits to Samantas5855 and others for this function.
|
||||
function(create_resource file output filename)
|
||||
# Read hex data from file
|
||||
file(READ ${file} filedata HEX)
|
||||
|
||||
@@ -278,6 +278,9 @@ void Config::ReadValues() {
|
||||
if (Settings::values.players.GetValue()[p].analogs[i].empty())
|
||||
Settings::values.players.GetValue()[p].analogs[i] = default_param;
|
||||
}
|
||||
|
||||
Settings::values.players.GetValue()[p].connected =
|
||||
sdl2_config->GetBoolean(group, "connected", false);
|
||||
}
|
||||
|
||||
ReadSetting("ControlsGeneral", Settings::values.mouse_enabled);
|
||||
|
||||
@@ -122,6 +122,10 @@ void EmuWindow_SDL2::OnResize() {
|
||||
UpdateCurrentFramebufferLayout(width, height);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::ShowCursor(bool show_cursor) {
|
||||
SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::Fullscreen() {
|
||||
switch (Settings::values.fullscreen_mode.GetValue()) {
|
||||
case Settings::FullscreenMode::Exclusive:
|
||||
@@ -228,6 +232,7 @@ void EmuWindow_SDL2::WaitEvent() {
|
||||
}
|
||||
}
|
||||
|
||||
// Credits to Samantas5855 and others for this function.
|
||||
void EmuWindow_SDL2::SetWindowIcon() {
|
||||
SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size);
|
||||
if (yuzu_icon_stream == nullptr) {
|
||||
|
||||
@@ -67,6 +67,9 @@ protected:
|
||||
/// Called by WaitEvent when any event that may cause the window to be resized occurs
|
||||
void OnResize();
|
||||
|
||||
/// Called when users want to hide the mouse cursor
|
||||
void ShowCursor(bool show_cursor);
|
||||
|
||||
/// Called when user passes the fullscreen parameter flag
|
||||
void Fullscreen();
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
ShowCursor(false);
|
||||
}
|
||||
|
||||
window_context = SDL_GL_CreateContext(render_window);
|
||||
|
||||
@@ -45,6 +45,7 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsyste
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
ShowCursor(false);
|
||||
}
|
||||
|
||||
switch (wm.subsystem) {
|
||||
|
||||
@@ -74,31 +74,14 @@ static void PrintVersion() {
|
||||
std::cout << "yuzu " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
|
||||
}
|
||||
|
||||
static void InitializeLogging() {
|
||||
using namespace Common;
|
||||
|
||||
Log::Filter log_filter(Log::Level::Debug);
|
||||
log_filter.ParseFilterString(static_cast<std::string>(Settings::values.log_filter));
|
||||
Log::SetGlobalFilter(log_filter);
|
||||
|
||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
||||
|
||||
const auto& log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir);
|
||||
void(FS::CreateDir(log_dir));
|
||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE));
|
||||
#ifdef _WIN32
|
||||
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Application entry point
|
||||
int main(int argc, char** argv) {
|
||||
Common::Log::Initialize();
|
||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||
Common::DetachedTasks detached_tasks;
|
||||
Config config;
|
||||
|
||||
int option_index = 0;
|
||||
|
||||
InitializeLogging();
|
||||
#ifdef _WIN32
|
||||
int argc_w;
|
||||
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
|
||||
@@ -163,6 +146,7 @@ int main(int argc, char** argv) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Core::System::InitializeGlobalInstance();
|
||||
auto& system{Core::System::GetInstance()};
|
||||
InputCommon::InputSubsystem input_subsystem;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user