Compare commits
62 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93cba6f699 | ||
|
|
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 | ||
|
|
49c0c081c4 | ||
|
|
4757ffdcce |
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: f7d11baa1c...dfdec797e3
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ 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,49 @@
|
||||
|
||||
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
|
||||
@@ -26,6 +64,12 @@ ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path&
|
||||
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.
|
||||
@@ -36,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
|
||||
|
||||
@@ -16,30 +16,20 @@
|
||||
namespace Kernel {
|
||||
|
||||
static const char* GetMemoryStateName(MemoryState state) {
|
||||
static const char* names[] = {
|
||||
"Unmapped",
|
||||
"Io",
|
||||
"Normal",
|
||||
"CodeStatic",
|
||||
"CodeMutable",
|
||||
"Heap",
|
||||
"Shared",
|
||||
"Unknown1"
|
||||
"ModuleCodeStatic",
|
||||
"ModuleCodeMutable",
|
||||
"IpcBuffer0",
|
||||
"Mapped",
|
||||
"ThreadLocal",
|
||||
"TransferMemoryIsolated",
|
||||
"TransferMemory",
|
||||
"ProcessMemory",
|
||||
"Unknown2"
|
||||
"IpcBuffer1",
|
||||
"IpcBuffer3",
|
||||
"KernelStack",
|
||||
static constexpr const char* names[] = {
|
||||
"Unmapped", "Io",
|
||||
"Normal", "CodeStatic",
|
||||
"CodeMutable", "Heap",
|
||||
"Shared", "Unknown1",
|
||||
"ModuleCodeStatic", "ModuleCodeMutable",
|
||||
"IpcBuffer0", "Mapped",
|
||||
"ThreadLocal", "TransferMemoryIsolated",
|
||||
"TransferMemory", "ProcessMemory",
|
||||
"Unknown2", "IpcBuffer1",
|
||||
"IpcBuffer3", "KernelStack",
|
||||
};
|
||||
|
||||
return names[(int)state];
|
||||
return names[static_cast<int>(state)];
|
||||
}
|
||||
|
||||
bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -13,11 +13,21 @@
|
||||
#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)
|
||||
@@ -487,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");
|
||||
|
||||
@@ -508,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);
|
||||
@@ -519,23 +517,26 @@ 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};
|
||||
|
||||
// TODO(Subv): Read the input parameters and mount the requested savedata instead of always
|
||||
// mounting the current process' savedata.
|
||||
FileSys::Path unused;
|
||||
auto filesystem = OpenFileSystem(Type::SaveData, unused);
|
||||
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};
|
||||
@@ -559,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};
|
||||
@@ -568,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};
|
||||
@@ -583,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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -398,27 +398,6 @@ 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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessClearBuffers() {
|
||||
ASSERT(regs.clear_buffers.R == regs.clear_buffers.G &&
|
||||
regs.clear_buffers.R == regs.clear_buffers.B &&
|
||||
|
||||
@@ -379,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);
|
||||
@@ -512,7 +520,18 @@ public:
|
||||
u32 enable[NumRenderTargets];
|
||||
} blend;
|
||||
|
||||
INSERT_PADDING_WORDS(0xB);
|
||||
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;
|
||||
@@ -548,7 +567,17 @@ public:
|
||||
}
|
||||
} tic;
|
||||
|
||||
INSERT_PADDING_WORDS(0x21);
|
||||
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;
|
||||
@@ -780,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;
|
||||
|
||||
@@ -846,10 +872,12 @@ 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);
|
||||
|
||||
@@ -142,6 +142,7 @@ enum class PredCondition : u64 {
|
||||
GreaterThan = 4,
|
||||
NotEqual = 5,
|
||||
GreaterEqual = 6,
|
||||
LessThanWithNan = 9,
|
||||
NotEqualWithNan = 13,
|
||||
// TODO(Subv): Other condition types
|
||||
};
|
||||
@@ -201,6 +202,11 @@ enum class IMinMaxExchange : u64 {
|
||||
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;
|
||||
@@ -315,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;
|
||||
|
||||
@@ -67,6 +67,7 @@ struct FramebufferConfig {
|
||||
|
||||
using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
|
||||
TransformFlags transform_flags;
|
||||
MathUtil::Rectangle<int> crop_rect;
|
||||
};
|
||||
|
||||
namespace Engines {
|
||||
|
||||
@@ -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();
|
||||
@@ -301,9 +324,6 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c
|
||||
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;
|
||||
|
||||
@@ -368,11 +388,20 @@ void RasterizerOpenGL::Clear() {
|
||||
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);
|
||||
|
||||
@@ -399,9 +428,12 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
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();
|
||||
|
||||
@@ -605,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;
|
||||
@@ -674,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.");
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
{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,
|
||||
@@ -112,6 +113,8 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
{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) {
|
||||
@@ -194,8 +197,9 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
|
||||
MortonCopy<true, PixelFormat::DXT1>, MortonCopy<true, PixelFormat::DXT23>,
|
||||
MortonCopy<true, PixelFormat::DXT45>, MortonCopy<true, PixelFormat::DXN1>,
|
||||
MortonCopy<true, PixelFormat::BC7U>, MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
|
||||
MortonCopy<true, PixelFormat::Z24S8>, MortonCopy<true, PixelFormat::S8Z24>,
|
||||
MortonCopy<true, PixelFormat::Z32F>,
|
||||
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),
|
||||
@@ -215,10 +219,12 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::ABGR8>,
|
||||
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
|
||||
@@ -271,9 +277,10 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
|
||||
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{y * 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);
|
||||
@@ -281,6 +288,19 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -301,6 +321,11 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,13 +37,15 @@ struct SurfaceParams {
|
||||
DXN1 = 11, // This is also known as BC4
|
||||
BC7U = 12,
|
||||
ASTC_2D_4X4 = 13,
|
||||
G8R8 = 14,
|
||||
|
||||
MaxColorFormat,
|
||||
|
||||
// DepthStencil formats
|
||||
Z24S8 = 14,
|
||||
S8Z24 = 15,
|
||||
Z32F = 16,
|
||||
Z24S8 = 15,
|
||||
S8Z24 = 16,
|
||||
Z32F = 17,
|
||||
Z16 = 18,
|
||||
|
||||
MaxDepthStencilFormat,
|
||||
|
||||
@@ -95,9 +97,11 @@ struct SurfaceParams {
|
||||
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());
|
||||
@@ -123,9 +127,11 @@ struct SurfaceParams {
|
||||
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());
|
||||
@@ -143,6 +149,8 @@ struct SurfaceParams {
|
||||
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();
|
||||
@@ -181,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:
|
||||
@@ -218,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:
|
||||
@@ -249,6 +261,8 @@ struct SurfaceParams {
|
||||
return Tegra::DepthFormat::Z24_S8_UNORM;
|
||||
case PixelFormat::Z32F:
|
||||
return Tegra::DepthFormat::Z32_FLOAT;
|
||||
case PixelFormat::Z16:
|
||||
return Tegra::DepthFormat::Z16_UNORM;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@@ -295,6 +309,7 @@ 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;
|
||||
|
||||
@@ -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 + ')';
|
||||
}
|
||||
|
||||
@@ -1639,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;
|
||||
}
|
||||
@@ -1669,8 +1690,9 @@ private:
|
||||
// can ignore this when generating GLSL code.
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::DEPBAR:
|
||||
case OpCode::Id::SYNC: {
|
||||
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");
|
||||
@@ -1710,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() + "();");
|
||||
@@ -1718,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;
|
||||
@@ -1781,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;");
|
||||
@@ -1794,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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -62,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;
|
||||
@@ -77,6 +78,8 @@ 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:
|
||||
@@ -110,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:
|
||||
@@ -133,6 +137,7 @@ 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:
|
||||
@@ -164,6 +169,7 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
|
||||
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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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:
|
||||
@@ -916,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,7 +163,7 @@ 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 yuzu will use during emulation
|
||||
|
||||
@@ -148,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