Compare commits
138 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5d7706ca1 | ||
|
|
3d1e8f750c | ||
|
|
e3da9fc367 | ||
|
|
24a55bba42 | ||
|
|
b87a71b3fe | ||
|
|
c65a8fafa0 | ||
|
|
0aebe6b3d5 | ||
|
|
7c3cc08957 | ||
|
|
3575d367a4 | ||
|
|
f4b98a857b | ||
|
|
c8f3fc9a4b | ||
|
|
f4c69149f9 | ||
|
|
6a03badcbc | ||
|
|
0f148548f3 | ||
|
|
c5803e30d3 | ||
|
|
e3fadb9616 | ||
|
|
4cd52a34b9 | ||
|
|
03c2d049d4 | ||
|
|
49e5de9f03 | ||
|
|
0d1a99edf6 | ||
|
|
c3dd456d51 | ||
|
|
8e28af6f89 | ||
|
|
3d3b10adc7 | ||
|
|
69bfe075b5 | ||
|
|
88a3140c9b | ||
|
|
519035db3d | ||
|
|
5f119bed56 | ||
|
|
170e19d4ea | ||
|
|
3b885691a1 | ||
|
|
56cc1c11ec | ||
|
|
f00ca69a81 | ||
|
|
0722adb471 | ||
|
|
068668780c | ||
|
|
04b9cde4f5 | ||
|
|
7d209b3c9f | ||
|
|
497b81558e | ||
|
|
98762e9601 | ||
|
|
3a96670f2d | ||
|
|
aaec0b7e70 | ||
|
|
f8ab956189 | ||
|
|
3145114190 | ||
|
|
e21190f47f | ||
|
|
2cb3fdca86 | ||
|
|
c324a378ac | ||
|
|
fd1f5c5414 | ||
|
|
6daebaaa57 | ||
|
|
b07f4d6afb | ||
|
|
ad0166a982 | ||
|
|
7e5e4f8d7a | ||
|
|
a7d6c0d6ea | ||
|
|
81739a5448 | ||
|
|
05cb10530f | ||
|
|
b37354cca8 | ||
|
|
9fc0d1d701 | ||
|
|
e066bc75b9 | ||
|
|
8aeff9cf8e | ||
|
|
c4015cd93a | ||
|
|
ce23ae3ede | ||
|
|
64b5e5d5d9 | ||
|
|
52636f67cc | ||
|
|
8bd8d1e3da | ||
|
|
6642011706 | ||
|
|
49c0c081c4 | ||
|
|
4757ffdcce | ||
|
|
274d1fb0fc | ||
|
|
3ff21345b4 | ||
|
|
c1ae841f47 | ||
|
|
316b933a31 | ||
|
|
0cad310e12 | ||
|
|
4f41ffdd41 | ||
|
|
7c7b2b8285 | ||
|
|
b89fb430c7 | ||
|
|
b30c5370b1 | ||
|
|
020d005d8c | ||
|
|
706892de7d | ||
|
|
3d68f6ba6c | ||
|
|
7230ceb584 | ||
|
|
afb26b190f | ||
|
|
e2037821b6 | ||
|
|
12a6996262 | ||
|
|
379a935016 | ||
|
|
04524e76c2 | ||
|
|
3e966be6fc | ||
|
|
0944bfe3cb | ||
|
|
aec90ca506 | ||
|
|
ef2c955db5 | ||
|
|
dacc89b38b | ||
|
|
51a3e93f8e | ||
|
|
0d51cfe2f5 | ||
|
|
eb6cbfdbd8 | ||
|
|
1b3dd30ba8 | ||
|
|
854f474f52 | ||
|
|
639346bcfb | ||
|
|
d990f2355b | ||
|
|
f89b47fdf7 | ||
|
|
913896cbd9 | ||
|
|
3417f46dd5 | ||
|
|
4633dd9505 | ||
|
|
f2c2383c8b | ||
|
|
476e0fae4c | ||
|
|
b8384c0c91 | ||
|
|
9df698fa9c | ||
|
|
70a6691e3b | ||
|
|
e197476344 | ||
|
|
650c89bbbc | ||
|
|
bebe09a1aa | ||
|
|
77c684c114 | ||
|
|
c9aadff9a9 | ||
|
|
51bd76a5fd | ||
|
|
9f6a5660e8 | ||
|
|
762bf6a522 | ||
|
|
637f9d780a | ||
|
|
956b5db52e | ||
|
|
8b815877a6 | ||
|
|
1b0a74e23f | ||
|
|
9a3c0b161e | ||
|
|
b0c92b80b1 | ||
|
|
d800a02b4b | ||
|
|
77cfe4f027 | ||
|
|
ce39ae3e57 | ||
|
|
4bda9693be | ||
|
|
c42b818cf9 | ||
|
|
53a55bd751 | ||
|
|
2355460d7c | ||
|
|
016e357c75 | ||
|
|
c1bebdef5e | ||
|
|
81a44d38ee | ||
|
|
5a9df3c675 | ||
|
|
c996787d84 | ||
|
|
4030f600dc | ||
|
|
78443a7f29 | ||
|
|
c1811ed3d1 | ||
|
|
be51120d23 | ||
|
|
827bb08c91 | ||
|
|
c164f02c48 | ||
|
|
9da1552417 | ||
|
|
23dc36ed71 | ||
|
|
5f57a70a7d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ src/common/scm_rev.cpp
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
||||
CMakeLists.txt.user
|
||||
|
||||
# *nix related
|
||||
# Common convention for backup or temporary files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Reporting Issues
|
||||
|
||||
**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/yuzu-emu/yuzu/wiki/FAQ) and then either visit our Discord server, [our forum](https://community.citra-emu.org) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
|
||||
**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/yuzu-emu/yuzu/wiki/FAQ) and then either visit our [Discord server](https://discordapp.com/invite/u77vRWY), [our forum](https://community.citra-emu.org) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
|
||||
|
||||
If you believe you have a valid issue report, please post text or a screenshot from the log (the console window that opens alongside yuzu) and build version (hex string visible in the titlebar and zip filename), as well as your hardware and software information if applicable.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2016 Citra Emulator Project
|
||||
# Copyright 2018 Yuzu Emulator Project
|
||||
# Licensed under GPLv2 or any later version
|
||||
# Refer to the license.txt file included.
|
||||
|
||||
@@ -22,7 +22,7 @@ function(windows_copy_files TARGET SOURCE_DIR DEST_DIR)
|
||||
# cmake adds an extra check for command success which doesn't work too well with robocopy
|
||||
# so trick it into thinking the command was successful with the || cmd /c "exit /b 0"
|
||||
add_custom_command(TARGET ${TARGET} POST_BUILD
|
||||
COMMAND if not exist ${DEST_DIR} mkdir ${DEST_DIR} 2> nul
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEST_DIR}
|
||||
COMMAND robocopy ${SOURCE_DIR} ${DEST_DIR} ${ARGN} /NJH /NJS /NDL /NFL /NC /NS /NP || cmd /c "exit /b 0"
|
||||
)
|
||||
endfunction()
|
||||
endfunction()
|
||||
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 990a569b7a...dfdec797e3
2
externals/fmt
vendored
2
externals/fmt
vendored
Submodule externals/fmt updated: 5859e58ba1...c2ce7e4f07
@@ -52,5 +52,5 @@ __declspec(noinline, noreturn)
|
||||
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...)
|
||||
#endif
|
||||
|
||||
#define UNIMPLEMENTED() DEBUG_ASSERT_MSG(false, "Unimplemented code!")
|
||||
#define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!")
|
||||
#define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(ARCHITECTURE_x86_64) && !defined(_M_ARM)
|
||||
#if !defined(ARCHITECTURE_x86_64) && !defined(ARCHITECTURE_ARM)
|
||||
#include <cstdlib> // for exit
|
||||
#endif
|
||||
#include "common/common_types.h"
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#define Crash() __asm__ __volatile__("int $3")
|
||||
#elif defined(_M_ARM)
|
||||
#elif defined(ARCHITECTURE_ARM)
|
||||
#define Crash() __asm__ __volatile__("trap")
|
||||
#else
|
||||
#define Crash() exit(1)
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
#define DEBUGGER_CONFIG "debugger.ini"
|
||||
#define LOGGER_CONFIG "logger.ini"
|
||||
// Files in the directory returned by GetUserPath(D_LOGS_IDX)
|
||||
#define LOG_FILE "citra_log.txt"
|
||||
#define LOG_FILE "yuzu_log.txt"
|
||||
|
||||
// Sys files
|
||||
#define SHARED_FONT "shared_font.bin"
|
||||
|
||||
@@ -678,7 +678,7 @@ std::string GetSysDirectory() {
|
||||
return sysDir;
|
||||
}
|
||||
|
||||
// Returns a string with a Citra data dir or file in the user's home
|
||||
// Returns a string with a yuzu data dir or file in the user's home
|
||||
// directory. To be used in "multi-user" mode (that is, installed).
|
||||
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath) {
|
||||
static std::string paths[NUM_PATH_INDICES];
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
@@ -83,8 +84,10 @@ private:
|
||||
}
|
||||
};
|
||||
while (true) {
|
||||
std::unique_lock<std::mutex> lock(message_mutex);
|
||||
message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); });
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(message_mutex);
|
||||
message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); });
|
||||
}
|
||||
if (!running) {
|
||||
break;
|
||||
}
|
||||
@@ -92,7 +95,7 @@ private:
|
||||
}
|
||||
// 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.
|
||||
constexpr int MAX_LOGS_TO_WRITE = 100;
|
||||
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);
|
||||
@@ -282,4 +285,4 @@ void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
|
||||
Impl::Instance().PushEntry(std::move(entry));
|
||||
}
|
||||
} // namespace Log
|
||||
} // namespace Log
|
||||
|
||||
@@ -94,4 +94,11 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin,
|
||||
bool Filter::CheckMessage(Class log_class, Level level) const {
|
||||
return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]);
|
||||
}
|
||||
|
||||
bool Filter::IsDebug() const {
|
||||
return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) {
|
||||
return static_cast<u8>(l) <= static_cast<u8>(Level::Debug);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Log
|
||||
|
||||
@@ -47,6 +47,9 @@ public:
|
||||
/// Matches class/level combination against the filter, returning true if it passed.
|
||||
bool CheckMessage(Class log_class, Level level) const;
|
||||
|
||||
/// Returns true if any logging classes are set to debug
|
||||
bool IsDebug() const;
|
||||
|
||||
private:
|
||||
std::array<Level, (size_t)Class::Count> class_levels;
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ template <typename... Args>
|
||||
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, const char* format, const Args&... args) {
|
||||
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
|
||||
fmt::make_args(args...));
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
} // namespace Log
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
#include <unistd.h>
|
||||
#define PAGE_MASK (getpagesize() - 1)
|
||||
#define round_page(x) ((((unsigned long)(x)) + PAGE_MASK) & ~(PAGE_MASK))
|
||||
@@ -30,7 +30,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
#else
|
||||
static char* map_hint = nullptr;
|
||||
#if defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
// This OS has no flag to enforce allocation below the 4 GB boundary,
|
||||
// but if we hint that we want a low address it is very likely we will
|
||||
// get one.
|
||||
@@ -42,7 +42,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
#endif
|
||||
void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_ANON | MAP_PRIVATE
|
||||
#if defined(ARCHITECTURE_X64) && defined(MAP_32BIT)
|
||||
#if defined(ARCHITECTURE_x86_64) && defined(MAP_32BIT)
|
||||
| (low ? MAP_32BIT : 0)
|
||||
#endif
|
||||
,
|
||||
@@ -57,7 +57,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
#endif
|
||||
LOG_ERROR(Common_Memory, "Failed to allocate executable memory");
|
||||
}
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
else {
|
||||
if (low) {
|
||||
map_hint += size;
|
||||
|
||||
@@ -134,7 +134,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
|
||||
size_t dir_end = full_path.find_last_of("/"
|
||||
// windows needs the : included for something like just "C:" to be considered a directory
|
||||
#ifdef _WIN32
|
||||
":"
|
||||
"\\:"
|
||||
#endif
|
||||
);
|
||||
if (std::string::npos == dir_end)
|
||||
|
||||
@@ -69,7 +69,7 @@ inline u32 swap32(u32 _data) {
|
||||
inline u64 swap64(u64 _data) {
|
||||
return _byteswap_uint64(_data);
|
||||
}
|
||||
#elif _M_ARM
|
||||
#elif ARCHITECTURE_ARM
|
||||
inline u16 swap16(u16 _data) {
|
||||
u32 data = _data;
|
||||
__asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data));
|
||||
|
||||
@@ -52,27 +52,14 @@ public:
|
||||
template <typename T>
|
||||
class Field : public FieldInterface {
|
||||
public:
|
||||
Field(FieldType type, std::string name, const T& value)
|
||||
: name(std::move(name)), type(type), value(value) {}
|
||||
|
||||
Field(FieldType type, std::string name, T&& value)
|
||||
Field(FieldType type, std::string name, T value)
|
||||
: name(std::move(name)), type(type), value(std::move(value)) {}
|
||||
|
||||
Field(const Field& other) : Field(other.type, other.name, other.value) {}
|
||||
Field(const Field&) = default;
|
||||
Field& operator=(const Field&) = default;
|
||||
|
||||
Field& operator=(const Field& other) {
|
||||
type = other.type;
|
||||
name = other.name;
|
||||
value = other.value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Field& operator=(Field&& other) {
|
||||
type = other.type;
|
||||
name = std::move(other.name);
|
||||
value = std::move(other.value);
|
||||
return *this;
|
||||
}
|
||||
Field(Field&&) = default;
|
||||
Field& operator=(Field&& other) = default;
|
||||
|
||||
void Accept(VisitorInterface& visitor) const override;
|
||||
|
||||
@@ -94,11 +81,11 @@ public:
|
||||
return value;
|
||||
}
|
||||
|
||||
inline bool operator==(const Field<T>& other) {
|
||||
bool operator==(const Field& other) const {
|
||||
return (type == other.type) && (name == other.name) && (value == other.value);
|
||||
}
|
||||
|
||||
inline bool operator!=(const Field<T>& other) {
|
||||
bool operator!=(const Field& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,8 @@ public:
|
||||
*/
|
||||
virtual void LoadContext(const ThreadContext& ctx) = 0;
|
||||
|
||||
virtual void ClearExclusiveState() = 0;
|
||||
|
||||
/// Prepare core for thread reschedule (if needed to correctly handle state)
|
||||
virtual void PrepareReschedule() = 0;
|
||||
};
|
||||
|
||||
@@ -226,6 +226,10 @@ void ARM_Dynarmic::ClearInstructionCache() {
|
||||
jit->ClearCache();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::ClearExclusiveState() {
|
||||
jit->ClearExclusiveState();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::PageTableChanged() {
|
||||
jit = MakeJit(cb);
|
||||
current_page_table = Memory::GetCurrentPageTable();
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
void LoadContext(const ThreadContext& ctx) override;
|
||||
|
||||
void PrepareReschedule() override;
|
||||
void ClearExclusiveState() override;
|
||||
|
||||
void ClearInstructionCache() override;
|
||||
void PageTableChanged() override;
|
||||
|
||||
@@ -193,11 +193,11 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
}
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
SaveContext(thread->context);
|
||||
if (last_bkpt_hit) {
|
||||
if (last_bkpt_hit || (num_instructions == 1)) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
}
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +263,8 @@ void ARM_Unicorn::PrepareReschedule() {
|
||||
CHECKED(uc_emu_stop(uc));
|
||||
}
|
||||
|
||||
void ARM_Unicorn::ClearExclusiveState() {}
|
||||
|
||||
void ARM_Unicorn::ClearInstructionCache() {}
|
||||
|
||||
void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) {
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
void SaveContext(ThreadContext& ctx) override;
|
||||
void LoadContext(const ThreadContext& ctx) override;
|
||||
void PrepareReschedule() override;
|
||||
void ClearExclusiveState() override;
|
||||
void ExecuteInstructions(int num_instructions);
|
||||
void Run() override;
|
||||
void Step() override;
|
||||
|
||||
@@ -58,11 +58,13 @@ ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::DeleteFile(const std::string& path) const {
|
||||
if (!FileUtil::Exists(path)) {
|
||||
std::string full_path = base_directory + path;
|
||||
|
||||
if (!FileUtil::Exists(full_path)) {
|
||||
return ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
|
||||
FileUtil::Delete(path);
|
||||
FileUtil::Delete(full_path);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace FileSys {
|
||||
namespace ErrCodes {
|
||||
enum {
|
||||
NotFound = 1,
|
||||
SaveDataNotFound = 1002,
|
||||
SdCardNotFound = 2001,
|
||||
RomFSNotFound = 2520,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -167,35 +167,4 @@ public:
|
||||
virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0;
|
||||
};
|
||||
|
||||
class FileSystemFactory : NonCopyable {
|
||||
public:
|
||||
virtual ~FileSystemFactory() {}
|
||||
|
||||
/**
|
||||
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
|
||||
*/
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
/**
|
||||
* Tries to open the archive of this type with the specified path
|
||||
* @param path Path to the archive
|
||||
* @return An ArchiveBackend corresponding operating specified archive path.
|
||||
*/
|
||||
virtual ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) = 0;
|
||||
|
||||
/**
|
||||
* Deletes the archive contents and then re-creates the base folder
|
||||
* @param path Path to the archive
|
||||
* @return ResultCode of the operation, 0 on success
|
||||
*/
|
||||
virtual ResultCode Format(const Path& path) = 0;
|
||||
|
||||
/**
|
||||
* Retrieves the format info about the archive with the specified path
|
||||
* @param path Path to the archive
|
||||
* @return Format information about the archive or error code
|
||||
*/
|
||||
virtual ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const = 0;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -11,28 +11,17 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
RomFS_Factory::RomFS_Factory(Loader::AppLoader& app_loader) {
|
||||
RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
|
||||
// Load the RomFS from the app
|
||||
if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) {
|
||||
LOG_ERROR(Service_FS, "Unable to read RomFS!");
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> RomFS_Factory::Open(const Path& path) {
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> RomFSFactory::Open(u64 title_id) {
|
||||
// TODO(DarkLordZach): Use title id.
|
||||
auto archive = std::make_unique<RomFS_FileSystem>(romfs_file, data_offset, data_size);
|
||||
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
|
||||
}
|
||||
|
||||
ResultCode RomFS_Factory::Format(const Path& path) {
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> RomFS_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -15,16 +15,11 @@
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the RomFS archive
|
||||
class RomFS_Factory final : public FileSystemFactory {
|
||||
class RomFSFactory {
|
||||
public:
|
||||
explicit RomFS_Factory(Loader::AppLoader& app_loader);
|
||||
explicit RomFSFactory(Loader::AppLoader& app_loader);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "ArchiveFactory_RomFS";
|
||||
}
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
|
||||
ResultCode Format(const Path& path) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(u64 title_id);
|
||||
|
||||
private:
|
||||
std::shared_ptr<FileUtil::IOFile> romfs_file;
|
||||
|
||||
@@ -12,11 +12,64 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
SaveData_Factory::SaveData_Factory(std::string nand_directory)
|
||||
std::string SaveDataDescriptor::DebugInfo() {
|
||||
return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}]",
|
||||
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(std::string nand_directory)
|
||||
: nand_directory(std::move(nand_directory)) {}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path& path) {
|
||||
std::string save_directory = GetFullPath();
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> SaveDataFactory::Open(SaveDataSpaceId space,
|
||||
SaveDataDescriptor meta) {
|
||||
if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
|
||||
if (meta.zero_1 != 0) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"Possibly incorrect SaveDataDescriptor, type is "
|
||||
"SystemSaveData||SaveData but offset 0x28 is non-zero ({:016X}).",
|
||||
meta.zero_1);
|
||||
}
|
||||
if (meta.zero_2 != 0) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"Possibly incorrect SaveDataDescriptor, type is "
|
||||
"SystemSaveData||SaveData but offset 0x30 is non-zero ({:016X}).",
|
||||
meta.zero_2);
|
||||
}
|
||||
if (meta.zero_3 != 0) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"Possibly incorrect SaveDataDescriptor, type is "
|
||||
"SystemSaveData||SaveData but offset 0x38 is non-zero ({:016X}).",
|
||||
meta.zero_3);
|
||||
}
|
||||
}
|
||||
|
||||
if (meta.type == SaveDataType::SystemSaveData && meta.title_id != 0) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"Possibly incorrect SaveDataDescriptor, type is SystemSaveData but title_id is "
|
||||
"non-zero ({:016X}).",
|
||||
meta.title_id);
|
||||
}
|
||||
|
||||
std::string save_directory =
|
||||
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
|
||||
|
||||
// TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods.
|
||||
// But, user_ids don't match so this works for now.
|
||||
|
||||
if (!FileUtil::Exists(save_directory)) {
|
||||
// TODO(bunnei): This is a work-around to always create a save data directory if it does not
|
||||
// already exist. This is a hack, as we do not understand yet how this works on hardware.
|
||||
// Without a save data directory, many games will assert on boot. This should not have any
|
||||
// bad side-effects.
|
||||
FileUtil::CreateFullPath(save_directory);
|
||||
}
|
||||
|
||||
// TODO(DarkLordZach): For some reason, CreateFullPath doesn't create the last bit. Should be
|
||||
// fixed with VFS.
|
||||
if (!FileUtil::IsDirectory(save_directory)) {
|
||||
FileUtil::CreateDir(save_directory);
|
||||
}
|
||||
|
||||
// Return an error if the save data doesn't actually exist.
|
||||
if (!FileUtil::IsDirectory(save_directory)) {
|
||||
// TODO(Subv): Find out correct error code.
|
||||
@@ -27,28 +80,35 @@ ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path&
|
||||
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
|
||||
}
|
||||
|
||||
ResultCode SaveData_Factory::Format(const Path& path) {
|
||||
LOG_WARNING(Service_FS, "Format archive {}", GetName());
|
||||
// Create the save data directory.
|
||||
if (!FileUtil::CreateFullPath(GetFullPath())) {
|
||||
// TODO(Subv): Find the correct error code.
|
||||
return ResultCode(-1);
|
||||
std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
|
||||
u128 user_id, u64 save_id) const {
|
||||
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
|
||||
// be interpreted as the title id of the current process.
|
||||
if (type == SaveDataType::SaveData && title_id == 0)
|
||||
title_id = Core::CurrentProcess()->program_id;
|
||||
|
||||
std::string prefix;
|
||||
|
||||
switch (space) {
|
||||
case SaveDataSpaceId::NandSystem:
|
||||
prefix = nand_directory + "system/save/";
|
||||
break;
|
||||
case SaveDataSpaceId::NandUser:
|
||||
prefix = nand_directory + "user/save/";
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> SaveData_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
std::string SaveData_Factory::GetFullPath() const {
|
||||
u64 title_id = Core::CurrentProcess()->program_id;
|
||||
// TODO(Subv): Somehow obtain this value.
|
||||
u32 user = 0;
|
||||
return fmt::format("{}save/{:016X}/{:08X}/", nand_directory, title_id, user);
|
||||
switch (type) {
|
||||
case SaveDataType::SystemSaveData:
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}", prefix, save_id, user_id[1], user_id[0]);
|
||||
case SaveDataType::SaveData:
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", prefix, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -12,22 +12,50 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the SaveData archive
|
||||
class SaveData_Factory final : public FileSystemFactory {
|
||||
public:
|
||||
explicit SaveData_Factory(std::string nand_directory);
|
||||
enum class SaveDataSpaceId : u8 {
|
||||
NandSystem = 0,
|
||||
NandUser = 1,
|
||||
SdCard = 2,
|
||||
TemporaryStorage = 3,
|
||||
};
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SaveData_Factory";
|
||||
}
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
|
||||
ResultCode Format(const Path& path) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
|
||||
enum class SaveDataType : u8 {
|
||||
SystemSaveData = 0,
|
||||
SaveData = 1,
|
||||
BcatDeliveryCacheStorage = 2,
|
||||
DeviceSaveData = 3,
|
||||
TemporaryStorage = 4,
|
||||
CacheStorage = 5,
|
||||
};
|
||||
|
||||
struct SaveDataDescriptor {
|
||||
u64_le title_id;
|
||||
u128 user_id;
|
||||
u64_le save_id;
|
||||
SaveDataType type;
|
||||
INSERT_PADDING_BYTES(7);
|
||||
u64_le zero_1;
|
||||
u64_le zero_2;
|
||||
u64_le zero_3;
|
||||
|
||||
std::string DebugInfo();
|
||||
};
|
||||
static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorrect size.");
|
||||
|
||||
/// File system interface to the SaveData archive
|
||||
class SaveDataFactory {
|
||||
public:
|
||||
explicit SaveDataFactory(std::string nand_directory);
|
||||
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(SaveDataSpaceId space,
|
||||
SaveDataDescriptor meta);
|
||||
|
||||
private:
|
||||
std::string nand_directory;
|
||||
std::string sd_directory;
|
||||
|
||||
std::string GetFullPath() const;
|
||||
std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id,
|
||||
u64 save_id) const;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {}
|
||||
SDMCFactory::SDMCFactory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) {
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> SDMCFactory::Open() {
|
||||
// Create the SD Card directory if it doesn't already exist.
|
||||
if (!FileUtil::IsDirectory(sd_directory)) {
|
||||
FileUtil::CreateFullPath(sd_directory);
|
||||
@@ -24,16 +24,4 @@ ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& pat
|
||||
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
|
||||
}
|
||||
|
||||
ResultCode SDMC_Factory::Format(const Path& path) {
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName());
|
||||
// TODO(Subv): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -13,16 +13,11 @@
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the SDCard archive
|
||||
class SDMC_Factory final : public FileSystemFactory {
|
||||
class SDMCFactory {
|
||||
public:
|
||||
explicit SDMC_Factory(std::string sd_directory);
|
||||
explicit SDMCFactory(std::string sd_directory);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SDMC_Factory";
|
||||
}
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
|
||||
ResultCode Format(const Path& path) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open();
|
||||
|
||||
private:
|
||||
std::string sd_directory;
|
||||
|
||||
@@ -61,10 +61,16 @@ const u32 SIGTERM = 15;
|
||||
const u32 MSG_WAITALL = 8;
|
||||
#endif
|
||||
|
||||
const u32 X30_REGISTER = 30;
|
||||
const u32 LR_REGISTER = 30;
|
||||
const u32 SP_REGISTER = 31;
|
||||
const u32 PC_REGISTER = 32;
|
||||
const u32 CPSR_REGISTER = 33;
|
||||
const u32 UC_ARM64_REG_Q0 = 34;
|
||||
const u32 FPSCR_REGISTER = 66;
|
||||
|
||||
// TODO/WiP - Used while working on support for FPU
|
||||
const u32 TODO_DUMMY_REG_997 = 997;
|
||||
const u32 TODO_DUMMY_REG_998 = 998;
|
||||
|
||||
// For sample XML files see the GDB source /gdb/features
|
||||
// GDB also wants the l character at the start
|
||||
@@ -130,6 +136,8 @@ static const char* target_xml =
|
||||
</flags>
|
||||
<reg name="cpsr" bitsize="32" type="cpsr_flags"/>
|
||||
</feature>
|
||||
<feature name="org.gnu.gdb.aarch64.fpu">
|
||||
</feature>
|
||||
</target>
|
||||
)";
|
||||
|
||||
@@ -144,6 +152,7 @@ static u32 latest_signal = 0;
|
||||
static bool memory_break = false;
|
||||
|
||||
static Kernel::Thread* current_thread = nullptr;
|
||||
static u32 current_core = 0;
|
||||
|
||||
// Binding to a port within the reserved ports range (0-1023) requires root permissions,
|
||||
// so default to a port outside of that range.
|
||||
@@ -171,13 +180,34 @@ static std::map<u64, Breakpoint> breakpoints_execute;
|
||||
static std::map<u64, Breakpoint> breakpoints_read;
|
||||
static std::map<u64, Breakpoint> breakpoints_write;
|
||||
|
||||
struct Module {
|
||||
std::string name;
|
||||
PAddr beg;
|
||||
PAddr end;
|
||||
};
|
||||
|
||||
static std::vector<Module> modules;
|
||||
|
||||
void RegisterModule(std::string name, PAddr beg, PAddr end, bool add_elf_ext) {
|
||||
Module module;
|
||||
if (add_elf_ext) {
|
||||
Common::SplitPath(name, nullptr, &module.name, nullptr);
|
||||
module.name += ".elf";
|
||||
} else {
|
||||
module.name = std::move(name);
|
||||
}
|
||||
module.beg = beg;
|
||||
module.end = end;
|
||||
modules.push_back(std::move(module));
|
||||
}
|
||||
|
||||
static Kernel::Thread* FindThreadById(int id) {
|
||||
for (int core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto thread : threads) {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadId() == id) {
|
||||
current_thread = thread.get();
|
||||
return current_thread;
|
||||
current_core = core;
|
||||
return thread.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,6 +227,8 @@ static u64 RegRead(int id, Kernel::Thread* thread = nullptr) {
|
||||
return thread->context.pc;
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
return thread->context.cpsr;
|
||||
} else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) {
|
||||
return thread->context.fpu_registers[id - UC_ARM64_REG_Q0][0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -215,6 +247,8 @@ static void RegWrite(int id, u64 val, Kernel::Thread* thread = nullptr) {
|
||||
thread->context.pc = val;
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
thread->context.cpsr = val;
|
||||
} else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) {
|
||||
thread->context.fpu_registers[id - (CPSR_REGISTER + 1)][0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,7 +568,11 @@ static void HandleQuery() {
|
||||
SendReply("T0");
|
||||
} else if (strncmp(query, "Supported", strlen("Supported")) == 0) {
|
||||
// PacketSize needs to be large enough for target xml
|
||||
SendReply("PacketSize=2000;qXfer:features:read+");
|
||||
std::string buffer = "PacketSize=2000;qXfer:features:read+;qXfer:threads:read+";
|
||||
if (!modules.empty()) {
|
||||
buffer += ";qXfer:libraries:read+";
|
||||
}
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "Xfer:features:read:target.xml:",
|
||||
strlen("Xfer:features:read:target.xml:")) == 0) {
|
||||
SendReply(target_xml);
|
||||
@@ -543,9 +581,9 @@ static void HandleQuery() {
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
|
||||
std::string val = "m";
|
||||
for (int core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto thread : threads) {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x}", thread->GetThreadId());
|
||||
val += ",";
|
||||
}
|
||||
@@ -554,6 +592,31 @@ static void HandleQuery() {
|
||||
SendReply(val.c_str());
|
||||
} else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) {
|
||||
SendReply("l");
|
||||
} else if (strncmp(query, "Xfer:threads:read", strlen("Xfer:threads:read")) == 0) {
|
||||
std::string buffer;
|
||||
buffer += "l<?xml version=\"1.0\"?>";
|
||||
buffer += "<threads>";
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
buffer +=
|
||||
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
|
||||
thread->GetThreadId(), core, thread->GetThreadId());
|
||||
}
|
||||
}
|
||||
buffer += "</threads>";
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "Xfer:libraries:read", strlen("Xfer:libraries:read")) == 0) {
|
||||
std::string buffer;
|
||||
buffer += "l<?xml version=\"1.0\"?>";
|
||||
buffer += "<library-list>";
|
||||
for (const auto& module : modules) {
|
||||
buffer +=
|
||||
fmt::format(R"*("<library name = "{}"><segment address = "0x{:x}"/></library>)*",
|
||||
module.name, module.beg);
|
||||
}
|
||||
buffer += "</library-list>";
|
||||
SendReply(buffer.c_str());
|
||||
} else {
|
||||
SendReply("");
|
||||
}
|
||||
@@ -561,33 +624,27 @@ static void HandleQuery() {
|
||||
|
||||
/// Handle set thread command from gdb client.
|
||||
static void HandleSetThread() {
|
||||
if (memcmp(command_buffer, "Hc", 2) == 0 || memcmp(command_buffer, "Hg", 2) == 0) {
|
||||
int thread_id = -1;
|
||||
if (command_buffer[2] != '-') {
|
||||
thread_id = static_cast<int>(HexToInt(
|
||||
command_buffer + 2,
|
||||
command_length - 2 /*strlen(reinterpret_cast<char*>(command_buffer) + 2)*/));
|
||||
}
|
||||
if (thread_id >= 1) {
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (!current_thread) {
|
||||
thread_id = 1;
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (current_thread) {
|
||||
SendReply("OK");
|
||||
return;
|
||||
}
|
||||
int thread_id = -1;
|
||||
if (command_buffer[2] != '-') {
|
||||
thread_id = static_cast<int>(HexToInt(command_buffer + 2, command_length - 2));
|
||||
}
|
||||
if (thread_id >= 1) {
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (!current_thread) {
|
||||
thread_id = 1;
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (current_thread) {
|
||||
SendReply("OK");
|
||||
return;
|
||||
}
|
||||
SendReply("E01");
|
||||
}
|
||||
|
||||
/// Handle thread alive command from gdb client.
|
||||
static void HandleThreadAlive() {
|
||||
int thread_id = static_cast<int>(
|
||||
HexToInt(command_buffer + 1,
|
||||
command_length - 1 /*strlen(reinterpret_cast<char*>(command_buffer) + 1)*/));
|
||||
int thread_id = static_cast<int>(HexToInt(command_buffer + 1, command_length - 1));
|
||||
if (thread_id == 0) {
|
||||
thread_id = 1;
|
||||
}
|
||||
@@ -610,16 +667,23 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
|
||||
|
||||
latest_signal = signal;
|
||||
|
||||
std::string buffer;
|
||||
if (full) {
|
||||
buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};", latest_signal, PC_REGISTER,
|
||||
Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER,
|
||||
Common::swap64(RegRead(SP_REGISTER, thread)));
|
||||
} else {
|
||||
buffer = fmt::format("T{:02x};", latest_signal);
|
||||
if (!thread) {
|
||||
full = false;
|
||||
}
|
||||
|
||||
buffer += fmt::format("thread:{:x};", thread->GetThreadId());
|
||||
std::string buffer;
|
||||
if (full) {
|
||||
buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};{:02x}:{:016x}", latest_signal,
|
||||
PC_REGISTER, Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER,
|
||||
Common::swap64(RegRead(SP_REGISTER, thread)), LR_REGISTER,
|
||||
Common::swap64(RegRead(LR_REGISTER, thread)));
|
||||
} else {
|
||||
buffer = fmt::format("T{:02x}", latest_signal);
|
||||
}
|
||||
|
||||
if (thread) {
|
||||
buffer += fmt::format(";thread:{:x};", thread->GetThreadId());
|
||||
}
|
||||
|
||||
SendReply(buffer.c_str());
|
||||
}
|
||||
@@ -711,8 +775,12 @@ static void ReadRegister() {
|
||||
LongToGdbHex(reply, RegRead(id, current_thread));
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
IntToGdbHex(reply, (u32)RegRead(id, current_thread));
|
||||
} else if (id >= UC_ARM64_REG_Q0 && id < FPSCR_REGISTER) {
|
||||
LongToGdbHex(reply, RegRead(id, current_thread));
|
||||
} else if (id == FPSCR_REGISTER) {
|
||||
LongToGdbHex(reply, RegRead(TODO_DUMMY_REG_998, current_thread));
|
||||
} else {
|
||||
return SendReply("E01");
|
||||
LongToGdbHex(reply, RegRead(TODO_DUMMY_REG_997, current_thread));
|
||||
}
|
||||
|
||||
SendReply(reinterpret_cast<char*>(reply));
|
||||
@@ -729,7 +797,7 @@ static void ReadRegisters() {
|
||||
LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread));
|
||||
}
|
||||
|
||||
bufptr += (32 * 16);
|
||||
bufptr += 32 * 16;
|
||||
|
||||
LongToGdbHex(bufptr, RegRead(PC_REGISTER, current_thread));
|
||||
|
||||
@@ -739,6 +807,16 @@ static void ReadRegisters() {
|
||||
|
||||
bufptr += 8;
|
||||
|
||||
for (int reg = UC_ARM64_REG_Q0; reg <= UC_ARM64_REG_Q0 + 31; reg++) {
|
||||
LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread));
|
||||
}
|
||||
|
||||
bufptr += 32 * 32;
|
||||
|
||||
LongToGdbHex(bufptr, RegRead(TODO_DUMMY_REG_998, current_thread));
|
||||
|
||||
bufptr += 8;
|
||||
|
||||
SendReply(reinterpret_cast<char*>(buffer));
|
||||
}
|
||||
|
||||
@@ -759,10 +837,17 @@ static void WriteRegister() {
|
||||
RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
RegWrite(id, GdbHexToInt(buffer_ptr), current_thread);
|
||||
} else if (id >= UC_ARM64_REG_Q0 && id < FPSCR_REGISTER) {
|
||||
RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
|
||||
} else if (id == FPSCR_REGISTER) {
|
||||
RegWrite(TODO_DUMMY_REG_998, GdbHexToLong(buffer_ptr), current_thread);
|
||||
} else {
|
||||
return SendReply("E01");
|
||||
RegWrite(TODO_DUMMY_REG_997, GdbHexToLong(buffer_ptr), current_thread);
|
||||
}
|
||||
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
|
||||
SendReply("OK");
|
||||
}
|
||||
|
||||
@@ -773,18 +858,25 @@ static void WriteRegisters() {
|
||||
if (command_buffer[0] != 'G')
|
||||
return SendReply("E01");
|
||||
|
||||
for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) {
|
||||
for (int i = 0, reg = 0; reg <= FPSCR_REGISTER; i++, reg++) {
|
||||
if (reg <= SP_REGISTER) {
|
||||
RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg == PC_REGISTER) {
|
||||
RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg == CPSR_REGISTER) {
|
||||
RegWrite(CPSR_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg >= UC_ARM64_REG_Q0 && reg < FPSCR_REGISTER) {
|
||||
RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg == FPSCR_REGISTER) {
|
||||
RegWrite(TODO_DUMMY_REG_998, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
}
|
||||
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
|
||||
SendReply("OK");
|
||||
}
|
||||
|
||||
@@ -806,6 +898,10 @@ static void ReadMemory() {
|
||||
SendReply("E01");
|
||||
}
|
||||
|
||||
if (addr < Memory::PROCESS_IMAGE_VADDR || addr >= Memory::MAP_REGION_VADDR_END) {
|
||||
return SendReply("E00");
|
||||
}
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(addr)) {
|
||||
return SendReply("E00");
|
||||
}
|
||||
@@ -840,16 +936,18 @@ static void WriteMemory() {
|
||||
}
|
||||
|
||||
void Break(bool is_memory_break) {
|
||||
if (!halt_loop) {
|
||||
halt_loop = true;
|
||||
send_trap = true;
|
||||
}
|
||||
send_trap = true;
|
||||
|
||||
memory_break = is_memory_break;
|
||||
}
|
||||
|
||||
/// Tell the CPU that it should perform a single step.
|
||||
static void Step() {
|
||||
if (command_length > 1) {
|
||||
RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
}
|
||||
step_loop = true;
|
||||
halt_loop = true;
|
||||
send_trap = true;
|
||||
@@ -1090,6 +1188,8 @@ static void Init(u16 port) {
|
||||
breakpoints_read.clear();
|
||||
breakpoints_write.clear();
|
||||
|
||||
modules.clear();
|
||||
|
||||
// Start gdb server
|
||||
LOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port);
|
||||
|
||||
@@ -1192,8 +1292,12 @@ void SetCpuStepFlag(bool is_step) {
|
||||
|
||||
void SendTrap(Kernel::Thread* thread, int trap) {
|
||||
if (send_trap) {
|
||||
if (!halt_loop || current_thread == thread) {
|
||||
current_thread = thread;
|
||||
SendSignal(thread, trap);
|
||||
}
|
||||
halt_loop = true;
|
||||
send_trap = false;
|
||||
SendSignal(thread, trap);
|
||||
}
|
||||
}
|
||||
}; // namespace GDBStub
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
@@ -51,6 +52,9 @@ bool IsServerEnabled();
|
||||
/// Returns true if there is an active socket connection.
|
||||
bool IsConnected();
|
||||
|
||||
/// Register module.
|
||||
void RegisterModule(std::string name, PAddr beg, PAddr end, bool add_elf_ext = true);
|
||||
|
||||
/**
|
||||
* Signal to the gdbstub server that it should halt CPU execution.
|
||||
*
|
||||
@@ -80,10 +84,10 @@ BreakpointAddress GetNextBreakpointFromAddress(PAddr addr, GDBStub::BreakpointTy
|
||||
*/
|
||||
bool CheckBreakpoint(PAddr addr, GDBStub::BreakpointType type);
|
||||
|
||||
// If set to true, the CPU will halt at the beginning of the next CPU loop.
|
||||
/// If set to true, the CPU will halt at the beginning of the next CPU loop.
|
||||
bool GetCpuHaltFlag();
|
||||
|
||||
// If set to true and the CPU is halted, the CPU will step one instruction.
|
||||
/// If set to true and the CPU is halted, the CPU will step one instruction.
|
||||
bool GetCpuStepFlag();
|
||||
|
||||
/**
|
||||
|
||||
@@ -115,7 +115,7 @@ ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 valu
|
||||
s32 updated_value;
|
||||
if (waiting_threads.size() == 0) {
|
||||
updated_value = value - 1;
|
||||
} else if (num_to_wake <= 0 || waiting_threads.size() <= num_to_wake) {
|
||||
} else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
|
||||
updated_value = value + 1;
|
||||
} else {
|
||||
updated_value = value;
|
||||
@@ -140,7 +140,9 @@ ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool
|
||||
|
||||
s32 cur_value = static_cast<s32>(Memory::Read32(address));
|
||||
if (cur_value < value) {
|
||||
Memory::Write32(address, static_cast<u32>(cur_value - 1));
|
||||
if (should_decrement) {
|
||||
Memory::Write32(address, static_cast<u32>(cur_value - 1));
|
||||
}
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s
|
||||
|
||||
SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
|
||||
const std::string& reason, u64 timeout,
|
||||
WakeupCallback&& callback) {
|
||||
WakeupCallback&& callback,
|
||||
Kernel::SharedPtr<Kernel::Event> event) {
|
||||
|
||||
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
|
||||
thread->wakeup_callback =
|
||||
@@ -41,7 +42,12 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
|
||||
return true;
|
||||
};
|
||||
|
||||
auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
|
||||
if (!event) {
|
||||
// Create event if not provided
|
||||
event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
|
||||
}
|
||||
|
||||
event->Clear();
|
||||
thread->status = THREADSTATUS_WAIT_HLE_EVENT;
|
||||
thread->wait_objects = {event};
|
||||
event->AddWaitingThread(thread);
|
||||
@@ -214,8 +220,8 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
|
||||
(sizeof(IPC::CommandHeader) + sizeof(IPC::HandleDescriptorHeader)) / sizeof(u32);
|
||||
ASSERT_MSG(!handle_descriptor_header->send_current_pid, "Sending PID is not implemented");
|
||||
|
||||
ASSERT_MSG(copy_objects.size() == handle_descriptor_header->num_handles_to_copy);
|
||||
ASSERT_MSG(move_objects.size() == handle_descriptor_header->num_handles_to_move);
|
||||
ASSERT(copy_objects.size() == handle_descriptor_header->num_handles_to_copy);
|
||||
ASSERT(move_objects.size() == handle_descriptor_header->num_handles_to_move);
|
||||
|
||||
// We don't make a distinction between copy and move handles when translating since HLE
|
||||
// services don't deal with handles directly. However, the guest applications might check
|
||||
|
||||
@@ -118,10 +118,12 @@ public:
|
||||
* @param callback Callback to be invoked when the thread is resumed. This callback must write
|
||||
* the entire command response once again, regardless of the state of it before this function
|
||||
* was called.
|
||||
* @param event Event to use to wake up the thread. If unspecified, an event will be created.
|
||||
* @returns Event that when signaled will resume the thread and call the callback function.
|
||||
*/
|
||||
SharedPtr<Event> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason,
|
||||
u64 timeout, WakeupCallback&& callback);
|
||||
u64 timeout, WakeupCallback&& callback,
|
||||
Kernel::SharedPtr<Kernel::Event> event = nullptr);
|
||||
|
||||
void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming);
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
cpu_core->LoadContext(new_thread->context);
|
||||
cpu_core->SetTlsAddress(new_thread->GetTLSAddress());
|
||||
cpu_core->ClearExclusiveState();
|
||||
} else {
|
||||
current_thread = nullptr;
|
||||
// Note: We do not reset the current process and current page table when idling because
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
#include <cinttypes>
|
||||
#include <stack>
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applet_ae.h"
|
||||
#include "core/hle/service/am/applet_oe.h"
|
||||
@@ -614,25 +616,14 @@ void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
|
||||
|
||||
void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
u128 uid = rp.PopRaw<u128>();
|
||||
u128 uid = rp.PopRaw<u128>(); // What does this do?
|
||||
|
||||
LOG_WARNING(Service, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
FileSys::Path unused;
|
||||
auto savedata = FileSystem::OpenFileSystem(FileSystem::Type::SaveData, unused);
|
||||
if (savedata.Failed()) {
|
||||
// Create the save data and return an error indicating that the operation was performed.
|
||||
FileSystem::FormatFileSystem(FileSystem::Type::SaveData);
|
||||
// TODO(Subv): Find out the correct error code for this.
|
||||
rb.Push(ResultCode(ErrorModule::FS, 40));
|
||||
} else {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(0);
|
||||
}
|
||||
} // namespace Service::AM
|
||||
|
||||
void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) {
|
||||
// Takes an input u32 Result, no output.
|
||||
|
||||
@@ -27,12 +27,12 @@ public:
|
||||
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
|
||||
{1, &IAudioOut::StartAudioOut, "StartAudioOut"},
|
||||
{2, &IAudioOut::StopAudioOut, "StopAudioOut"},
|
||||
{3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"},
|
||||
{3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"},
|
||||
{4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
|
||||
{5, &IAudioOut::GetReleasedAudioOutBuffer, "GetReleasedAudioOutBuffer"},
|
||||
{5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"},
|
||||
{6, nullptr, "ContainsAudioOutBuffer"},
|
||||
{7, nullptr, "AppendAudioOutBufferAuto"},
|
||||
{8, nullptr, "GetReleasedAudioOutBufferAuto"},
|
||||
{7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},
|
||||
{8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
|
||||
{9, nullptr, "GetAudioOutBufferCount"},
|
||||
{10, nullptr, "GetAudioOutPlayedSampleCount"},
|
||||
{11, nullptr, "FlushAudioOutBuffers"},
|
||||
@@ -96,7 +96,7 @@ private:
|
||||
rb.PushCopyObjects(buffer_event);
|
||||
}
|
||||
|
||||
void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) {
|
||||
void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
@@ -107,7 +107,7 @@ private:
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetReleasedAudioOutBuffer(Kernel::HLERequestContext& ctx) {
|
||||
void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
|
||||
// TODO(st4rk): This is how libtransistor currently implements the
|
||||
@@ -163,7 +163,7 @@ private:
|
||||
AudioState audio_out_state;
|
||||
};
|
||||
|
||||
void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
|
||||
void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
@@ -179,7 +179,7 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(1);
|
||||
}
|
||||
|
||||
void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
|
||||
void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
|
||||
if (!audio_out_interface) {
|
||||
@@ -196,10 +196,10 @@ void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
AudOutU::AudOutU() : ServiceFramework("audout:u") {
|
||||
static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOuts, "ListAudioOuts"},
|
||||
{1, &AudOutU::OpenAudioOut, "OpenAudioOut"},
|
||||
{2, nullptr, "ListAudioOutsAuto"},
|
||||
{3, nullptr, "OpenAudioOutAuto"}};
|
||||
static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"},
|
||||
{1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"},
|
||||
{2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
|
||||
{3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ public:
|
||||
private:
|
||||
std::shared_ptr<IAudioOut> audio_out_interface;
|
||||
|
||||
void ListAudioOuts(Kernel::HLERequestContext& ctx);
|
||||
void OpenAudioOut(Kernel::HLERequestContext& ctx);
|
||||
void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
|
||||
void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
|
||||
|
||||
enum class PcmFormat : u32 {
|
||||
Invalid = 0,
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
|
||||
// Start the audio event
|
||||
CoreTiming::ScheduleEvent(audio_ticks, audio_event);
|
||||
voice_status_list.reserve(worker_params.voice_count);
|
||||
voice_status_list.resize(worker_params.voice_count);
|
||||
}
|
||||
~IAudioRenderer() {
|
||||
CoreTiming::UnscheduleEvent(audio_event, 0);
|
||||
@@ -87,8 +87,6 @@ private:
|
||||
memory_pool[i].state = MemoryPoolStates::Attached;
|
||||
else if (mem_pool_info[i].pool_state == MemoryPoolStates::RequestDetach)
|
||||
memory_pool[i].state = MemoryPoolStates::Detached;
|
||||
else
|
||||
memory_pool[i].state = mem_pool_info[i].pool_state;
|
||||
}
|
||||
std::memcpy(output.data() + sizeof(UpdateDataHeader), memory_pool.data(),
|
||||
response_data.memory_pools_size);
|
||||
@@ -183,7 +181,9 @@ private:
|
||||
behavior_size = 0xb0;
|
||||
memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
|
||||
voices_size = config.voice_count * 0x10;
|
||||
voice_resource_size = 0x0;
|
||||
effects_size = config.effect_count * 0x10;
|
||||
mixes_size = 0x0;
|
||||
sinks_size = config.sink_count * 0x20;
|
||||
performance_manager_size = 0x10;
|
||||
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size +
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
@@ -16,57 +17,77 @@ namespace Service::FileSystem {
|
||||
* Map of registered file systems, identified by type. Once an file system is registered here, it
|
||||
* is never removed until UnregisterFileSystems is called.
|
||||
*/
|
||||
static boost::container::flat_map<Type, std::unique_ptr<FileSys::FileSystemFactory>> filesystem_map;
|
||||
static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
|
||||
static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
|
||||
static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
|
||||
|
||||
ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type) {
|
||||
auto result = filesystem_map.emplace(type, std::move(factory));
|
||||
|
||||
bool inserted = result.second;
|
||||
ASSERT_MSG(inserted, "Tried to register more than one system with same id code");
|
||||
|
||||
auto& filesystem = result.first->second;
|
||||
LOG_DEBUG(Service_FS, "Registered file system {} with id code 0x{:08X}", filesystem->GetName(),
|
||||
static_cast<u32>(type));
|
||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
|
||||
ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
|
||||
romfs_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registered RomFS");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
|
||||
FileSys::Path& path) {
|
||||
LOG_TRACE(Service_FS, "Opening FileSystem with type={}", static_cast<u32>(type));
|
||||
|
||||
auto itr = filesystem_map.find(type);
|
||||
if (itr == filesystem_map.end()) {
|
||||
// TODO(bunnei): Find a better error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return itr->second->Open(path);
|
||||
ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory) {
|
||||
ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second save data");
|
||||
save_data_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registered save data");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode FormatFileSystem(Type type) {
|
||||
LOG_TRACE(Service_FS, "Formatting FileSystem with type={}", static_cast<u32>(type));
|
||||
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
|
||||
ASSERT_MSG(sdmc_factory == nullptr, "Tried to register a second SDMC");
|
||||
sdmc_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registered SDMC");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
auto itr = filesystem_map.find(type);
|
||||
if (itr == filesystem_map.end()) {
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenRomFS(u64 title_id) {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id);
|
||||
|
||||
if (romfs_factory == nullptr) {
|
||||
// TODO(bunnei): Find a better error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
FileSys::Path unused;
|
||||
return itr->second->Format(unused);
|
||||
return romfs_factory->Open(title_id);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSaveData(
|
||||
FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct) {
|
||||
LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}",
|
||||
static_cast<u8>(space), SaveStructDebugInfo(save_struct));
|
||||
|
||||
if (save_data_factory == nullptr) {
|
||||
return ResultCode(ErrorModule::FS, FileSys::ErrCodes::SaveDataNotFound);
|
||||
}
|
||||
|
||||
return save_data_factory->Open(space, save_struct);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSDMC() {
|
||||
LOG_TRACE(Service_FS, "Opening SDMC");
|
||||
|
||||
if (sdmc_factory == nullptr) {
|
||||
return ResultCode(ErrorModule::FS, FileSys::ErrCodes::SdCardNotFound);
|
||||
}
|
||||
|
||||
return sdmc_factory->Open();
|
||||
}
|
||||
|
||||
void RegisterFileSystems() {
|
||||
filesystem_map.clear();
|
||||
romfs_factory = nullptr;
|
||||
save_data_factory = nullptr;
|
||||
sdmc_factory = nullptr;
|
||||
|
||||
std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX);
|
||||
std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX);
|
||||
|
||||
auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory));
|
||||
RegisterFileSystem(std::move(savedata), Type::SaveData);
|
||||
auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
|
||||
save_data_factory = std::move(savedata);
|
||||
|
||||
auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory));
|
||||
RegisterFileSystem(std::move(sdcard), Type::SDMC);
|
||||
auto sdcard = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
|
||||
sdmc_factory = std::move(sdcard);
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
class FileSystemBackend;
|
||||
class FileSystemFactory;
|
||||
class Path;
|
||||
} // namespace FileSys
|
||||
|
||||
namespace Service {
|
||||
@@ -22,35 +23,20 @@ class ServiceManager;
|
||||
|
||||
namespace FileSystem {
|
||||
|
||||
/// Supported FileSystem types
|
||||
enum class Type {
|
||||
RomFS = 1,
|
||||
SaveData = 2,
|
||||
SDMC = 3,
|
||||
};
|
||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
|
||||
ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
|
||||
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
|
||||
|
||||
/**
|
||||
* Registers a FileSystem, instances of which can later be opened using its IdCode.
|
||||
* @param factory FileSystem backend interface to use
|
||||
* @param type Type used to access this type of FileSystem
|
||||
*/
|
||||
ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type);
|
||||
// TODO(DarkLordZach): BIS Filesystem
|
||||
// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
|
||||
|
||||
/**
|
||||
* Opens a file system
|
||||
* @param type Type of the file system to open
|
||||
* @param path Path to the file system, used with Binary paths
|
||||
* @return FileSys::FileSystemBackend interface to the file system
|
||||
*/
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
|
||||
FileSys::Path& path);
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenRomFS(u64 title_id);
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSaveData(
|
||||
FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct);
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSDMC();
|
||||
|
||||
/**
|
||||
* Formats a file system
|
||||
* @param type Type of the file system to format
|
||||
* @return ResultCode of the operation
|
||||
*/
|
||||
ResultCode FormatFileSystem(Type type);
|
||||
// TODO(DarkLordZach): BIS Filesystem
|
||||
// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
|
||||
|
||||
/// Registers all Filesystem services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
@@ -7,16 +7,27 @@
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/storage.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/filesystem/fsp_srv.h"
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
enum class StorageId : u8 {
|
||||
None = 0,
|
||||
Host = 1,
|
||||
GameCard = 2,
|
||||
NandSystem = 3,
|
||||
NandUser = 4,
|
||||
SdCard = 5
|
||||
};
|
||||
|
||||
class IStorage final : public ServiceFramework<IStorage> {
|
||||
public:
|
||||
IStorage(std::unique_ptr<FileSys::StorageBackend>&& backend)
|
||||
@@ -486,17 +497,6 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
void FSP_SRV::TryLoadRomFS() {
|
||||
if (romfs) {
|
||||
return;
|
||||
}
|
||||
FileSys::Path unused;
|
||||
auto res = OpenFileSystem(Type::RomFS, unused);
|
||||
if (res.Succeeded()) {
|
||||
romfs = std::move(res.Unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
|
||||
@@ -507,8 +507,7 @@ void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
FileSys::Path unused;
|
||||
auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap();
|
||||
IFileSystem filesystem(OpenSDMC().Unwrap());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -518,25 +517,36 @@ void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) {
|
||||
void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto save_struct = rp.PopRaw<std::array<u8, 0x40>>();
|
||||
auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
|
||||
auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
|
||||
u128 uid = rp.PopRaw<u128>();
|
||||
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]);
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called save_struct = {}, uid = {:016X}{:016X}",
|
||||
save_struct.DebugInfo(), uid[1], uid[0]);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
FileSys::Path unused;
|
||||
auto filesystem = OpenFileSystem(Type::SaveData, unused).Unwrap();
|
||||
auto space_id = rp.PopRaw<FileSys::SaveDataSpaceId>();
|
||||
auto unk = rp.Pop<u32>();
|
||||
LOG_INFO(Service_FS, "called with unknown={:08X}", unk);
|
||||
auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
|
||||
|
||||
auto filesystem = OpenSaveData(space_id, save_struct);
|
||||
|
||||
if (filesystem.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
|
||||
rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::SaveDataNotFound));
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
|
||||
rb.PushIpcInterface<IFileSystem>(std::move(filesystem.Unwrap()));
|
||||
}
|
||||
|
||||
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
@@ -550,8 +560,8 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
TryLoadRomFS();
|
||||
if (!romfs) {
|
||||
auto romfs = OpenRomFS(Core::System::GetInstance().CurrentProcess()->program_id);
|
||||
if (romfs.Failed()) {
|
||||
// TODO (bunnei): Find the right error code to use here
|
||||
LOG_CRITICAL(Service_FS, "no file system interface available!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
@@ -559,8 +569,8 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to open a StorageBackend interface to the RomFS
|
||||
auto storage = romfs->OpenFile({}, {});
|
||||
auto storage = romfs.Unwrap()->OpenFile({}, {});
|
||||
|
||||
if (storage.Failed()) {
|
||||
LOG_CRITICAL(Service_FS, "no storage interface available!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
@@ -574,8 +584,40 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called, using OpenDataStorageByCurrentProcess");
|
||||
OpenDataStorageByCurrentProcess(ctx);
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto storage_id = rp.PopRaw<StorageId>();
|
||||
auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}",
|
||||
static_cast<u8>(storage_id), title_id);
|
||||
if (title_id != Core::System::GetInstance().CurrentProcess()->program_id) {
|
||||
LOG_CRITICAL(
|
||||
Service_FS,
|
||||
"Attempting to access RomFS of another title id (current={:016X}, requested={:016X}).",
|
||||
Core::System::GetInstance().CurrentProcess()->program_id, title_id);
|
||||
}
|
||||
|
||||
auto romfs = OpenRomFS(title_id);
|
||||
if (romfs.Failed()) {
|
||||
LOG_CRITICAL(Service_FS, "no file system interface available!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::RomFSNotFound));
|
||||
return;
|
||||
}
|
||||
|
||||
auto storage = romfs.Unwrap()->OpenFile({}, {});
|
||||
|
||||
if (storage.Failed()) {
|
||||
LOG_CRITICAL(Service_FS, "no storage interface available!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(storage.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap()));
|
||||
}
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -19,8 +19,6 @@ public:
|
||||
~FSP_SRV() = default;
|
||||
|
||||
private:
|
||||
void TryLoadRomFS();
|
||||
|
||||
void Initialize(Kernel::HLERequestContext& ctx);
|
||||
void MountSdCard(Kernel::HLERequestContext& ctx);
|
||||
void CreateSaveData(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -18,9 +18,9 @@ namespace Service::HID {
|
||||
|
||||
// Updating period for each HID device.
|
||||
// TODO(shinyquagsire23): These need better values.
|
||||
constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000;
|
||||
constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000;
|
||||
constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000;
|
||||
constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
|
||||
constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
|
||||
constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
|
||||
|
||||
class IAppletResource final : public ServiceFramework<IAppletResource> {
|
||||
public:
|
||||
@@ -75,7 +75,7 @@ private:
|
||||
|
||||
// Set up controllers as neon red+blue Joy-Con attached to console
|
||||
ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header;
|
||||
controller_header.type = ControllerType_Handheld | ControllerType_JoyconPair;
|
||||
controller_header.type = ControllerType_Handheld;
|
||||
controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent;
|
||||
controller_header.right_color_body = JOYCON_BODY_NEON_RED;
|
||||
controller_header.right_color_buttons = JOYCON_BUTTONS_NEON_RED;
|
||||
@@ -84,23 +84,21 @@ private:
|
||||
|
||||
for (size_t controller = 0; controller < mem.controllers.size(); controller++) {
|
||||
for (int index = 0; index < HID_NUM_LAYOUTS; index++) {
|
||||
// TODO(DarkLordZach): Is this layout/controller config actually invalid?
|
||||
if (controller == Controller_Handheld && index == Layout_Single)
|
||||
continue;
|
||||
|
||||
ControllerLayout& layout = mem.controllers[controller].layouts[index];
|
||||
layout.header.num_entries = HID_NUM_ENTRIES;
|
||||
layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
|
||||
|
||||
// HID shared memory stores the state of the past 17 samples in a circlular buffer,
|
||||
// each with a timestamp in number of samples since boot.
|
||||
const ControllerInputEntry& last_entry = layout.entries[layout.header.latest_entry];
|
||||
|
||||
layout.header.timestamp_ticks = CoreTiming::GetTicks();
|
||||
layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
|
||||
|
||||
ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
|
||||
entry.timestamp++;
|
||||
entry.timestamp = last_entry.timestamp + 1;
|
||||
// TODO(shinyquagsire23): Is this always identical to timestamp?
|
||||
entry.timestamp_2++;
|
||||
entry.timestamp_2 = entry.timestamp;
|
||||
|
||||
// TODO(shinyquagsire23): More than just handheld input
|
||||
if (controller != Controller_Handheld)
|
||||
|
||||
@@ -148,6 +148,24 @@ private:
|
||||
|
||||
LOG_DEBUG(Service_NIFM, "called");
|
||||
}
|
||||
void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
}
|
||||
void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
}
|
||||
void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
}
|
||||
};
|
||||
|
||||
IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
|
||||
@@ -167,11 +185,11 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
|
||||
{14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
|
||||
{15, nullptr, "GetCurrentIpConfigInfo"},
|
||||
{16, nullptr, "SetWirelessCommunicationEnabled"},
|
||||
{17, nullptr, "IsWirelessCommunicationEnabled"},
|
||||
{17, &IGeneralService::IsWirelessCommunicationEnabled, "IsWirelessCommunicationEnabled"},
|
||||
{18, nullptr, "GetInternetConnectionStatus"},
|
||||
{19, nullptr, "SetEthernetCommunicationEnabled"},
|
||||
{20, nullptr, "IsEthernetCommunicationEnabled"},
|
||||
{21, nullptr, "IsAnyInternetRequestAccepted"},
|
||||
{20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
|
||||
{21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
|
||||
{22, nullptr, "IsAnyForegroundRequestAccepted"},
|
||||
{23, nullptr, "PutToSleep"},
|
||||
{24, nullptr, "WakeUp"},
|
||||
|
||||
@@ -18,7 +18,8 @@ u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector
|
||||
}
|
||||
|
||||
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height,
|
||||
u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform) {
|
||||
u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform,
|
||||
const MathUtil::Rectangle<int>& crop_rect) {
|
||||
VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
|
||||
LOG_WARNING(Service,
|
||||
"Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
|
||||
@@ -26,7 +27,8 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3
|
||||
|
||||
using PixelFormat = Tegra::FramebufferConfig::PixelFormat;
|
||||
const Tegra::FramebufferConfig framebuffer{
|
||||
addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform};
|
||||
addr, offset, width, height, stride, static_cast<PixelFormat>(format),
|
||||
transform, crop_rect};
|
||||
|
||||
Core::System::GetInstance().perf_stats.EndGameFrame();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||
|
||||
@@ -23,7 +24,8 @@ public:
|
||||
|
||||
/// Performs a screen flip, drawing the buffer pointed to by the handle.
|
||||
void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,
|
||||
NVFlinger::BufferQueue::BufferTransformFlags transform);
|
||||
NVFlinger::BufferQueue::BufferTransformFlags transform,
|
||||
const MathUtil::Rectangle<int>& crop_rect);
|
||||
|
||||
private:
|
||||
std::shared_ptr<nvmap> nvmap_dev;
|
||||
|
||||
@@ -29,24 +29,9 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<
|
||||
u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IocGetConfigParams params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||
LOG_DEBUG(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(),
|
||||
LOG_TRACE(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(),
|
||||
params.param_str.data());
|
||||
|
||||
if (!strcmp(params.domain_str.data(), "nv")) {
|
||||
if (!strcmp(params.param_str.data(), "NV_MEMORY_PROFILER")) {
|
||||
params.config_str[0] = '0';
|
||||
} else if (!strcmp(params.param_str.data(), "NVN_THROUGH_OPENGL")) {
|
||||
params.config_str[0] = '0';
|
||||
} else if (!strcmp(params.param_str.data(), "NVRM_GPU_PREVENT_USE")) {
|
||||
params.config_str[0] = '0';
|
||||
} else {
|
||||
params.config_str[0] = '\0';
|
||||
}
|
||||
} else {
|
||||
UNIMPLEMENTED(); // unknown domain? Only nv has been seen so far on hardware
|
||||
}
|
||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||
return 0;
|
||||
return 0x30006; // Returns error on production mode
|
||||
}
|
||||
|
||||
u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Service {
|
||||
namespace NVFlinger {
|
||||
|
||||
BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
|
||||
native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle");
|
||||
native_handle->Signal();
|
||||
buffer_wait_event =
|
||||
Kernel::Event::Create(Kernel::ResetType::Sticky, "BufferQueue NativeHandle");
|
||||
}
|
||||
|
||||
void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) {
|
||||
@@ -26,10 +26,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) {
|
||||
LOG_WARNING(Service, "Adding graphics buffer {}", slot);
|
||||
|
||||
queue.emplace_back(buffer);
|
||||
|
||||
if (buffer_wait_event) {
|
||||
buffer_wait_event->Signal();
|
||||
}
|
||||
buffer_wait_event->Signal();
|
||||
}
|
||||
|
||||
boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
|
||||
@@ -48,8 +45,6 @@ boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
buffer_wait_event = nullptr;
|
||||
|
||||
itr->status = Buffer::Status::Dequeued;
|
||||
return itr->slot;
|
||||
}
|
||||
@@ -62,13 +57,15 @@ const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
|
||||
return itr->igbp_buffer;
|
||||
}
|
||||
|
||||
void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform) {
|
||||
void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
|
||||
const MathUtil::Rectangle<int>& crop_rect) {
|
||||
auto itr = std::find_if(queue.begin(), queue.end(),
|
||||
[&](const Buffer& buffer) { return buffer.slot == slot; });
|
||||
ASSERT(itr != queue.end());
|
||||
ASSERT(itr->status == Buffer::Status::Dequeued);
|
||||
itr->status = Buffer::Status::Queued;
|
||||
itr->transform = transform;
|
||||
itr->crop_rect = crop_rect;
|
||||
}
|
||||
|
||||
boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() {
|
||||
@@ -88,9 +85,7 @@ void BufferQueue::ReleaseBuffer(u32 slot) {
|
||||
ASSERT(itr->status == Buffer::Status::Acquired);
|
||||
itr->status = Buffer::Status::Free;
|
||||
|
||||
if (buffer_wait_event) {
|
||||
buffer_wait_event->Signal();
|
||||
}
|
||||
buffer_wait_event->Signal();
|
||||
}
|
||||
|
||||
u32 BufferQueue::Query(QueryType type) {
|
||||
@@ -106,10 +101,5 @@ u32 BufferQueue::Query(QueryType type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BufferQueue::SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event) {
|
||||
ASSERT_MSG(!buffer_wait_event, "buffer_wait_event only supports a single waiting thread!");
|
||||
buffer_wait_event = std::move(wait_event);
|
||||
}
|
||||
|
||||
} // namespace NVFlinger
|
||||
} // namespace Service
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/math_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
|
||||
@@ -68,23 +69,24 @@ public:
|
||||
Status status = Status::Free;
|
||||
IGBPBuffer igbp_buffer;
|
||||
BufferTransformFlags transform;
|
||||
MathUtil::Rectangle<int> crop_rect;
|
||||
};
|
||||
|
||||
void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer);
|
||||
boost::optional<u32> DequeueBuffer(u32 width, u32 height);
|
||||
const IGBPBuffer& RequestBuffer(u32 slot) const;
|
||||
void QueueBuffer(u32 slot, BufferTransformFlags transform);
|
||||
void QueueBuffer(u32 slot, BufferTransformFlags transform,
|
||||
const MathUtil::Rectangle<int>& crop_rect);
|
||||
boost::optional<const Buffer&> AcquireBuffer();
|
||||
void ReleaseBuffer(u32 slot);
|
||||
u32 Query(QueryType type);
|
||||
void SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event);
|
||||
|
||||
u32 GetId() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Event> GetNativeHandle() const {
|
||||
return native_handle;
|
||||
Kernel::SharedPtr<Kernel::Event> GetBufferWaitEvent() const {
|
||||
return buffer_wait_event;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -92,9 +94,6 @@ private:
|
||||
u64 layer_id;
|
||||
|
||||
std::vector<Buffer> queue;
|
||||
Kernel::SharedPtr<Kernel::Event> native_handle;
|
||||
|
||||
/// Used to signal waiting thread when no buffers are available
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_wait_event;
|
||||
};
|
||||
|
||||
|
||||
@@ -149,12 +149,10 @@ void NVFlinger::Compose() {
|
||||
ASSERT(nvdisp);
|
||||
|
||||
nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format,
|
||||
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform);
|
||||
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform,
|
||||
buffer->crop_rect);
|
||||
|
||||
buffer_queue->ReleaseBuffer(buffer->slot);
|
||||
|
||||
// TODO(Subv): Figure out when we should actually signal this event.
|
||||
buffer_queue->GetNativeHandle()->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,9 @@ void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
|
||||
void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::Socket(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/alignment.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
@@ -27,8 +28,8 @@ struct DisplayInfo {
|
||||
char display_name[0x40]{"Default"};
|
||||
u64 unknown_1{1};
|
||||
u64 unknown_2{1};
|
||||
u64 width{1920};
|
||||
u64 height{1080};
|
||||
u64 width{1280};
|
||||
u64 height{720};
|
||||
};
|
||||
static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
|
||||
|
||||
@@ -327,8 +328,8 @@ public:
|
||||
|
||||
protected:
|
||||
void SerializeData() override {
|
||||
// TODO(Subv): Figure out what this value means, writing non-zero here will make libnx try
|
||||
// to read an IGBPBuffer object from the parcel.
|
||||
// TODO(Subv): Figure out what this value means, writing non-zero here will make libnx
|
||||
// try to read an IGBPBuffer object from the parcel.
|
||||
Write<u32_le>(1);
|
||||
WriteObject(buffer);
|
||||
Write<u32_le>(0);
|
||||
@@ -360,8 +361,8 @@ public:
|
||||
INSERT_PADDING_WORDS(3);
|
||||
u32_le timestamp;
|
||||
s32_le is_auto_timestamp;
|
||||
s32_le crop_left;
|
||||
s32_le crop_top;
|
||||
s32_le crop_left;
|
||||
s32_le crop_right;
|
||||
s32_le crop_bottom;
|
||||
s32_le scaling_mode;
|
||||
@@ -370,6 +371,10 @@ public:
|
||||
INSERT_PADDING_WORDS(2);
|
||||
u32_le fence_is_valid;
|
||||
std::array<Fence, 2> fences;
|
||||
|
||||
MathUtil::Rectangle<int> GetCropRect() const {
|
||||
return {crop_left, crop_top, crop_right, crop_bottom};
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Data) == 80, "ParcelData has wrong size");
|
||||
|
||||
@@ -495,7 +500,7 @@ private:
|
||||
ctx.WriteBuffer(response.Serialize());
|
||||
} else {
|
||||
// Wait the current thread until a buffer becomes available
|
||||
auto wait_event = ctx.SleepClientThread(
|
||||
ctx.SleepClientThread(
|
||||
Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1,
|
||||
[=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
|
||||
ThreadWakeupReason reason) {
|
||||
@@ -506,8 +511,8 @@ private:
|
||||
ctx.WriteBuffer(response.Serialize());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
});
|
||||
buffer_queue->SetBufferWaitEvent(std::move(wait_event));
|
||||
},
|
||||
buffer_queue->GetBufferWaitEvent());
|
||||
}
|
||||
} else if (transaction == TransactionId::RequestBuffer) {
|
||||
IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()};
|
||||
@@ -519,7 +524,8 @@ private:
|
||||
} else if (transaction == TransactionId::QueueBuffer) {
|
||||
IGBPQueueBufferRequestParcel request{ctx.ReadBuffer()};
|
||||
|
||||
buffer_queue->QueueBuffer(request.data.slot, request.data.transform);
|
||||
buffer_queue->QueueBuffer(request.data.slot, request.data.transform,
|
||||
request.data.GetCropRect());
|
||||
|
||||
IGBPQueueBufferResponseParcel response{1280, 720};
|
||||
ctx.WriteBuffer(response.Serialize());
|
||||
@@ -532,7 +538,7 @@ private:
|
||||
IGBPQueryResponseParcel response{value};
|
||||
ctx.WriteBuffer(response.Serialize());
|
||||
} else if (transaction == TransactionId::CancelBuffer) {
|
||||
LOG_WARNING(Service_VI, "(STUBBED) called, transaction=CancelBuffer");
|
||||
LOG_CRITICAL(Service_VI, "(STUBBED) called, transaction=CancelBuffer");
|
||||
} else {
|
||||
ASSERT_MSG(false, "Unimplemented");
|
||||
}
|
||||
@@ -565,7 +571,7 @@ private:
|
||||
LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown);
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(buffer_queue->GetNativeHandle());
|
||||
rb.PushCopyObjects(buffer_queue->GetBufferWaitEvent());
|
||||
}
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
@@ -133,6 +134,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
next_load_addr = AppLoader_NSO::LoadModule(path, load_addr);
|
||||
if (next_load_addr) {
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
|
||||
} else {
|
||||
next_load_addr = load_addr;
|
||||
}
|
||||
@@ -151,8 +154,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
|
||||
// Register the RomFS if a ".romfs" file was found
|
||||
if (!filepath_romfs.empty()) {
|
||||
Service::FileSystem::RegisterFileSystem(std::make_unique<FileSys::RomFS_Factory>(*this),
|
||||
Service::FileSystem::Type::RomFS);
|
||||
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
|
||||
}
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
@@ -259,6 +261,8 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
next_load_addr = AppLoader_NSO::LoadModule(module, nca->GetExeFsFile(module), load_addr);
|
||||
if (next_load_addr) {
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
|
||||
} else {
|
||||
next_load_addr = load_addr;
|
||||
}
|
||||
@@ -273,8 +277,7 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
metadata.GetMainThreadStackSize());
|
||||
|
||||
if (nca->GetRomFsSize() > 0)
|
||||
Service::FileSystem::RegisterFileSystem(std::make_unique<FileSys::RomFS_Factory>(*this),
|
||||
Service::FileSystem::Type::RomFS);
|
||||
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
|
||||
|
||||
is_loaded = true;
|
||||
return ResultStatus::Success;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/loader/nro.h"
|
||||
@@ -115,6 +116,9 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) {
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(codeset, load_base);
|
||||
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(codeset->name, load_base, load_base);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/loader/nso.h"
|
||||
@@ -114,7 +115,7 @@ VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>&
|
||||
std::vector<u8> program_image;
|
||||
for (int i = 0; i < nso_header.segments.size(); ++i) {
|
||||
std::vector<u8> compressed_data(nso_header.segments_compressed_size[i]);
|
||||
for (int j = 0; j < nso_header.segments_compressed_size[i]; ++j)
|
||||
for (auto j = 0; j < nso_header.segments_compressed_size[i]; ++j)
|
||||
compressed_data[j] = file_data[nso_header.segments[i].offset + j];
|
||||
std::vector<u8> data = DecompressSegment(compressed_data, nso_header.segments[i]);
|
||||
program_image.resize(nso_header.segments[i].location);
|
||||
@@ -147,6 +148,9 @@ VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>&
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(codeset, load_base);
|
||||
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(codeset->name, load_base, load_base);
|
||||
|
||||
return load_base + image_size;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
|
||||
DrawArrays();
|
||||
break;
|
||||
}
|
||||
case MAXWELL3D_REG_INDEX(clear_buffers): {
|
||||
ProcessClearBuffers();
|
||||
break;
|
||||
}
|
||||
case MAXWELL3D_REG_INDEX(query.query_get): {
|
||||
ProcessQueryGet();
|
||||
break;
|
||||
@@ -394,25 +398,12 @@ u32 Maxwell3D::GetRegisterValue(u32 method) const {
|
||||
return regs.reg_array[method];
|
||||
}
|
||||
|
||||
bool Maxwell3D::IsShaderStageEnabled(Regs::ShaderStage stage) const {
|
||||
// The Vertex stage is always enabled.
|
||||
if (stage == Regs::ShaderStage::Vertex)
|
||||
return true;
|
||||
void Maxwell3D::ProcessClearBuffers() {
|
||||
ASSERT(regs.clear_buffers.R == regs.clear_buffers.G &&
|
||||
regs.clear_buffers.R == regs.clear_buffers.B &&
|
||||
regs.clear_buffers.R == regs.clear_buffers.A);
|
||||
|
||||
switch (stage) {
|
||||
case Regs::ShaderStage::TesselationControl:
|
||||
return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::TesselationControl)]
|
||||
.enable != 0;
|
||||
case Regs::ShaderStage::TesselationEval:
|
||||
return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::TesselationEval)]
|
||||
.enable != 0;
|
||||
case Regs::ShaderStage::Geometry:
|
||||
return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::Geometry)].enable != 0;
|
||||
case Regs::ShaderStage::Fragment:
|
||||
return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::Fragment)].enable != 0;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
VideoCore::g_renderer->Rasterizer()->Clear();
|
||||
}
|
||||
|
||||
} // namespace Engines
|
||||
|
||||
@@ -281,14 +281,26 @@ public:
|
||||
};
|
||||
|
||||
enum class ComparisonOp : u32 {
|
||||
Never = 0,
|
||||
Less = 1,
|
||||
Equal = 2,
|
||||
LessEqual = 3,
|
||||
Greater = 4,
|
||||
NotEqual = 5,
|
||||
GreaterEqual = 6,
|
||||
Always = 7,
|
||||
// These values are used by Nouveau and most games, they correspond to the OpenGL token
|
||||
// values for these operations.
|
||||
Never = 0x200,
|
||||
Less = 0x201,
|
||||
Equal = 0x202,
|
||||
LessEqual = 0x203,
|
||||
Greater = 0x204,
|
||||
NotEqual = 0x205,
|
||||
GreaterEqual = 0x206,
|
||||
Always = 0x207,
|
||||
|
||||
// These values are used by some games, they seem to be NV04 values.
|
||||
NeverOld = 1,
|
||||
LessOld = 2,
|
||||
EqualOld = 3,
|
||||
LessEqualOld = 4,
|
||||
GreaterOld = 5,
|
||||
NotEqualOld = 6,
|
||||
GreaterEqualOld = 7,
|
||||
AlwaysOld = 8,
|
||||
};
|
||||
|
||||
struct Cull {
|
||||
@@ -367,6 +379,14 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
bool IsShaderConfigEnabled(size_t index) const {
|
||||
// The VertexB is always enabled.
|
||||
if (index == static_cast<size_t>(Regs::ShaderProgram::VertexB)) {
|
||||
return true;
|
||||
}
|
||||
return shader_config[index].enable != 0;
|
||||
}
|
||||
|
||||
union {
|
||||
struct {
|
||||
INSERT_PADDING_WORDS(0x45);
|
||||
@@ -436,7 +456,12 @@ public:
|
||||
u32 count;
|
||||
} vertex_buffer;
|
||||
|
||||
INSERT_PADDING_WORDS(0x99);
|
||||
INSERT_PADDING_WORDS(1);
|
||||
|
||||
float clear_color[4];
|
||||
float clear_depth;
|
||||
|
||||
INSERT_PADDING_WORDS(0x93);
|
||||
|
||||
struct {
|
||||
u32 address_high;
|
||||
@@ -473,9 +498,11 @@ public:
|
||||
|
||||
u32 depth_write_enabled;
|
||||
|
||||
INSERT_PADDING_WORDS(0x8);
|
||||
INSERT_PADDING_WORDS(0x7);
|
||||
|
||||
BitField<0, 3, ComparisonOp> depth_test_func;
|
||||
u32 d3d_cull_mode;
|
||||
|
||||
ComparisonOp depth_test_func;
|
||||
|
||||
INSERT_PADDING_WORDS(0xB);
|
||||
|
||||
@@ -493,7 +520,24 @@ public:
|
||||
u32 enable[NumRenderTargets];
|
||||
} blend;
|
||||
|
||||
INSERT_PADDING_WORDS(0x2D);
|
||||
struct {
|
||||
u32 enable;
|
||||
u32 front_op_fail;
|
||||
u32 front_op_zfail;
|
||||
u32 front_op_zpass;
|
||||
u32 front_func_func;
|
||||
u32 front_func_ref;
|
||||
u32 front_func_mask;
|
||||
u32 front_mask;
|
||||
} stencil;
|
||||
|
||||
INSERT_PADDING_WORDS(0x3);
|
||||
|
||||
union {
|
||||
BitField<4, 1, u32> triangle_rast_flip;
|
||||
} screen_y_control;
|
||||
|
||||
INSERT_PADDING_WORDS(0x21);
|
||||
|
||||
u32 vb_element_base;
|
||||
|
||||
@@ -523,7 +567,22 @@ public:
|
||||
}
|
||||
} tic;
|
||||
|
||||
INSERT_PADDING_WORDS(0x22);
|
||||
INSERT_PADDING_WORDS(0x5);
|
||||
|
||||
struct {
|
||||
u32 enable;
|
||||
u32 back_op_fail;
|
||||
u32 back_op_zfail;
|
||||
u32 back_op_zpass;
|
||||
u32 back_func_func;
|
||||
} stencil_two_side;
|
||||
|
||||
INSERT_PADDING_WORDS(0x17);
|
||||
|
||||
union {
|
||||
BitField<2, 1, u32> coord_origin;
|
||||
BitField<3, 10, u32> enable;
|
||||
} point_coord_replace;
|
||||
|
||||
struct {
|
||||
u32 code_address_high;
|
||||
@@ -584,7 +643,21 @@ public:
|
||||
|
||||
Cull cull;
|
||||
|
||||
INSERT_PADDING_WORDS(0x77);
|
||||
INSERT_PADDING_WORDS(0x2B);
|
||||
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<0, 1, u32> Z;
|
||||
BitField<1, 1, u32> S;
|
||||
BitField<2, 1, u32> R;
|
||||
BitField<3, 1, u32> G;
|
||||
BitField<4, 1, u32> B;
|
||||
BitField<5, 1, u32> A;
|
||||
BitField<6, 4, u32> RT;
|
||||
BitField<10, 11, u32> layer;
|
||||
} clear_buffers;
|
||||
|
||||
INSERT_PADDING_WORDS(0x4B);
|
||||
|
||||
struct {
|
||||
u32 query_address_high;
|
||||
@@ -736,9 +809,6 @@ public:
|
||||
/// Returns the texture information for a specific texture in a specific shader stage.
|
||||
Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, size_t offset) const;
|
||||
|
||||
/// Returns whether the specified shader stage is enabled or not.
|
||||
bool IsShaderStageEnabled(Regs::ShaderStage stage) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<u32, std::vector<u32>> uploaded_macros;
|
||||
|
||||
@@ -766,6 +836,9 @@ private:
|
||||
/// Handles writes to the macro uploading registers.
|
||||
void ProcessMacroUpload(u32 data);
|
||||
|
||||
/// Handles a write to the CLEAR_BUFFERS register.
|
||||
void ProcessClearBuffers();
|
||||
|
||||
/// Handles a write to the QUERY_GET register.
|
||||
void ProcessQueryGet();
|
||||
|
||||
@@ -788,21 +861,29 @@ ASSERT_REG_POSITION(rt, 0x200);
|
||||
ASSERT_REG_POSITION(viewport_transform[0], 0x280);
|
||||
ASSERT_REG_POSITION(viewport, 0x300);
|
||||
ASSERT_REG_POSITION(vertex_buffer, 0x35D);
|
||||
ASSERT_REG_POSITION(clear_color[0], 0x360);
|
||||
ASSERT_REG_POSITION(clear_depth, 0x364);
|
||||
ASSERT_REG_POSITION(zeta, 0x3F8);
|
||||
ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458);
|
||||
ASSERT_REG_POSITION(rt_control, 0x487);
|
||||
ASSERT_REG_POSITION(depth_test_enable, 0x4B3);
|
||||
ASSERT_REG_POSITION(independent_blend_enable, 0x4B9);
|
||||
ASSERT_REG_POSITION(depth_write_enabled, 0x4BA);
|
||||
ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2);
|
||||
ASSERT_REG_POSITION(depth_test_func, 0x4C3);
|
||||
ASSERT_REG_POSITION(blend, 0x4CF);
|
||||
ASSERT_REG_POSITION(stencil, 0x4E0);
|
||||
ASSERT_REG_POSITION(screen_y_control, 0x4EB);
|
||||
ASSERT_REG_POSITION(vb_element_base, 0x50D);
|
||||
ASSERT_REG_POSITION(tsc, 0x557);
|
||||
ASSERT_REG_POSITION(tic, 0x55D);
|
||||
ASSERT_REG_POSITION(stencil_two_side, 0x565);
|
||||
ASSERT_REG_POSITION(point_coord_replace, 0x581);
|
||||
ASSERT_REG_POSITION(code_address, 0x582);
|
||||
ASSERT_REG_POSITION(draw, 0x585);
|
||||
ASSERT_REG_POSITION(index_array, 0x5F2);
|
||||
ASSERT_REG_POSITION(cull, 0x646);
|
||||
ASSERT_REG_POSITION(clear_buffers, 0x674);
|
||||
ASSERT_REG_POSITION(query, 0x6C0);
|
||||
ASSERT_REG_POSITION(vertex_array[0], 0x700);
|
||||
ASSERT_REG_POSITION(independent_blend, 0x780);
|
||||
|
||||
@@ -142,6 +142,7 @@ enum class PredCondition : u64 {
|
||||
GreaterThan = 4,
|
||||
NotEqual = 5,
|
||||
GreaterEqual = 6,
|
||||
LessThanWithNan = 9,
|
||||
NotEqualWithNan = 13,
|
||||
// TODO(Subv): Other condition types
|
||||
};
|
||||
@@ -194,6 +195,18 @@ enum class UniformType : u64 {
|
||||
Double = 5,
|
||||
};
|
||||
|
||||
enum class IMinMaxExchange : u64 {
|
||||
None = 0,
|
||||
XLo = 1,
|
||||
XMed = 2,
|
||||
XHi = 3,
|
||||
};
|
||||
|
||||
enum class FlowCondition : u64 {
|
||||
Always = 0xF,
|
||||
Fcsm_Tr = 0x1C, // TODO(bunnei): What is this used for?
|
||||
};
|
||||
|
||||
union Instruction {
|
||||
Instruction& operator=(const Instruction& instr) {
|
||||
value = instr.value;
|
||||
@@ -278,11 +291,25 @@ union Instruction {
|
||||
BitField<49, 1, u64> negate_a;
|
||||
} alu_integer;
|
||||
|
||||
union {
|
||||
BitField<39, 3, u64> pred;
|
||||
BitField<42, 1, u64> negate_pred;
|
||||
BitField<43, 2, IMinMaxExchange> exchange;
|
||||
BitField<48, 1, u64> is_signed;
|
||||
} imnmx;
|
||||
|
||||
union {
|
||||
BitField<54, 1, u64> saturate;
|
||||
BitField<56, 1, u64> negate_a;
|
||||
} iadd32i;
|
||||
|
||||
union {
|
||||
BitField<53, 1, u64> negate_b;
|
||||
BitField<54, 1, u64> abs_a;
|
||||
BitField<56, 1, u64> negate_a;
|
||||
BitField<57, 1, u64> abs_b;
|
||||
} fadd32i;
|
||||
|
||||
union {
|
||||
BitField<20, 8, u64> shift_position;
|
||||
BitField<28, 8, u64> shift_length;
|
||||
@@ -294,6 +321,10 @@ union Instruction {
|
||||
}
|
||||
} bfe;
|
||||
|
||||
union {
|
||||
BitField<0, 5, FlowCondition> cond;
|
||||
} flow;
|
||||
|
||||
union {
|
||||
BitField<48, 1, u64> negate_b;
|
||||
BitField<49, 1, u64> negate_c;
|
||||
@@ -328,6 +359,19 @@ union Instruction {
|
||||
BitField<49, 3, PredCondition> cond;
|
||||
} isetp;
|
||||
|
||||
union {
|
||||
BitField<0, 3, u64> pred0;
|
||||
BitField<3, 3, u64> pred3;
|
||||
BitField<12, 3, u64> pred12;
|
||||
BitField<15, 1, u64> neg_pred12;
|
||||
BitField<24, 2, PredOperation> cond;
|
||||
BitField<29, 3, u64> pred29;
|
||||
BitField<32, 1, u64> neg_pred29;
|
||||
BitField<39, 3, u64> pred39;
|
||||
BitField<42, 1, u64> neg_pred39;
|
||||
BitField<45, 2, PredOperation> op;
|
||||
} psetp;
|
||||
|
||||
union {
|
||||
BitField<39, 3, u64> pred39;
|
||||
BitField<42, 1, u64> neg_pred;
|
||||
@@ -438,6 +482,8 @@ public:
|
||||
enum class Id {
|
||||
KIL,
|
||||
SSY,
|
||||
SYNC,
|
||||
DEPBAR,
|
||||
BFE_C,
|
||||
BFE_R,
|
||||
BFE_IMM,
|
||||
@@ -458,6 +504,7 @@ public:
|
||||
FADD_C,
|
||||
FADD_R,
|
||||
FADD_IMM,
|
||||
FADD32I,
|
||||
FMUL_C,
|
||||
FMUL_R,
|
||||
FMUL_IMM,
|
||||
@@ -534,6 +581,7 @@ public:
|
||||
Shift,
|
||||
Ffma,
|
||||
Flow,
|
||||
Synch,
|
||||
Memory,
|
||||
FloatSet,
|
||||
FloatSetPredicate,
|
||||
@@ -638,22 +686,25 @@ private:
|
||||
INST("111000110011----", Id::KIL, Type::Flow, "KIL"),
|
||||
INST("111000101001----", Id::SSY, Type::Flow, "SSY"),
|
||||
INST("111000100100----", Id::BRA, Type::Flow, "BRA"),
|
||||
INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
|
||||
INST("1111000011111---", Id::SYNC, Type::Synch, "SYNC"),
|
||||
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
|
||||
INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
|
||||
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
|
||||
INST("1100000000111---", Id::TEX, Type::Memory, "TEX"),
|
||||
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
|
||||
INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"),
|
||||
INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"),
|
||||
INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
|
||||
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
|
||||
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
|
||||
INST("001100101-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
|
||||
INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
|
||||
INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
|
||||
INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),
|
||||
INST("010110011-------", Id::FFMA_RR, Type::Ffma, "FFMA_RR"),
|
||||
INST("0100110001011---", Id::FADD_C, Type::Arithmetic, "FADD_C"),
|
||||
INST("0101110001011---", Id::FADD_R, Type::Arithmetic, "FADD_R"),
|
||||
INST("0011100-01011---", Id::FADD_IMM, Type::Arithmetic, "FADD_IMM"),
|
||||
INST("000010----------", Id::FADD32I, Type::ArithmeticImmediate, "FADD32I"),
|
||||
INST("0100110001101---", Id::FMUL_C, Type::Arithmetic, "FMUL_C"),
|
||||
INST("0101110001101---", Id::FMUL_R, Type::Arithmetic, "FMUL_R"),
|
||||
INST("0011100-01101---", Id::FMUL_IMM, Type::Arithmetic, "FMUL_IMM"),
|
||||
@@ -682,9 +733,9 @@ private:
|
||||
INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"),
|
||||
INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"),
|
||||
INST("0011100-01100---", Id::FMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("0100110000100---", Id::IMNMX_C, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("0101110000100---", Id::IMNMX_R, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("0011100-00100---", Id::IMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("0100110000100---", Id::IMNMX_C, Type::ArithmeticInteger, "IMNMX_C"),
|
||||
INST("0101110000100---", Id::IMNMX_R, Type::ArithmeticInteger, "IMNMX_R"),
|
||||
INST("0011100-00100---", Id::IMNMX_IMM, Type::ArithmeticInteger, "IMNMX_IMM"),
|
||||
INST("0100110000000---", Id::BFE_C, Type::Bfe, "BFE_C"),
|
||||
INST("0101110000000---", Id::BFE_R, Type::Bfe, "BFE_R"),
|
||||
INST("0011100-00000---", Id::BFE_IMM, Type::Bfe, "BFE_IMM"),
|
||||
|
||||
@@ -67,6 +67,7 @@ struct FramebufferConfig {
|
||||
|
||||
using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
|
||||
TransformFlags transform_flags;
|
||||
MathUtil::Rectangle<int> crop_rect;
|
||||
};
|
||||
|
||||
namespace Engines {
|
||||
|
||||
@@ -19,6 +19,9 @@ public:
|
||||
/// Draw the current batch of vertex arrays
|
||||
virtual void DrawArrays() = 0;
|
||||
|
||||
/// Clear the current framebuffer
|
||||
virtual void Clear() = 0;
|
||||
|
||||
/// Notify rasterizer that the specified Maxwell register has been changed
|
||||
virtual void NotifyMaxwellRegisterChanged(u32 method) = 0;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/maxwell_to_gl.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using PixelFormat = SurfaceParams::PixelFormat;
|
||||
@@ -181,6 +183,19 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
|
||||
return {array_ptr, buffer_offset};
|
||||
}
|
||||
|
||||
static GLShader::ProgramCode GetShaderProgramCode(Maxwell::ShaderProgram program) {
|
||||
auto& gpu = Core::System().GetInstance().GPU().Maxwell3D();
|
||||
|
||||
// Fetch program code from memory
|
||||
GLShader::ProgramCode program_code;
|
||||
auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)];
|
||||
const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset};
|
||||
const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)};
|
||||
Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64));
|
||||
|
||||
return program_code;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
|
||||
// Helper function for uploading uniform data
|
||||
const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) {
|
||||
@@ -193,26 +208,23 @@ void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
|
||||
};
|
||||
|
||||
auto& gpu = Core::System().GetInstance().GPU().Maxwell3D();
|
||||
ASSERT_MSG(!gpu.regs.shader_config[0].enable, "VertexA is unsupported!");
|
||||
|
||||
// Next available bindpoints to use when uploading the const buffers and textures to the GLSL
|
||||
// shaders. The constbuffer bindpoint starts after the shader stage configuration bind points.
|
||||
u32 current_constbuffer_bindpoint = uniform_buffers.size();
|
||||
u32 current_texture_bindpoint = 0;
|
||||
|
||||
for (unsigned index = 1; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
auto& shader_config = gpu.regs.shader_config[index];
|
||||
const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)};
|
||||
|
||||
const auto& stage = index - 1; // Stage indices are 0 - 5
|
||||
|
||||
const bool is_enabled = gpu.IsShaderStageEnabled(static_cast<Maxwell::ShaderStage>(stage));
|
||||
|
||||
// Skip stages that are not enabled
|
||||
if (!is_enabled) {
|
||||
if (!gpu.regs.IsShaderConfigEnabled(index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t stage{index == 0 ? 0 : index - 1}; // Stage indices are 0 - 5
|
||||
|
||||
GLShader::MaxwellUniformData ubo{};
|
||||
ubo.SetFromRegs(gpu.state.shader_stages[stage]);
|
||||
std::memcpy(buffer_ptr, &ubo, sizeof(ubo));
|
||||
@@ -228,16 +240,21 @@ void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
|
||||
buffer_ptr += sizeof(GLShader::MaxwellUniformData);
|
||||
buffer_offset += sizeof(GLShader::MaxwellUniformData);
|
||||
|
||||
// Fetch program code from memory
|
||||
GLShader::ProgramCode program_code;
|
||||
const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset};
|
||||
const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)};
|
||||
Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64));
|
||||
GLShader::ShaderSetup setup{std::move(program_code)};
|
||||
|
||||
GLShader::ShaderSetup setup{GetShaderProgramCode(program)};
|
||||
GLShader::ShaderEntries shader_resources;
|
||||
|
||||
switch (program) {
|
||||
case Maxwell::ShaderProgram::VertexA: {
|
||||
// VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders.
|
||||
// Conventional HW does not support this, so we combine VertexA and VertexB into one
|
||||
// stage here.
|
||||
setup.SetProgramB(GetShaderProgramCode(Maxwell::ShaderProgram::VertexB));
|
||||
GLShader::MaxwellVSConfig vs_config{setup};
|
||||
shader_resources =
|
||||
shader_program_manager->UseProgrammableVertexShader(vs_config, setup);
|
||||
break;
|
||||
}
|
||||
|
||||
case Maxwell::ShaderProgram::VertexB: {
|
||||
GLShader::MaxwellVSConfig vs_config{setup};
|
||||
shader_resources =
|
||||
@@ -268,6 +285,12 @@ void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
|
||||
current_texture_bindpoint =
|
||||
SetupTextures(static_cast<Maxwell::ShaderStage>(stage), gl_stage_program,
|
||||
current_texture_bindpoint, shader_resources.texture_samplers);
|
||||
|
||||
// When VertexA is enabled, we have dual vertex shaders
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
// VertexB was combined with VertexA, so we skip the VertexB iteration
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
shader_program_manager->UseTrivialGeometryShader();
|
||||
@@ -297,22 +320,13 @@ bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::DrawArrays() {
|
||||
if (accelerate_draw == AccelDraw::Disabled)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_Drawing);
|
||||
std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb,
|
||||
bool using_depth_fb) {
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
// Sync the depth test state before configuring the framebuffer surfaces.
|
||||
SyncDepthTestState();
|
||||
|
||||
// TODO(bunnei): Implement this
|
||||
const bool has_stencil = false;
|
||||
|
||||
const bool using_color_fb = true;
|
||||
const bool using_depth_fb = regs.zeta.Address() != 0;
|
||||
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
|
||||
|
||||
const bool write_color_fb =
|
||||
@@ -344,11 +358,6 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
BindFramebufferSurfaces(color_surface, depth_surface, has_stencil);
|
||||
|
||||
SyncViewport(surfaces_rect);
|
||||
SyncBlendState();
|
||||
SyncCullMode();
|
||||
|
||||
// TODO(bunnei): Sync framebuffer_scale uniform here
|
||||
// TODO(bunnei): Sync scissorbox uniform(s) here
|
||||
|
||||
// Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable
|
||||
// scissor test to prevent drawing outside of the framebuffer region
|
||||
@@ -359,6 +368,78 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
state.scissor.height = draw_rect.GetHeight();
|
||||
state.Apply();
|
||||
|
||||
// Only return the surface to be marked as dirty if writing to it is enabled.
|
||||
return std::make_pair(write_color_fb ? color_surface : nullptr,
|
||||
write_depth_fb ? depth_surface : nullptr);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::Clear() {
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
bool use_color_fb = false;
|
||||
bool use_depth_fb = false;
|
||||
|
||||
GLbitfield clear_mask = 0;
|
||||
if (regs.clear_buffers.R && regs.clear_buffers.G && regs.clear_buffers.B &&
|
||||
regs.clear_buffers.A) {
|
||||
clear_mask |= GL_COLOR_BUFFER_BIT;
|
||||
use_color_fb = true;
|
||||
}
|
||||
if (regs.clear_buffers.Z) {
|
||||
clear_mask |= GL_DEPTH_BUFFER_BIT;
|
||||
use_depth_fb = true;
|
||||
|
||||
// Always enable the depth write when clearing the depth buffer. The depth write mask is
|
||||
// ignored when clearing the buffer in the Switch, but OpenGL obeys it so we set it to true.
|
||||
state.depth.test_enabled = true;
|
||||
state.depth.write_mask = GL_TRUE;
|
||||
state.depth.test_func = GL_ALWAYS;
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
if (clear_mask == 0)
|
||||
return;
|
||||
|
||||
ScopeAcquireGLContext acquire_context;
|
||||
|
||||
auto [dirty_color_surface, dirty_depth_surface] =
|
||||
ConfigureFramebuffers(use_color_fb, use_depth_fb);
|
||||
|
||||
// TODO(Subv): Support clearing only partial colors.
|
||||
glClearColor(regs.clear_color[0], regs.clear_color[1], regs.clear_color[2],
|
||||
regs.clear_color[3]);
|
||||
glClearDepth(regs.clear_depth);
|
||||
|
||||
glClear(clear_mask);
|
||||
|
||||
// Mark framebuffer surfaces as dirty
|
||||
if (dirty_color_surface != nullptr) {
|
||||
res_cache.MarkSurfaceAsDirty(dirty_color_surface);
|
||||
}
|
||||
if (dirty_depth_surface != nullptr) {
|
||||
res_cache.MarkSurfaceAsDirty(dirty_depth_surface);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::DrawArrays() {
|
||||
if (accelerate_draw == AccelDraw::Disabled)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_Drawing);
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
ScopeAcquireGLContext acquire_context;
|
||||
|
||||
auto [dirty_color_surface, dirty_depth_surface] =
|
||||
ConfigureFramebuffers(true, regs.zeta.Address() != 0);
|
||||
|
||||
SyncDepthTestState();
|
||||
SyncBlendState();
|
||||
SyncCullMode();
|
||||
|
||||
// TODO(bunnei): Sync framebuffer_scale uniform here
|
||||
// TODO(bunnei): Sync scissorbox uniform(s) here
|
||||
|
||||
// Draw the vertex batch
|
||||
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
|
||||
const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()};
|
||||
@@ -439,11 +520,11 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
state.Apply();
|
||||
|
||||
// Mark framebuffer surfaces as dirty
|
||||
if (color_surface != nullptr && write_color_fb) {
|
||||
res_cache.MarkSurfaceAsDirty(color_surface);
|
||||
if (dirty_color_surface != nullptr) {
|
||||
res_cache.MarkSurfaceAsDirty(dirty_color_surface);
|
||||
}
|
||||
if (depth_surface != nullptr && write_depth_fb) {
|
||||
res_cache.MarkSurfaceAsDirty(depth_surface);
|
||||
if (dirty_depth_surface != nullptr) {
|
||||
res_cache.MarkSurfaceAsDirty(dirty_depth_surface);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,9 +637,6 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, GLuint progr
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
auto& maxwell3d = gpu.Get3DEngine();
|
||||
|
||||
ASSERT_MSG(maxwell3d.IsShaderStageEnabled(stage),
|
||||
"Attempted to upload constbuffer of disabled shader stage");
|
||||
|
||||
// Reset all buffer draw state for this stage.
|
||||
for (auto& buffer : state.draw.const_buffers[static_cast<size_t>(stage)]) {
|
||||
buffer.bindpoint = 0;
|
||||
@@ -625,9 +703,6 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, GLuint program,
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
auto& maxwell3d = gpu.Get3DEngine();
|
||||
|
||||
ASSERT_MSG(maxwell3d.IsShaderStageEnabled(stage),
|
||||
"Attempted to upload textures of disabled shader stage");
|
||||
|
||||
ASSERT_MSG(current_unit + entries.size() <= std::size(state.texture_units),
|
||||
"Exceeded the number of active textures.");
|
||||
|
||||
@@ -637,7 +712,10 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, GLuint program,
|
||||
|
||||
// Bind the uniform to the sampler.
|
||||
GLint uniform = glGetUniformLocation(program, entry.GetName().c_str());
|
||||
ASSERT(uniform != -1);
|
||||
if (uniform == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
glProgramUniform1i(program, uniform, current_bindpoint);
|
||||
|
||||
const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());
|
||||
@@ -722,6 +800,19 @@ void RasterizerOpenGL::SyncCullMode() {
|
||||
if (state.cull.enabled) {
|
||||
state.cull.front_face = MaxwellToGL::FrontFace(regs.cull.front_face);
|
||||
state.cull.mode = MaxwellToGL::CullFace(regs.cull.cull_face);
|
||||
|
||||
const bool flip_triangles{regs.screen_y_control.triangle_rast_flip == 0 ||
|
||||
regs.viewport_transform[0].scale_y < 0.0f};
|
||||
|
||||
// If the GPU is configured to flip the rasterized triangles, then we need to flip the
|
||||
// notion of front and back. Note: We flip the triangles when the value of the register is 0
|
||||
// because OpenGL already does it for us.
|
||||
if (flip_triangles) {
|
||||
if (state.cull.front_face == GL_CCW)
|
||||
state.cull.front_face = GL_CW;
|
||||
else if (state.cull.front_face == GL_CW)
|
||||
state.cull.front_face = GL_CCW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
@@ -28,6 +29,7 @@ public:
|
||||
~RasterizerOpenGL() override;
|
||||
|
||||
void DrawArrays() override;
|
||||
void Clear() override;
|
||||
void NotifyMaxwellRegisterChanged(u32 method) override;
|
||||
void FlushAll() override;
|
||||
void FlushRegion(Tegra::GPUVAddr addr, u64 size) override;
|
||||
@@ -81,6 +83,10 @@ private:
|
||||
u32 border_color_a;
|
||||
};
|
||||
|
||||
/// Configures the color and depth framebuffer states and returns the dirty <Color, Depth>
|
||||
/// surfaces if writing was enabled.
|
||||
std::pair<Surface, Surface> ConfigureFramebuffers(bool using_color_fb, bool using_depth_fb);
|
||||
|
||||
/// Binds the framebuffer color and depth surface
|
||||
void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface,
|
||||
bool has_stencil);
|
||||
|
||||
@@ -65,6 +65,25 @@ struct FormatTuple {
|
||||
return params;
|
||||
}
|
||||
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(
|
||||
const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config, Tegra::GPUVAddr zeta_address,
|
||||
Tegra::DepthFormat format) {
|
||||
|
||||
SurfaceParams params{};
|
||||
params.addr = zeta_address;
|
||||
params.is_tiled = true;
|
||||
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
|
||||
params.pixel_format = PixelFormatFromDepthFormat(format);
|
||||
params.component_type = ComponentTypeFromDepthFormat(format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.width = config.width;
|
||||
params.height = config.height;
|
||||
params.unaligned_height = config.height;
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
return params;
|
||||
}
|
||||
|
||||
static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8
|
||||
{GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5
|
||||
@@ -83,11 +102,19 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
{GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // DXT45
|
||||
{GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN1
|
||||
{GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // BC7U
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4
|
||||
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8
|
||||
|
||||
// DepthStencil formats
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm,
|
||||
false}, // Z24S8
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm,
|
||||
false}, // S8Z24
|
||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm,
|
||||
false}, // Z16
|
||||
}};
|
||||
|
||||
static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) {
|
||||
@@ -131,13 +158,6 @@ MathUtil::Rectangle<u32> SurfaceParams::GetRect() const {
|
||||
return {0, actual_height, width, 0};
|
||||
}
|
||||
|
||||
static void ConvertASTCToRGBA8(std::vector<u8>& data, PixelFormat format, u32 width, u32 height) {
|
||||
u32 block_width{};
|
||||
u32 block_height{};
|
||||
std::tie(block_width, block_height) = GetASTCBlockSize(format);
|
||||
data = Tegra::Texture::ASTC::Decompress(data, width, height, block_width, block_height);
|
||||
}
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, Tegra::GPUVAddr addr) {
|
||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
|
||||
@@ -176,7 +196,10 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
|
||||
MortonCopy<true, PixelFormat::R11FG11FB10F>, MortonCopy<true, PixelFormat::RGBA32UI>,
|
||||
MortonCopy<true, PixelFormat::DXT1>, MortonCopy<true, PixelFormat::DXT23>,
|
||||
MortonCopy<true, PixelFormat::DXT45>, MortonCopy<true, PixelFormat::DXN1>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4>, MortonCopy<true, PixelFormat::Z24S8>,
|
||||
MortonCopy<true, PixelFormat::BC7U>, MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
|
||||
MortonCopy<true, PixelFormat::G8R8>, MortonCopy<true, PixelFormat::Z24S8>,
|
||||
MortonCopy<true, PixelFormat::S8Z24>, MortonCopy<true, PixelFormat::Z32F>,
|
||||
MortonCopy<true, PixelFormat::Z16>,
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
|
||||
@@ -190,13 +213,18 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
|
||||
MortonCopy<false, PixelFormat::RGBA16F>,
|
||||
MortonCopy<false, PixelFormat::R11FG11FB10F>,
|
||||
MortonCopy<false, PixelFormat::RGBA32UI>,
|
||||
// TODO(Subv): Swizzling the DXT1/DXT23/DXT45/DXN1 formats is not yet supported
|
||||
// TODO(Subv): Swizzling the DXT1/DXT23/DXT45/DXN1/BC7U formats is not yet supported
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::ABGR8>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::G8R8>,
|
||||
MortonCopy<false, PixelFormat::Z24S8>,
|
||||
MortonCopy<false, PixelFormat::S8Z24>,
|
||||
MortonCopy<false, PixelFormat::Z32F>,
|
||||
MortonCopy<false, PixelFormat::Z16>,
|
||||
};
|
||||
|
||||
// Allocate an uninitialized texture of appropriate size and format for the surface
|
||||
@@ -234,6 +262,90 @@ CachedSurface::CachedSurface(const SurfaceParams& params) : params(params) {
|
||||
rect.GetWidth(), rect.GetHeight());
|
||||
}
|
||||
|
||||
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
union S8Z24 {
|
||||
BitField<0, 24, u32> z24;
|
||||
BitField<24, 8, u32> s8;
|
||||
};
|
||||
static_assert(sizeof(S8Z24) == 4, "S8Z24 is incorrect size");
|
||||
|
||||
union Z24S8 {
|
||||
BitField<0, 8, u32> s8;
|
||||
BitField<8, 24, u32> z24;
|
||||
};
|
||||
static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size");
|
||||
|
||||
S8Z24 input_pixel{};
|
||||
Z24S8 output_pixel{};
|
||||
const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)};
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
const size_t offset{bpp * (y * width + x)};
|
||||
std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24));
|
||||
output_pixel.s8.Assign(input_pixel.s8);
|
||||
output_pixel.z24.Assign(input_pixel.z24);
|
||||
std::memcpy(&data[offset], &output_pixel, sizeof(Z24S8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8)};
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
const size_t offset{bpp * (y * width + x)};
|
||||
const u8 temp{data[offset]};
|
||||
data[offset] = data[offset + 1];
|
||||
data[offset + 1] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform software conversion (as needed) when loading a buffer from Switch
|
||||
* memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or with
|
||||
* typical desktop GPUs.
|
||||
*/
|
||||
static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
|
||||
u32 width, u32 height) {
|
||||
switch (pixel_format) {
|
||||
case PixelFormat::ASTC_2D_4X4: {
|
||||
// Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
|
||||
u32 block_width{};
|
||||
u32 block_height{};
|
||||
std::tie(block_width, block_height) = GetASTCBlockSize(pixel_format);
|
||||
data = Tegra::Texture::ASTC::Decompress(data, width, height, block_width, block_height);
|
||||
break;
|
||||
}
|
||||
case PixelFormat::S8Z24:
|
||||
// Convert the S8Z24 depth format to Z24S8, as OpenGL does not support S8Z24.
|
||||
ConvertS8Z24ToZ24S8(data, width, height);
|
||||
break;
|
||||
|
||||
case PixelFormat::G8R8:
|
||||
// Convert the G8R8 color format to R8G8, as OpenGL does not support G8R8.
|
||||
ConvertG8R8ToR8G8(data, width, height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform software conversion (as needed) when flushing a buffer to Switch
|
||||
* memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or with
|
||||
* typical desktop GPUs.
|
||||
*/
|
||||
static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& /*data*/, PixelFormat pixel_format,
|
||||
u32 /*width*/, u32 /*height*/) {
|
||||
switch (pixel_format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
case PixelFormat::S8Z24:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented pixel_format={}",
|
||||
static_cast<u32>(pixel_format));
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
|
||||
void CachedSurface::LoadGLBuffer() {
|
||||
ASSERT(params.type != SurfaceType::Fill);
|
||||
@@ -256,10 +368,7 @@ void CachedSurface::LoadGLBuffer() {
|
||||
params.width, params.block_height, params.height, gl_buffer.data(), params.addr);
|
||||
}
|
||||
|
||||
if (IsPixelFormatASTC(params.pixel_format)) {
|
||||
// ASTC formats are converted to RGBA8 in software, as most PC GPUs do not support this
|
||||
ConvertASTCToRGBA8(gl_buffer, params.pixel_format, params.width, params.height);
|
||||
}
|
||||
ConvertFormatAsNeeded_LoadGLBuffer(gl_buffer, params.pixel_format, params.width, params.height);
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
|
||||
@@ -272,6 +381,9 @@ void CachedSurface::FlushGLBuffer() {
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
|
||||
|
||||
ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
|
||||
params.height);
|
||||
|
||||
if (!params.is_tiled) {
|
||||
std::memcpy(dst_buffer, gl_buffer.data(), params.size_in_bytes);
|
||||
} else {
|
||||
@@ -399,15 +511,16 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
|
||||
LOG_WARNING(Render_OpenGL, "hard-coded for render target 0!");
|
||||
|
||||
// get color and depth surfaces
|
||||
const SurfaceParams color_params{SurfaceParams::CreateForFramebuffer(regs.rt[0])};
|
||||
SurfaceParams depth_params{color_params};
|
||||
SurfaceParams color_params{};
|
||||
SurfaceParams depth_params{};
|
||||
|
||||
if (using_color_fb) {
|
||||
color_params = SurfaceParams::CreateForFramebuffer(regs.rt[0]);
|
||||
}
|
||||
|
||||
if (using_depth_fb) {
|
||||
depth_params.addr = regs.zeta.Address();
|
||||
depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(regs.zeta.format);
|
||||
depth_params.component_type = SurfaceParams::ComponentTypeFromDepthFormat(regs.zeta.format);
|
||||
depth_params.type = SurfaceParams::GetFormatType(depth_params.pixel_format);
|
||||
depth_params.size_in_bytes = depth_params.SizeInBytes();
|
||||
depth_params =
|
||||
SurfaceParams::CreateForDepthBuffer(regs.rt[0], regs.zeta.Address(), regs.zeta.format);
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<u32> color_rect{};
|
||||
|
||||
@@ -35,12 +35,17 @@ struct SurfaceParams {
|
||||
DXT23 = 9,
|
||||
DXT45 = 10,
|
||||
DXN1 = 11, // This is also known as BC4
|
||||
ASTC_2D_4X4 = 12,
|
||||
BC7U = 12,
|
||||
ASTC_2D_4X4 = 13,
|
||||
G8R8 = 14,
|
||||
|
||||
MaxColorFormat,
|
||||
|
||||
// DepthStencil formats
|
||||
Z24S8 = 13,
|
||||
Z24S8 = 15,
|
||||
S8Z24 = 16,
|
||||
Z32F = 17,
|
||||
Z16 = 18,
|
||||
|
||||
MaxDepthStencilFormat,
|
||||
|
||||
@@ -90,8 +95,13 @@ struct SurfaceParams {
|
||||
4, // DXT23
|
||||
4, // DXT45
|
||||
4, // DXN1
|
||||
4, // BC7U
|
||||
4, // ASTC_2D_4X4
|
||||
1, // G8R8
|
||||
1, // Z24S8
|
||||
1, // S8Z24
|
||||
1, // Z32F
|
||||
1, // Z16
|
||||
}};
|
||||
|
||||
ASSERT(static_cast<size_t>(format) < compression_factor_table.size());
|
||||
@@ -115,8 +125,13 @@ struct SurfaceParams {
|
||||
128, // DXT23
|
||||
128, // DXT45
|
||||
64, // DXN1
|
||||
128, // BC7U
|
||||
32, // ASTC_2D_4X4
|
||||
16, // G8R8
|
||||
32, // Z24S8
|
||||
32, // S8Z24
|
||||
32, // Z32F
|
||||
16, // Z16
|
||||
}};
|
||||
|
||||
ASSERT(static_cast<size_t>(format) < bpp_table.size());
|
||||
@@ -128,8 +143,14 @@ struct SurfaceParams {
|
||||
|
||||
static PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
|
||||
switch (format) {
|
||||
case Tegra::DepthFormat::S8_Z24_UNORM:
|
||||
return PixelFormat::S8Z24;
|
||||
case Tegra::DepthFormat::Z24_S8_UNORM:
|
||||
return PixelFormat::Z24S8;
|
||||
case Tegra::DepthFormat::Z32_FLOAT:
|
||||
return PixelFormat::Z32F;
|
||||
case Tegra::DepthFormat::Z16_UNORM:
|
||||
return PixelFormat::Z16;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
@@ -168,6 +189,8 @@ struct SurfaceParams {
|
||||
return PixelFormat::A1B5G5R5;
|
||||
case Tegra::Texture::TextureFormat::R8:
|
||||
return PixelFormat::R8;
|
||||
case Tegra::Texture::TextureFormat::G8R8:
|
||||
return PixelFormat::G8R8;
|
||||
case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
|
||||
return PixelFormat::RGBA16F;
|
||||
case Tegra::Texture::TextureFormat::BF10GF11RF11:
|
||||
@@ -182,6 +205,8 @@ struct SurfaceParams {
|
||||
return PixelFormat::DXT45;
|
||||
case Tegra::Texture::TextureFormat::DXN1:
|
||||
return PixelFormat::DXN1;
|
||||
case Tegra::Texture::TextureFormat::BC7U:
|
||||
return PixelFormat::BC7U;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
|
||||
return PixelFormat::ASTC_2D_4X4;
|
||||
default:
|
||||
@@ -203,6 +228,8 @@ struct SurfaceParams {
|
||||
return Tegra::Texture::TextureFormat::A1B5G5R5;
|
||||
case PixelFormat::R8:
|
||||
return Tegra::Texture::TextureFormat::R8;
|
||||
case PixelFormat::G8R8:
|
||||
return Tegra::Texture::TextureFormat::G8R8;
|
||||
case PixelFormat::RGBA16F:
|
||||
return Tegra::Texture::TextureFormat::R16_G16_B16_A16;
|
||||
case PixelFormat::R11FG11FB10F:
|
||||
@@ -217,6 +244,8 @@ struct SurfaceParams {
|
||||
return Tegra::Texture::TextureFormat::DXT45;
|
||||
case PixelFormat::DXN1:
|
||||
return Tegra::Texture::TextureFormat::DXN1;
|
||||
case PixelFormat::BC7U:
|
||||
return Tegra::Texture::TextureFormat::BC7U;
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
return Tegra::Texture::TextureFormat::ASTC_2D_4X4;
|
||||
default:
|
||||
@@ -226,8 +255,14 @@ struct SurfaceParams {
|
||||
|
||||
static Tegra::DepthFormat DepthFormatFromPixelFormat(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::S8Z24:
|
||||
return Tegra::DepthFormat::S8_Z24_UNORM;
|
||||
case PixelFormat::Z24S8:
|
||||
return Tegra::DepthFormat::Z24_S8_UNORM;
|
||||
case PixelFormat::Z32F:
|
||||
return Tegra::DepthFormat::Z32_FLOAT;
|
||||
case PixelFormat::Z16:
|
||||
return Tegra::DepthFormat::Z16_UNORM;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@@ -274,8 +309,12 @@ struct SurfaceParams {
|
||||
|
||||
static ComponentType ComponentTypeFromDepthFormat(Tegra::DepthFormat format) {
|
||||
switch (format) {
|
||||
case Tegra::DepthFormat::Z16_UNORM:
|
||||
case Tegra::DepthFormat::S8_Z24_UNORM:
|
||||
case Tegra::DepthFormat::Z24_S8_UNORM:
|
||||
return ComponentType::UNorm;
|
||||
case Tegra::DepthFormat::Z32_FLOAT:
|
||||
return ComponentType::Float;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
@@ -318,13 +357,18 @@ struct SurfaceParams {
|
||||
return addr <= (region_addr + region_size) && region_addr <= (addr + size_in_bytes);
|
||||
}
|
||||
|
||||
/// Creates SurfaceParams from a texture configation
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config);
|
||||
|
||||
/// Creates SurfaceParams from a framebuffer configation
|
||||
/// Creates SurfaceParams from a framebuffer configuration
|
||||
static SurfaceParams CreateForFramebuffer(
|
||||
const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config);
|
||||
|
||||
/// Creates SurfaceParams for a depth buffer configuration
|
||||
static SurfaceParams CreateForDepthBuffer(
|
||||
const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config,
|
||||
Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format);
|
||||
|
||||
Tegra::GPUVAddr addr;
|
||||
bool is_tiled;
|
||||
u32 block_height;
|
||||
|
||||
@@ -42,13 +42,14 @@ enum class ExitMethod {
|
||||
struct Subroutine {
|
||||
/// Generates a name suitable for GLSL source code.
|
||||
std::string GetName() const {
|
||||
return "sub_" + std::to_string(begin) + '_' + std::to_string(end);
|
||||
return "sub_" + std::to_string(begin) + '_' + std::to_string(end) + '_' + suffix;
|
||||
}
|
||||
|
||||
u32 begin; ///< Entry point of the subroutine.
|
||||
u32 end; ///< Return point of the subroutine.
|
||||
ExitMethod exit_method; ///< Exit method of the subroutine.
|
||||
std::set<u32> labels; ///< Addresses refereced by JMP instructions.
|
||||
u32 begin; ///< Entry point of the subroutine.
|
||||
u32 end; ///< Return point of the subroutine.
|
||||
const std::string& suffix; ///< Suffix of the shader, used to make a unique subroutine name
|
||||
ExitMethod exit_method; ///< Exit method of the subroutine.
|
||||
std::set<u32> labels; ///< Addresses refereced by JMP instructions.
|
||||
|
||||
bool operator<(const Subroutine& rhs) const {
|
||||
return std::tie(begin, end) < std::tie(rhs.begin, rhs.end);
|
||||
@@ -58,11 +59,11 @@ struct Subroutine {
|
||||
/// Analyzes shader code and produces a set of subroutines.
|
||||
class ControlFlowAnalyzer {
|
||||
public:
|
||||
ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset)
|
||||
ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset, const std::string& suffix)
|
||||
: program_code(program_code) {
|
||||
|
||||
// Recursively finds all subroutines.
|
||||
const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END);
|
||||
const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END, suffix);
|
||||
if (program_main.exit_method != ExitMethod::AlwaysEnd)
|
||||
throw DecompileFail("Program does not always end");
|
||||
}
|
||||
@@ -77,12 +78,12 @@ private:
|
||||
std::map<std::pair<u32, u32>, ExitMethod> exit_method_map;
|
||||
|
||||
/// Adds and analyzes a new subroutine if it is not added yet.
|
||||
const Subroutine& AddSubroutine(u32 begin, u32 end) {
|
||||
auto iter = subroutines.find(Subroutine{begin, end});
|
||||
const Subroutine& AddSubroutine(u32 begin, u32 end, const std::string& suffix) {
|
||||
auto iter = subroutines.find(Subroutine{begin, end, suffix});
|
||||
if (iter != subroutines.end())
|
||||
return *iter;
|
||||
|
||||
Subroutine subroutine{begin, end};
|
||||
Subroutine subroutine{begin, end, suffix};
|
||||
subroutine.exit_method = Scan(begin, end, subroutine.labels);
|
||||
if (subroutine.exit_method == ExitMethod::Undetermined)
|
||||
throw DecompileFail("Recursive function detected");
|
||||
@@ -191,7 +192,8 @@ public:
|
||||
UnsignedInteger,
|
||||
};
|
||||
|
||||
GLSLRegister(size_t index, ShaderWriter& shader) : index{index}, shader{shader} {}
|
||||
GLSLRegister(size_t index, ShaderWriter& shader, const std::string& suffix)
|
||||
: index{index}, shader{shader}, suffix{suffix} {}
|
||||
|
||||
/// Gets the GLSL type string for a register
|
||||
static std::string GetTypeString(Type type) {
|
||||
@@ -216,7 +218,7 @@ public:
|
||||
/// Returns a GLSL string representing the current state of the register
|
||||
const std::string GetActiveString() {
|
||||
declr_type.insert(active_type);
|
||||
return GetPrefixString(active_type) + std::to_string(index);
|
||||
return GetPrefixString(active_type) + std::to_string(index) + '_' + suffix;
|
||||
}
|
||||
|
||||
/// Returns true if the active type is a float
|
||||
@@ -251,6 +253,7 @@ private:
|
||||
ShaderWriter& shader;
|
||||
Type active_type{Type::Float};
|
||||
std::set<Type> declr_type;
|
||||
const std::string& suffix;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -262,8 +265,8 @@ private:
|
||||
class GLSLRegisterManager {
|
||||
public:
|
||||
GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations,
|
||||
const Maxwell3D::Regs::ShaderStage& stage)
|
||||
: shader{shader}, declarations{declarations}, stage{stage} {
|
||||
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix)
|
||||
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} {
|
||||
BuildRegisterList();
|
||||
}
|
||||
|
||||
@@ -430,12 +433,12 @@ public:
|
||||
}
|
||||
|
||||
/// Add declarations for registers
|
||||
void GenerateDeclarations() {
|
||||
void GenerateDeclarations(const std::string& suffix) {
|
||||
for (const auto& reg : regs) {
|
||||
for (const auto& type : reg.DeclaredTypes()) {
|
||||
declarations.AddLine(GLSLRegister::GetTypeString(type) + ' ' +
|
||||
GLSLRegister::GetPrefixString(type) +
|
||||
std::to_string(reg.GetIndex()) + " = 0;");
|
||||
reg.GetPrefixString(type) + std::to_string(reg.GetIndex()) +
|
||||
'_' + suffix + " = 0;");
|
||||
}
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
@@ -558,7 +561,7 @@ private:
|
||||
/// Build the GLSL register list.
|
||||
void BuildRegisterList() {
|
||||
for (size_t index = 0; index < Register::NumRegisters; ++index) {
|
||||
regs.emplace_back(index, shader);
|
||||
regs.emplace_back(index, shader, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,16 +623,17 @@ private:
|
||||
std::array<ConstBufferEntry, Maxwell3D::Regs::MaxConstBuffers> declr_const_buffers;
|
||||
std::vector<SamplerEntry> used_samplers;
|
||||
const Maxwell3D::Regs::ShaderStage& stage;
|
||||
const std::string& suffix;
|
||||
};
|
||||
|
||||
class GLSLGenerator {
|
||||
public:
|
||||
GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code,
|
||||
u32 main_offset, Maxwell3D::Regs::ShaderStage stage)
|
||||
u32 main_offset, Maxwell3D::Regs::ShaderStage stage, const std::string& suffix)
|
||||
: subroutines(subroutines), program_code(program_code), main_offset(main_offset),
|
||||
stage(stage) {
|
||||
stage(stage), suffix(suffix) {
|
||||
|
||||
Generate();
|
||||
Generate(suffix);
|
||||
}
|
||||
|
||||
std::string GetShaderCode() {
|
||||
@@ -644,7 +648,7 @@ public:
|
||||
private:
|
||||
/// Gets the Subroutine object corresponding to the specified address.
|
||||
const Subroutine& GetSubroutine(u32 begin, u32 end) const {
|
||||
auto iter = subroutines.find(Subroutine{begin, end});
|
||||
auto iter = subroutines.find(Subroutine{begin, end, suffix});
|
||||
ASSERT(iter != subroutines.end());
|
||||
return *iter;
|
||||
}
|
||||
@@ -689,7 +693,7 @@ private:
|
||||
// Can't assign to the constant predicate.
|
||||
ASSERT(pred != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string variable = 'p' + std::to_string(pred);
|
||||
std::string variable = 'p' + std::to_string(pred) + '_' + suffix;
|
||||
shader.AddLine(variable + " = " + value + ';');
|
||||
declr_predicates.insert(std::move(variable));
|
||||
}
|
||||
@@ -707,7 +711,7 @@ private:
|
||||
if (index == static_cast<u64>(Pred::UnusedIndex))
|
||||
variable = "true";
|
||||
else
|
||||
variable = 'p' + std::to_string(index);
|
||||
variable = 'p' + std::to_string(index) + '_' + suffix;
|
||||
|
||||
if (negate) {
|
||||
return "!(" + variable + ')';
|
||||
@@ -728,10 +732,10 @@ private:
|
||||
const std::string& op_a, const std::string& op_b) const {
|
||||
using Tegra::Shader::PredCondition;
|
||||
static const std::unordered_map<PredCondition, const char*> PredicateComparisonStrings = {
|
||||
{PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="},
|
||||
{PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"},
|
||||
{PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="},
|
||||
{PredCondition::NotEqualWithNan, "!="},
|
||||
{PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="},
|
||||
{PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"},
|
||||
{PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="},
|
||||
{PredCondition::LessThanWithNan, "<"}, {PredCondition::NotEqualWithNan, "!="},
|
||||
};
|
||||
|
||||
const auto& comparison{PredicateComparisonStrings.find(condition)};
|
||||
@@ -739,7 +743,8 @@ private:
|
||||
"Unknown predicate comparison operation");
|
||||
|
||||
std::string predicate{'(' + op_a + ") " + comparison->second + " (" + op_b + ')'};
|
||||
if (condition == PredCondition::NotEqualWithNan) {
|
||||
if (condition == PredCondition::LessThanWithNan ||
|
||||
condition == PredCondition::NotEqualWithNan) {
|
||||
predicate += " || isnan(" + op_a + ") || isnan(" + op_b + ')';
|
||||
}
|
||||
|
||||
@@ -968,6 +973,29 @@ private:
|
||||
regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::FADD32I: {
|
||||
std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string op_b = GetImmediate32(instr);
|
||||
|
||||
if (instr.fadd32i.abs_a) {
|
||||
op_a = "abs(" + op_a + ')';
|
||||
}
|
||||
|
||||
if (instr.fadd32i.negate_a) {
|
||||
op_a = "-(" + op_a + ')';
|
||||
}
|
||||
|
||||
if (instr.fadd32i.abs_b) {
|
||||
op_b = "abs(" + op_b + ')';
|
||||
}
|
||||
|
||||
if (instr.fadd32i.negate_b) {
|
||||
op_b = "-(" + op_b + ')';
|
||||
}
|
||||
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1127,6 +1155,20 @@ private:
|
||||
WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::IMNMX_C:
|
||||
case OpCode::Id::IMNMX_R:
|
||||
case OpCode::Id::IMNMX_IMM: {
|
||||
ASSERT_MSG(instr.imnmx.exchange == Tegra::Shader::IMinMaxExchange::None,
|
||||
"Unimplemented");
|
||||
std::string condition =
|
||||
GetPredicateCondition(instr.imnmx.pred, instr.imnmx.negate_pred != 0);
|
||||
std::string parameters = op_a + ',' + op_b;
|
||||
regs.SetRegisterToInteger(instr.gpr0, instr.imnmx.is_signed, 0,
|
||||
'(' + condition + ") ? min(" + parameters + ") : max(" +
|
||||
parameters + ')',
|
||||
1, 1);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}",
|
||||
opcode->GetName());
|
||||
@@ -1213,6 +1255,9 @@ private:
|
||||
switch (instr.conversion.f2f.rounding) {
|
||||
case Tegra::Shader::F2fRoundingOp::None:
|
||||
break;
|
||||
case Tegra::Shader::F2fRoundingOp::Round:
|
||||
op_a = "roundEven(" + op_a + ')';
|
||||
break;
|
||||
case Tegra::Shader::F2fRoundingOp::Floor:
|
||||
op_a = "floor(" + op_a + ')';
|
||||
break;
|
||||
@@ -1477,6 +1522,36 @@ private:
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::PredicateSetPredicate: {
|
||||
std::string op_a =
|
||||
GetPredicateCondition(instr.psetp.pred12, instr.psetp.neg_pred12 != 0);
|
||||
std::string op_b =
|
||||
GetPredicateCondition(instr.psetp.pred29, instr.psetp.neg_pred29 != 0);
|
||||
|
||||
using Tegra::Shader::Pred;
|
||||
// We can't use the constant predicate as destination.
|
||||
ASSERT(instr.psetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string second_pred =
|
||||
GetPredicateCondition(instr.psetp.pred39, instr.psetp.neg_pred39 != 0);
|
||||
|
||||
std::string combiner = GetPredicateCombiner(instr.psetp.op);
|
||||
|
||||
std::string predicate =
|
||||
'(' + op_a + ") " + GetPredicateCombiner(instr.psetp.cond) + " (" + op_b + ')';
|
||||
|
||||
// Set the primary predicate to the result of Predicate OP SecondPredicate
|
||||
SetPredicate(instr.psetp.pred3,
|
||||
'(' + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
|
||||
if (instr.psetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
|
||||
// Set the secondary predicate to the result of !Predicate OP SecondPredicate,
|
||||
// if enabled
|
||||
SetPredicate(instr.psetp.pred0,
|
||||
"!(" + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::FloatSet: {
|
||||
std::string op_a = instr.fset.neg_a ? "-" : "";
|
||||
op_a += regs.GetRegisterAsFloat(instr.gpr8);
|
||||
@@ -1569,16 +1644,32 @@ private:
|
||||
shader.AddLine("color.a = " + regs.GetRegisterAsFloat(3) + ';');
|
||||
}
|
||||
|
||||
shader.AddLine("return true;");
|
||||
if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) {
|
||||
// If this is an unconditional exit then just end processing here, otherwise
|
||||
// we have to account for the possibility of the condition not being met, so
|
||||
// continue processing the next instruction.
|
||||
offset = PROGRAM_END - 1;
|
||||
switch (instr.flow.cond) {
|
||||
case Tegra::Shader::FlowCondition::Always:
|
||||
shader.AddLine("return true;");
|
||||
if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) {
|
||||
// If this is an unconditional exit then just end processing here,
|
||||
// otherwise we have to account for the possibility of the condition
|
||||
// not being met, so continue processing the next instruction.
|
||||
offset = PROGRAM_END - 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case Tegra::Shader::FlowCondition::Fcsm_Tr:
|
||||
// TODO(bunnei): What is this used for? If we assume this conditon is not
|
||||
// satisifed, dual vertex shaders in Farming Simulator make more sense
|
||||
LOG_CRITICAL(HW_GPU, "Skipping unknown FlowCondition::Fcsm_Tr");
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled flow condition: {}",
|
||||
static_cast<u32>(instr.flow.cond.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::KIL: {
|
||||
ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always);
|
||||
shader.AddLine("discard;");
|
||||
break;
|
||||
}
|
||||
@@ -1599,6 +1690,14 @@ private:
|
||||
// can ignore this when generating GLSL code.
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::SYNC:
|
||||
ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always);
|
||||
case OpCode::Id::DEPBAR: {
|
||||
// TODO(Subv): Find out if we actually have to care about these instructions or if
|
||||
// the GLSL compiler takes care of that for us.
|
||||
LOG_WARNING(HW_GPU, "DEPBAR/SYNC instruction is stubbed");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
|
||||
UNREACHABLE();
|
||||
@@ -1633,7 +1732,7 @@ private:
|
||||
return program_counter;
|
||||
}
|
||||
|
||||
void Generate() {
|
||||
void Generate(const std::string& suffix) {
|
||||
// Add declarations for all subroutines
|
||||
for (const auto& subroutine : subroutines) {
|
||||
shader.AddLine("bool " + subroutine.GetName() + "();");
|
||||
@@ -1641,7 +1740,7 @@ private:
|
||||
shader.AddNewLine();
|
||||
|
||||
// Add the main entry point
|
||||
shader.AddLine("bool exec_shader() {");
|
||||
shader.AddLine("bool exec_" + suffix + "() {");
|
||||
++shader.scope;
|
||||
CallSubroutine(GetSubroutine(main_offset, PROGRAM_END));
|
||||
--shader.scope;
|
||||
@@ -1704,7 +1803,7 @@ private:
|
||||
|
||||
/// Add declarations for registers
|
||||
void GenerateDeclarations() {
|
||||
regs.GenerateDeclarations();
|
||||
regs.GenerateDeclarations(suffix);
|
||||
|
||||
for (const auto& pred : declr_predicates) {
|
||||
declarations.AddLine("bool " + pred + " = false;");
|
||||
@@ -1717,27 +1816,30 @@ private:
|
||||
const ProgramCode& program_code;
|
||||
const u32 main_offset;
|
||||
Maxwell3D::Regs::ShaderStage stage;
|
||||
const std::string& suffix;
|
||||
|
||||
ShaderWriter shader;
|
||||
ShaderWriter declarations;
|
||||
GLSLRegisterManager regs{shader, declarations, stage};
|
||||
GLSLRegisterManager regs{shader, declarations, stage, suffix};
|
||||
|
||||
// Declarations
|
||||
std::set<std::string> declr_predicates;
|
||||
}; // namespace Decompiler
|
||||
|
||||
std::string GetCommonDeclarations() {
|
||||
std::string declarations = "bool exec_shader();\n";
|
||||
std::string declarations;
|
||||
declarations += "#define MAX_CONSTBUFFER_ELEMENTS " +
|
||||
std::to_string(RasterizerOpenGL::MaxConstbufferSize / (sizeof(GLvec4)));
|
||||
declarations += '\n';
|
||||
return declarations;
|
||||
}
|
||||
|
||||
boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
|
||||
Maxwell3D::Regs::ShaderStage stage) {
|
||||
Maxwell3D::Regs::ShaderStage stage,
|
||||
const std::string& suffix) {
|
||||
try {
|
||||
auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines();
|
||||
GLSLGenerator generator(subroutines, program_code, main_offset, stage);
|
||||
auto subroutines = ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines();
|
||||
GLSLGenerator generator(subroutines, program_code, main_offset, stage, suffix);
|
||||
return ProgramResult{generator.GetShaderCode(), generator.GetEntries()};
|
||||
} catch (const DecompileFail& exception) {
|
||||
LOG_ERROR(HW_GPU, "Shader decompilation failed: {}", exception.what());
|
||||
|
||||
@@ -20,7 +20,8 @@ using Tegra::Engines::Maxwell3D;
|
||||
std::string GetCommonDeclarations();
|
||||
|
||||
boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
|
||||
Maxwell3D::Regs::ShaderStage stage);
|
||||
Maxwell3D::Regs::ShaderStage stage,
|
||||
const std::string& suffix);
|
||||
|
||||
} // namespace Decompiler
|
||||
} // namespace GLShader
|
||||
|
||||
@@ -17,10 +17,17 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup, const MaxwellVSConf
|
||||
std::string out = "#version 430 core\n";
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
|
||||
out += Decompiler::GetCommonDeclarations();
|
||||
out += "bool exec_vertex();\n";
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
out += "bool exec_vertex_b();\n";
|
||||
}
|
||||
|
||||
ProgramResult program =
|
||||
Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
|
||||
Maxwell3D::Regs::ShaderStage::Vertex, "vertex")
|
||||
.get_value_or({});
|
||||
|
||||
ProgramResult program = Decompiler::DecompileProgram(setup.program_code, PROGRAM_OFFSET,
|
||||
Maxwell3D::Regs::ShaderStage::Vertex)
|
||||
.get_value_or({});
|
||||
out += R"(
|
||||
|
||||
out gl_PerVertex {
|
||||
@@ -34,7 +41,14 @@ layout (std140) uniform vs_config {
|
||||
};
|
||||
|
||||
void main() {
|
||||
exec_shader();
|
||||
exec_vertex();
|
||||
)";
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
out += " exec_vertex_b();";
|
||||
}
|
||||
|
||||
out += R"(
|
||||
|
||||
// Viewport can be flipped, which is unsupported by glViewport
|
||||
position.xy *= viewport_flip.xy;
|
||||
@@ -44,8 +58,19 @@ void main() {
|
||||
// For now, this is here to bring order in lieu of proper emulation
|
||||
position.w = 1.0;
|
||||
}
|
||||
|
||||
)";
|
||||
|
||||
out += program.first;
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
ProgramResult program_b =
|
||||
Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
|
||||
Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
|
||||
.get_value_or({});
|
||||
out += program_b.first;
|
||||
}
|
||||
|
||||
return {out, program.second};
|
||||
}
|
||||
|
||||
@@ -53,12 +78,13 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSCo
|
||||
std::string out = "#version 430 core\n";
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
|
||||
out += Decompiler::GetCommonDeclarations();
|
||||
out += "bool exec_fragment();\n";
|
||||
|
||||
ProgramResult program = Decompiler::DecompileProgram(setup.program_code, PROGRAM_OFFSET,
|
||||
Maxwell3D::Regs::ShaderStage::Fragment)
|
||||
.get_value_or({});
|
||||
ProgramResult program =
|
||||
Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
|
||||
Maxwell3D::Regs::ShaderStage::Fragment, "fragment")
|
||||
.get_value_or({});
|
||||
out += R"(
|
||||
|
||||
in vec4 position;
|
||||
out vec4 color;
|
||||
|
||||
@@ -67,7 +93,7 @@ layout (std140) uniform fs_config {
|
||||
};
|
||||
|
||||
void main() {
|
||||
exec_shader();
|
||||
exec_fragment();
|
||||
}
|
||||
|
||||
)";
|
||||
|
||||
@@ -115,21 +115,48 @@ struct ShaderEntries {
|
||||
using ProgramResult = std::pair<std::string, ShaderEntries>;
|
||||
|
||||
struct ShaderSetup {
|
||||
ShaderSetup(ProgramCode&& program_code) : program_code(std::move(program_code)) {}
|
||||
ShaderSetup(const ProgramCode& program_code) {
|
||||
program.code = program_code;
|
||||
}
|
||||
|
||||
struct {
|
||||
ProgramCode code;
|
||||
ProgramCode code_b; // Used for dual vertex shaders
|
||||
} program;
|
||||
|
||||
ProgramCode program_code;
|
||||
bool program_code_hash_dirty = true;
|
||||
|
||||
u64 GetProgramCodeHash() {
|
||||
if (program_code_hash_dirty) {
|
||||
program_code_hash = Common::ComputeHash64(&program_code, sizeof(program_code));
|
||||
program_code_hash = GetNewHash();
|
||||
program_code_hash_dirty = false;
|
||||
}
|
||||
return program_code_hash;
|
||||
}
|
||||
|
||||
/// Used in scenarios where we have a dual vertex shaders
|
||||
void SetProgramB(const ProgramCode& program_b) {
|
||||
program.code_b = program_b;
|
||||
has_program_b = true;
|
||||
}
|
||||
|
||||
bool IsDualProgram() const {
|
||||
return has_program_b;
|
||||
}
|
||||
|
||||
private:
|
||||
u64 GetNewHash() const {
|
||||
if (has_program_b) {
|
||||
// Compute hash over dual shader programs
|
||||
return Common::ComputeHash64(&program, sizeof(program));
|
||||
} else {
|
||||
// Compute hash over a single shader program
|
||||
return Common::ComputeHash64(&program.code, program.code.size());
|
||||
}
|
||||
}
|
||||
|
||||
u64 program_code_hash{};
|
||||
bool has_program_b{};
|
||||
};
|
||||
|
||||
struct MaxwellShaderConfigCommon {
|
||||
|
||||
@@ -29,6 +29,10 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
|
||||
switch (attrib.size) {
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
|
||||
return GL_UNSIGNED_BYTE;
|
||||
case Maxwell::VertexAttribute::Size::Size_16_16:
|
||||
return GL_UNSIGNED_SHORT;
|
||||
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
|
||||
return GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
|
||||
@@ -41,6 +45,10 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
|
||||
switch (attrib.size) {
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
|
||||
return GL_BYTE;
|
||||
case Maxwell::VertexAttribute::Size::Size_16_16:
|
||||
return GL_SHORT;
|
||||
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
|
||||
return GL_INT_2_10_10_10_REV;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
|
||||
@@ -203,20 +211,28 @@ inline GLenum SwizzleSource(Tegra::Texture::SwizzleSource source) {
|
||||
inline GLenum ComparisonOp(Maxwell::ComparisonOp comparison) {
|
||||
switch (comparison) {
|
||||
case Maxwell::ComparisonOp::Never:
|
||||
case Maxwell::ComparisonOp::NeverOld:
|
||||
return GL_NEVER;
|
||||
case Maxwell::ComparisonOp::Less:
|
||||
case Maxwell::ComparisonOp::LessOld:
|
||||
return GL_LESS;
|
||||
case Maxwell::ComparisonOp::Equal:
|
||||
case Maxwell::ComparisonOp::EqualOld:
|
||||
return GL_EQUAL;
|
||||
case Maxwell::ComparisonOp::LessEqual:
|
||||
case Maxwell::ComparisonOp::LessEqualOld:
|
||||
return GL_LEQUAL;
|
||||
case Maxwell::ComparisonOp::Greater:
|
||||
case Maxwell::ComparisonOp::GreaterOld:
|
||||
return GL_GREATER;
|
||||
case Maxwell::ComparisonOp::NotEqual:
|
||||
case Maxwell::ComparisonOp::NotEqualOld:
|
||||
return GL_NOTEQUAL;
|
||||
case Maxwell::ComparisonOp::GreaterEqual:
|
||||
case Maxwell::ComparisonOp::GreaterEqualOld:
|
||||
return GL_GEQUAL;
|
||||
case Maxwell::ComparisonOp::Always:
|
||||
case Maxwell::ComparisonOp::AlwaysOld:
|
||||
return GL_ALWAYS;
|
||||
}
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented comparison op={}", static_cast<u32>(comparison));
|
||||
|
||||
@@ -92,11 +92,24 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons
|
||||
return matrix;
|
||||
}
|
||||
|
||||
ScopeAcquireGLContext::ScopeAcquireGLContext() {
|
||||
if (Settings::values.use_multi_core) {
|
||||
VideoCore::g_emu_window->MakeCurrent();
|
||||
}
|
||||
}
|
||||
ScopeAcquireGLContext::~ScopeAcquireGLContext() {
|
||||
if (Settings::values.use_multi_core) {
|
||||
VideoCore::g_emu_window->DoneCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
RendererOpenGL::RendererOpenGL() = default;
|
||||
RendererOpenGL::~RendererOpenGL() = default;
|
||||
|
||||
/// Swap buffers (render frame)
|
||||
void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) {
|
||||
ScopeAcquireGLContext acquire_context;
|
||||
|
||||
Core::System::GetInstance().perf_stats.EndSystemFrame();
|
||||
|
||||
// Maintain the rasterizer's state as a priority
|
||||
@@ -141,6 +154,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
|
||||
// Framebuffer orientation handling
|
||||
framebuffer_transform_flags = framebuffer.transform_flags;
|
||||
framebuffer_crop_rect = framebuffer.crop_rect;
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default
|
||||
// only allows rows to have a memory alignement of 4.
|
||||
@@ -307,11 +321,24 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_MSG(framebuffer_crop_rect.top == 0, "Unimplemented");
|
||||
ASSERT_MSG(framebuffer_crop_rect.left == 0, "Unimplemented");
|
||||
|
||||
// Scale the output by the crop width/height. This is commonly used with 1280x720 rendering
|
||||
// (e.g. handheld mode) on a 1920x1080 framebuffer.
|
||||
f32 scale_u = 1.f, scale_v = 1.f;
|
||||
if (framebuffer_crop_rect.GetWidth() > 0) {
|
||||
scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) / screen_info.texture.width;
|
||||
}
|
||||
if (framebuffer_crop_rect.GetHeight() > 0) {
|
||||
scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) / screen_info.texture.height;
|
||||
}
|
||||
|
||||
std::array<ScreenRectVertex, 4> vertices = {{
|
||||
ScreenRectVertex(x, y, texcoords.top, left),
|
||||
ScreenRectVertex(x + w, y, texcoords.bottom, left),
|
||||
ScreenRectVertex(x, y + h, texcoords.top, right),
|
||||
ScreenRectVertex(x + w, y + h, texcoords.bottom, right),
|
||||
ScreenRectVertex(x, y, texcoords.top * scale_u, left * scale_v),
|
||||
ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left * scale_v),
|
||||
ScreenRectVertex(x, y + h, texcoords.top * scale_u, right * scale_v),
|
||||
ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v),
|
||||
}};
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.display_texture;
|
||||
@@ -418,7 +445,7 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum
|
||||
|
||||
/// Initialize the renderer
|
||||
bool RendererOpenGL::Init() {
|
||||
render_window->MakeCurrent();
|
||||
ScopeAcquireGLContext acquire_context;
|
||||
|
||||
if (GLAD_GL_KHR_debug) {
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
|
||||
@@ -31,6 +31,13 @@ struct ScreenInfo {
|
||||
TextureInfo texture;
|
||||
};
|
||||
|
||||
/// Helper class to acquire/release OpenGL context within a given scope
|
||||
class ScopeAcquireGLContext : NonCopyable {
|
||||
public:
|
||||
ScopeAcquireGLContext();
|
||||
~ScopeAcquireGLContext();
|
||||
};
|
||||
|
||||
class RendererOpenGL : public RendererBase {
|
||||
public:
|
||||
RendererOpenGL();
|
||||
@@ -90,4 +97,5 @@ private:
|
||||
|
||||
/// Used for transforming the framebuffer orientation
|
||||
Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
|
||||
MathUtil::Rectangle<int> framebuffer_crop_rect;
|
||||
};
|
||||
|
||||
@@ -25,16 +25,15 @@
|
||||
|
||||
class BitStream {
|
||||
public:
|
||||
BitStream(unsigned char* ptr, int nBits = 0, int start_offset = 0)
|
||||
: m_BitsWritten(0), m_BitsRead(0), m_NumBits(nBits), m_CurByte(ptr),
|
||||
m_NextBit(start_offset % 8), done(false) {}
|
||||
explicit BitStream(unsigned char* ptr, int nBits = 0, int start_offset = 0)
|
||||
: m_NumBits(nBits), m_CurByte(ptr), m_NextBit(start_offset % 8) {}
|
||||
|
||||
~BitStream() = default;
|
||||
|
||||
int GetBitsWritten() const {
|
||||
return m_BitsWritten;
|
||||
}
|
||||
|
||||
~BitStream() {}
|
||||
|
||||
void WriteBitsR(unsigned int val, unsigned int nBits) {
|
||||
for (unsigned int i = 0; i < nBits; i++) {
|
||||
WriteBit((val >> (nBits - i - 1)) & 1);
|
||||
@@ -95,33 +94,28 @@ private:
|
||||
done = done || ++m_BitsWritten >= m_NumBits;
|
||||
}
|
||||
|
||||
int m_BitsWritten;
|
||||
int m_BitsWritten = 0;
|
||||
const int m_NumBits;
|
||||
unsigned char* m_CurByte;
|
||||
int m_NextBit;
|
||||
int m_BitsRead;
|
||||
int m_NextBit = 0;
|
||||
int m_BitsRead = 0;
|
||||
|
||||
bool done;
|
||||
bool done = false;
|
||||
};
|
||||
|
||||
template <typename IntType>
|
||||
class Bits {
|
||||
private:
|
||||
const IntType& m_Bits;
|
||||
|
||||
// Don't copy
|
||||
Bits() {}
|
||||
Bits(const Bits&) {}
|
||||
Bits& operator=(const Bits&) {}
|
||||
|
||||
public:
|
||||
explicit Bits(IntType& v) : m_Bits(v) {}
|
||||
explicit Bits(const IntType& v) : m_Bits(v) {}
|
||||
|
||||
uint8_t operator[](uint32_t bitPos) {
|
||||
Bits(const Bits&) = delete;
|
||||
Bits& operator=(const Bits&) = delete;
|
||||
|
||||
uint8_t operator[](uint32_t bitPos) const {
|
||||
return static_cast<uint8_t>((m_Bits >> bitPos) & 1);
|
||||
}
|
||||
|
||||
IntType operator()(uint32_t start, uint32_t end) {
|
||||
IntType operator()(uint32_t start, uint32_t end) const {
|
||||
if (start == end) {
|
||||
return (*this)[start];
|
||||
} else if (start > end) {
|
||||
@@ -133,6 +127,9 @@ public:
|
||||
uint64_t mask = (1 << (end - start + 1)) - 1;
|
||||
return (m_Bits >> start) & mask;
|
||||
}
|
||||
|
||||
private:
|
||||
const IntType& m_Bits;
|
||||
};
|
||||
|
||||
enum EIntegerEncoding { eIntegerEncoding_JustBits, eIntegerEncoding_Quint, eIntegerEncoding_Trit };
|
||||
@@ -186,12 +183,12 @@ public:
|
||||
m_QuintValue = val;
|
||||
}
|
||||
|
||||
bool MatchesEncoding(const IntegerEncodedValue& other) {
|
||||
bool MatchesEncoding(const IntegerEncodedValue& other) const {
|
||||
return m_Encoding == other.m_Encoding && m_NumBits == other.m_NumBits;
|
||||
}
|
||||
|
||||
// Returns the number of bits required to encode nVals values.
|
||||
uint32_t GetBitLength(uint32_t nVals) {
|
||||
uint32_t GetBitLength(uint32_t nVals) const {
|
||||
uint32_t totalBits = m_NumBits * nVals;
|
||||
if (m_Encoding == eIntegerEncoding_Trit) {
|
||||
totalBits += (nVals * 8 + 4) / 5;
|
||||
@@ -382,19 +379,15 @@ private:
|
||||
namespace ASTCC {
|
||||
|
||||
struct TexelWeightParams {
|
||||
uint32_t m_Width;
|
||||
uint32_t m_Height;
|
||||
bool m_bDualPlane;
|
||||
uint32_t m_MaxWeight;
|
||||
bool m_bError;
|
||||
bool m_bVoidExtentLDR;
|
||||
bool m_bVoidExtentHDR;
|
||||
uint32_t m_Width = 0;
|
||||
uint32_t m_Height = 0;
|
||||
bool m_bDualPlane = false;
|
||||
uint32_t m_MaxWeight = 0;
|
||||
bool m_bError = false;
|
||||
bool m_bVoidExtentLDR = false;
|
||||
bool m_bVoidExtentHDR = false;
|
||||
|
||||
TexelWeightParams() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
uint32_t GetPackedBitSize() {
|
||||
uint32_t GetPackedBitSize() const {
|
||||
// How many indices do we have?
|
||||
uint32_t nIdxs = m_Height * m_Width;
|
||||
if (m_bDualPlane) {
|
||||
@@ -413,7 +406,7 @@ struct TexelWeightParams {
|
||||
}
|
||||
};
|
||||
|
||||
TexelWeightParams DecodeBlockInfo(BitStream& strm) {
|
||||
static TexelWeightParams DecodeBlockInfo(BitStream& strm) {
|
||||
TexelWeightParams params;
|
||||
|
||||
// Read the entire block mode all at once
|
||||
@@ -612,8 +605,8 @@ TexelWeightParams DecodeBlockInfo(BitStream& strm) {
|
||||
return params;
|
||||
}
|
||||
|
||||
void FillVoidExtentLDR(BitStream& strm, uint32_t* const outBuf, uint32_t blockWidth,
|
||||
uint32_t blockHeight) {
|
||||
static void FillVoidExtentLDR(BitStream& strm, uint32_t* const outBuf, uint32_t blockWidth,
|
||||
uint32_t blockHeight) {
|
||||
// Don't actually care about the void extent, just read the bits...
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
strm.ReadBits(13);
|
||||
@@ -628,23 +621,25 @@ void FillVoidExtentLDR(BitStream& strm, uint32_t* const outBuf, uint32_t blockWi
|
||||
uint32_t rgba = (r >> 8) | (g & 0xFF00) | (static_cast<uint32_t>(b) & 0xFF00) << 8 |
|
||||
(static_cast<uint32_t>(a) & 0xFF00) << 16;
|
||||
|
||||
for (uint32_t j = 0; j < blockHeight; j++)
|
||||
for (uint32_t j = 0; j < blockHeight; j++) {
|
||||
for (uint32_t i = 0; i < blockWidth; i++) {
|
||||
outBuf[j * blockWidth + i] = rgba;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FillError(uint32_t* outBuf, uint32_t blockWidth, uint32_t blockHeight) {
|
||||
for (uint32_t j = 0; j < blockHeight; j++)
|
||||
static void FillError(uint32_t* outBuf, uint32_t blockWidth, uint32_t blockHeight) {
|
||||
for (uint32_t j = 0; j < blockHeight; j++) {
|
||||
for (uint32_t i = 0; i < blockWidth; i++) {
|
||||
outBuf[j * blockWidth + i] = 0xFFFF00FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replicates low numBits such that [(toBit - 1):(toBit - 1 - fromBit)]
|
||||
// is the same as [(numBits - 1):0] and repeats all the way down.
|
||||
template <typename IntType>
|
||||
IntType Replicate(const IntType& val, uint32_t numBits, uint32_t toBit) {
|
||||
static IntType Replicate(const IntType& val, uint32_t numBits, uint32_t toBit) {
|
||||
if (numBits == 0)
|
||||
return 0;
|
||||
if (toBit == 0)
|
||||
@@ -668,27 +663,15 @@ IntType Replicate(const IntType& val, uint32_t numBits, uint32_t toBit) {
|
||||
|
||||
class Pixel {
|
||||
protected:
|
||||
typedef int16_t ChannelType;
|
||||
uint8_t m_BitDepth[4];
|
||||
int16_t color[4];
|
||||
using ChannelType = int16_t;
|
||||
uint8_t m_BitDepth[4] = {8, 8, 8, 8};
|
||||
int16_t color[4] = {};
|
||||
|
||||
public:
|
||||
Pixel() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
m_BitDepth[i] = 8;
|
||||
color[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Pixel(ChannelType a, ChannelType r, ChannelType g, ChannelType b, unsigned bitDepth = 8) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
m_BitDepth[i] = bitDepth;
|
||||
|
||||
color[0] = a;
|
||||
color[1] = r;
|
||||
color[2] = g;
|
||||
color[3] = b;
|
||||
}
|
||||
Pixel() = default;
|
||||
Pixel(ChannelType a, ChannelType r, ChannelType g, ChannelType b, unsigned bitDepth = 8)
|
||||
: m_BitDepth{uint8_t(bitDepth), uint8_t(bitDepth), uint8_t(bitDepth), uint8_t(bitDepth)},
|
||||
color{a, r, g, b} {}
|
||||
|
||||
// Changes the depth of each pixel. This scales the values to
|
||||
// the appropriate bit depth by either truncating the least
|
||||
@@ -807,8 +790,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void DecodeColorValues(uint32_t* out, uint8_t* data, uint32_t* modes, const uint32_t nPartitions,
|
||||
const uint32_t nBitsForColorData) {
|
||||
static void DecodeColorValues(uint32_t* out, uint8_t* data, const uint32_t* modes,
|
||||
const uint32_t nPartitions, const uint32_t nBitsForColorData) {
|
||||
// First figure out how many color values we have
|
||||
uint32_t nValues = 0;
|
||||
for (uint32_t i = 0; i < nPartitions; i++) {
|
||||
@@ -844,8 +827,7 @@ void DecodeColorValues(uint32_t* out, uint8_t* data, uint32_t* modes, const uint
|
||||
// Once we have the decoded values, we need to dequantize them to the 0-255 range
|
||||
// This procedure is outlined in ASTC spec C.2.13
|
||||
uint32_t outIdx = 0;
|
||||
std::vector<IntegerEncodedValue>::const_iterator itr;
|
||||
for (itr = decodedColorValues.begin(); itr != decodedColorValues.end(); itr++) {
|
||||
for (auto itr = decodedColorValues.begin(); itr != decodedColorValues.end(); ++itr) {
|
||||
// Have we already decoded all that we need?
|
||||
if (outIdx >= nValues) {
|
||||
break;
|
||||
@@ -978,7 +960,7 @@ void DecodeColorValues(uint32_t* out, uint8_t* data, uint32_t* modes, const uint
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t UnquantizeTexelWeight(const IntegerEncodedValue& val) {
|
||||
static uint32_t UnquantizeTexelWeight(const IntegerEncodedValue& val) {
|
||||
uint32_t bitval = val.GetBitValue();
|
||||
uint32_t bitlen = val.BaseBitLength();
|
||||
|
||||
@@ -1067,17 +1049,18 @@ uint32_t UnquantizeTexelWeight(const IntegerEncodedValue& val) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void UnquantizeTexelWeights(uint32_t out[2][144], std::vector<IntegerEncodedValue>& weights,
|
||||
const TexelWeightParams& params, const uint32_t blockWidth,
|
||||
const uint32_t blockHeight) {
|
||||
static void UnquantizeTexelWeights(uint32_t out[2][144],
|
||||
const std::vector<IntegerEncodedValue>& weights,
|
||||
const TexelWeightParams& params, const uint32_t blockWidth,
|
||||
const uint32_t blockHeight) {
|
||||
uint32_t weightIdx = 0;
|
||||
uint32_t unquantized[2][144];
|
||||
std::vector<IntegerEncodedValue>::const_iterator itr;
|
||||
for (itr = weights.begin(); itr != weights.end(); itr++) {
|
||||
|
||||
for (auto itr = weights.begin(); itr != weights.end(); ++itr) {
|
||||
unquantized[0][weightIdx] = UnquantizeTexelWeight(*itr);
|
||||
|
||||
if (params.m_bDualPlane) {
|
||||
itr++;
|
||||
++itr;
|
||||
unquantized[1][weightIdx] = UnquantizeTexelWeight(*itr);
|
||||
if (itr == weights.end()) {
|
||||
break;
|
||||
@@ -1261,8 +1244,8 @@ static inline uint32_t Select2DPartition(int32_t seed, int32_t x, int32_t y, int
|
||||
}
|
||||
|
||||
// Section C.2.14
|
||||
void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const uint32_t*& colorValues,
|
||||
uint32_t colorEndpointMode) {
|
||||
static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const uint32_t*& colorValues,
|
||||
uint32_t colorEndpointMode) {
|
||||
#define READ_UINT_VALUES(N) \
|
||||
uint32_t v[N]; \
|
||||
for (uint32_t i = 0; i < N; i++) { \
|
||||
@@ -1382,8 +1365,8 @@ void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const uint32_t*& colorValues,
|
||||
#undef READ_INT_VALUES
|
||||
}
|
||||
|
||||
void DecompressBlock(uint8_t inBuf[16], const uint32_t blockWidth, const uint32_t blockHeight,
|
||||
uint32_t* outBuf) {
|
||||
static void DecompressBlock(uint8_t inBuf[16], const uint32_t blockWidth,
|
||||
const uint32_t blockHeight, uint32_t* outBuf) {
|
||||
BitStream strm(inBuf);
|
||||
TexelWeightParams weightParams = DecodeBlockInfo(strm);
|
||||
|
||||
@@ -1617,8 +1600,7 @@ namespace Tegra::Texture::ASTC {
|
||||
std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint32_t height,
|
||||
uint32_t block_width, uint32_t block_height) {
|
||||
uint32_t blockIdx = 0;
|
||||
std::vector<uint8_t> outData;
|
||||
outData.resize(height * width * 4);
|
||||
std::vector<uint8_t> outData(height * width * 4);
|
||||
for (uint32_t j = 0; j < height; j += block_height) {
|
||||
for (uint32_t i = 0; i < width; i += block_width) {
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||
return 8;
|
||||
case TextureFormat::DXT23:
|
||||
case TextureFormat::DXT45:
|
||||
case TextureFormat::BC7U:
|
||||
// In this case a 'pixel' actually refers to a 4x4 tile.
|
||||
return 16;
|
||||
case TextureFormat::ASTC_2D_4X4:
|
||||
@@ -61,6 +62,7 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||
return 4;
|
||||
case TextureFormat::A1B5G5R5:
|
||||
case TextureFormat::B5G6R5:
|
||||
case TextureFormat::G8R8:
|
||||
return 2;
|
||||
case TextureFormat::R8:
|
||||
return 1;
|
||||
@@ -76,7 +78,11 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||
|
||||
static u32 DepthBytesPerPixel(DepthFormat format) {
|
||||
switch (format) {
|
||||
case DepthFormat::Z16_UNORM:
|
||||
return 2;
|
||||
case DepthFormat::S8_Z24_UNORM:
|
||||
case DepthFormat::Z24_S8_UNORM:
|
||||
case DepthFormat::Z32_FLOAT:
|
||||
return 4;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Format not implemented");
|
||||
@@ -96,6 +102,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width,
|
||||
case TextureFormat::DXT23:
|
||||
case TextureFormat::DXT45:
|
||||
case TextureFormat::DXN1:
|
||||
case TextureFormat::BC7U:
|
||||
// In the DXT and DXN formats, each 4x4 tile is swizzled instead of just individual pixel
|
||||
// values.
|
||||
CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data,
|
||||
@@ -106,6 +113,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width,
|
||||
case TextureFormat::A1B5G5R5:
|
||||
case TextureFormat::B5G6R5:
|
||||
case TextureFormat::R8:
|
||||
case TextureFormat::G8R8:
|
||||
case TextureFormat::R16_G16_B16_A16:
|
||||
case TextureFormat::R32_G32_B32_A32:
|
||||
case TextureFormat::BF10GF11RF11:
|
||||
@@ -129,7 +137,10 @@ std::vector<u8> UnswizzleDepthTexture(VAddr address, DepthFormat format, u32 wid
|
||||
std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
|
||||
|
||||
switch (format) {
|
||||
case DepthFormat::Z16_UNORM:
|
||||
case DepthFormat::S8_Z24_UNORM:
|
||||
case DepthFormat::Z24_S8_UNORM:
|
||||
case DepthFormat::Z32_FLOAT:
|
||||
CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data,
|
||||
unswizzled_data.data(), true, block_height);
|
||||
break;
|
||||
@@ -151,12 +162,14 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
|
||||
case TextureFormat::DXT23:
|
||||
case TextureFormat::DXT45:
|
||||
case TextureFormat::DXN1:
|
||||
case TextureFormat::BC7U:
|
||||
case TextureFormat::ASTC_2D_4X4:
|
||||
case TextureFormat::A8R8G8B8:
|
||||
case TextureFormat::A2B10G10R10:
|
||||
case TextureFormat::A1B5G5R5:
|
||||
case TextureFormat::B5G6R5:
|
||||
case TextureFormat::R8:
|
||||
case TextureFormat::G8R8:
|
||||
case TextureFormat::BF10GF11RF11:
|
||||
case TextureFormat::R32_G32_B32_A32:
|
||||
// TODO(Subv): For the time being just forward the same data without any decoding.
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
|
||||
|
||||
void EmuThread::run() {
|
||||
render_window->MakeCurrent();
|
||||
if (!Settings::values.use_multi_core) {
|
||||
// Single core mode must acquire OpenGL context for entire emulation session
|
||||
render_window->MakeCurrent();
|
||||
}
|
||||
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
|
||||
@@ -127,13 +130,14 @@ void GRenderWindow::moveContext() {
|
||||
}
|
||||
|
||||
void GRenderWindow::SwapBuffers() {
|
||||
#if !defined(QT_NO_DEBUG)
|
||||
// Qt debug runtime prints a bogus warning on the console if you haven't called makeCurrent
|
||||
// since the last time you called swapBuffers. This presumably means something if you're using
|
||||
// QGLWidget the "regular" way, but in our multi-threaded use case is harmless since we never
|
||||
// call doneCurrent in this thread.
|
||||
// In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`,
|
||||
// since we never call `doneCurrent` in this thread.
|
||||
// However:
|
||||
// - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
|
||||
// since the last time `swapBuffers` was executed;
|
||||
// - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks.
|
||||
child->makeCurrent();
|
||||
#endif
|
||||
|
||||
child->swapBuffers();
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ void Config::ReadValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
Settings::values.use_docked_mode = qt_config->value("use_docked_mode", true).toBool();
|
||||
Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Miscellaneous");
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
|
||||
namespace Debugger {
|
||||
void ToggleConsole() {
|
||||
static bool console_shown = false;
|
||||
if (console_shown == UISettings::values.show_console) {
|
||||
return;
|
||||
} else {
|
||||
console_shown = UISettings::values.show_console;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && !defined(_DEBUG)
|
||||
FILE* temp;
|
||||
if (UISettings::values.show_console) {
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
#include "game_list_p.h"
|
||||
#include "ui_settings.h"
|
||||
|
||||
GameList::SearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) {
|
||||
this->gamelist = gamelist;
|
||||
edit_filter_text_old = "";
|
||||
}
|
||||
GameList::SearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {}
|
||||
|
||||
// EventFilter in order to process systemkeys while editing the searchfield
|
||||
bool GameList::SearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) {
|
||||
@@ -141,10 +138,12 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} {
|
||||
* @param userinput String containing all words getting checked
|
||||
* @return true if the haystack contains all words of userinput
|
||||
*/
|
||||
bool GameList::containsAllWords(QString haystack, QString userinput) {
|
||||
QStringList userinput_split = userinput.split(" ", QString::SplitBehavior::SkipEmptyParts);
|
||||
bool GameList::ContainsAllWords(const QString& haystack, const QString& userinput) const {
|
||||
const QStringList userinput_split =
|
||||
userinput.split(' ', QString::SplitBehavior::SkipEmptyParts);
|
||||
|
||||
return std::all_of(userinput_split.begin(), userinput_split.end(),
|
||||
[haystack](QString s) { return haystack.contains(s); });
|
||||
[&haystack](const QString& s) { return haystack.contains(s); });
|
||||
}
|
||||
|
||||
// Event in order to filter the gamelist after editing the searchfield
|
||||
@@ -178,7 +177,7 @@ void GameList::onTextChanged(const QString& newText) {
|
||||
// The search is case insensitive because of toLower()
|
||||
// I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
|
||||
// multiple conversions of edit_filter_text for each game in the gamelist
|
||||
if (containsAllWords(file_name.append(" ").append(file_title), edit_filter_text) ||
|
||||
if (ContainsAllWords(file_name.append(' ').append(file_title), edit_filter_text) ||
|
||||
(file_programmid.count() == 16 && edit_filter_text.contains(file_programmid))) {
|
||||
tree_view->setRowHidden(i, root_index, false);
|
||||
++result_count;
|
||||
|
||||
@@ -89,7 +89,7 @@ private:
|
||||
|
||||
void PopupContextMenu(const QPoint& menu_location);
|
||||
void RefreshGameDirectory();
|
||||
bool containsAllWords(QString haystack, QString userinput);
|
||||
bool ContainsAllWords(const QString& haystack, const QString& userinput) const;
|
||||
|
||||
SearchField* search_field;
|
||||
GMainWindow* main_window = nullptr;
|
||||
|
||||
@@ -374,6 +374,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
|
||||
const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
|
||||
|
||||
render_window->DoneCurrent();
|
||||
|
||||
if (result != Core::System::ResultStatus::Success) {
|
||||
switch (result) {
|
||||
case Core::System::ResultStatus::ErrorGetLoader:
|
||||
@@ -908,8 +910,6 @@ void GMainWindow::UpdateUITheme() {
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
||||
|
||||
MicroProfileOnThreadCreate("Frontend");
|
||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||
|
||||
@@ -918,6 +918,7 @@ int main(int argc, char* argv[]) {
|
||||
QCoreApplication::setApplicationName("yuzu");
|
||||
|
||||
QApplication::setAttribute(Qt::AA_X11InitThreads);
|
||||
QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// Qt changes the locale and causes issues in float conversion using std::to_string() when
|
||||
|
||||
@@ -110,7 +110,7 @@ void Config::ReadValues() {
|
||||
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
|
||||
|
||||
// System
|
||||
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", true);
|
||||
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
|
||||
|
||||
// Miscellaneous
|
||||
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
|
||||
|
||||
@@ -163,10 +163,10 @@ use_virtual_sd =
|
||||
|
||||
[System]
|
||||
# Whether the system is docked
|
||||
# 1 (default): Yes, 0: No
|
||||
# 1: Yes, 0 (default): No
|
||||
use_docked_mode =
|
||||
|
||||
# The system region that Citra will use during emulation
|
||||
# The system region that yuzu will use during emulation
|
||||
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
|
||||
region_value =
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -137,12 +137,12 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
gl_context = SDL_GL_CreateContext(render_window);
|
||||
|
||||
if (gl_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
#include "yuzu_cmd/config.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <getopt.h>
|
||||
#else
|
||||
#include <getopt.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -127,6 +125,7 @@ int main(int argc, char** argv) {
|
||||
#endif
|
||||
|
||||
Log::Filter log_filter(Log::Level::Debug);
|
||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||
Log::SetGlobalFilter(log_filter);
|
||||
|
||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
||||
@@ -142,8 +141,6 @@ int main(int argc, char** argv) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||
|
||||
// Apply the command line arguments
|
||||
Settings::values.gdbstub_port = gdb_port;
|
||||
Settings::values.use_gdbstub = use_gdbstub;
|
||||
@@ -151,6 +148,11 @@ int main(int argc, char** argv) {
|
||||
|
||||
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
|
||||
|
||||
if (!Settings::values.use_multi_core) {
|
||||
// Single core mode must acquire OpenGL context for entire emulation session
|
||||
emu_window->MakeCurrent();
|
||||
}
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
SCOPE_EXIT({ system.Shutdown(); });
|
||||
|
||||
Reference in New Issue
Block a user